diff --git a/api/numerics/samples_stats_counter.h b/api/numerics/samples_stats_counter.h index 16d5d2a891..5e2204196a 100644 --- a/api/numerics/samples_stats_counter.h +++ b/api/numerics/samples_stats_counter.h @@ -11,6 +11,8 @@ #ifndef API_NUMERICS_SAMPLES_STATS_COUNTER_H_ #define API_NUMERICS_SAMPLES_STATS_COUNTER_H_ +#include +#include #include #include "api/array_view.h" @@ -27,6 +29,8 @@ class SamplesStatsCounter { struct StatsSample { double value; Timestamp time; + // Sample's specific metadata. + std::map metadata; }; SamplesStatsCounter(); diff --git a/api/test/metrics/BUILD.gn b/api/test/metrics/BUILD.gn index 099ee8a283..544808ffa7 100644 --- a/api/test/metrics/BUILD.gn +++ b/api/test/metrics/BUILD.gn @@ -12,6 +12,7 @@ group("metrics") { deps = [ ":metric", ":metrics_exporter", + ":metrics_logger_and_exporter", ":stdout_metrics_exporter", ] } @@ -20,7 +21,10 @@ if (rtc_include_tests) { group("metrics_unittests") { testonly = true - deps = [ ":stdout_metrics_exporter_test" ] + deps = [ + ":metrics_logger_and_exporter_test", + ":stdout_metrics_exporter_test", + ] } } @@ -39,7 +43,7 @@ rtc_library("metrics_exporter") { sources = [ "metrics_exporter.h" ] deps = [ ":metric", - "../../../api:array_view", + "../..:array_view", ] } @@ -52,12 +56,34 @@ rtc_library("stdout_metrics_exporter") { deps = [ ":metric", ":metrics_exporter", - "../../../api:array_view", + "../..:array_view", "../../../rtc_base:stringutils", ] absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] } +rtc_library("metrics_logger_and_exporter") { + visibility = [ "*" ] + sources = [ + "metrics_logger_and_exporter.cc", + "metrics_logger_and_exporter.h", + ] + deps = [ + ":metric", + ":metrics_exporter", + "../../../rtc_base:checks", + "../../../rtc_base:logging", + "../../../rtc_base/synchronization:mutex", + "../../../system_wrappers", + "../../numerics", + ] + + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] +} + if (rtc_include_tests) { rtc_library("stdout_metrics_exporter_test") { testonly = true @@ -65,8 +91,22 @@ if (rtc_include_tests) { deps = [ ":metric", ":stdout_metrics_exporter", - "../../../api/units:timestamp", "../../../test:test_support", + "../../units:timestamp", ] } + + rtc_library("metrics_logger_and_exporter_test") { + testonly = true + sources = [ "metrics_logger_and_exporter_test.cc" ] + deps = [ + ":metric", + ":metrics_exporter", + ":metrics_logger_and_exporter", + "../../../system_wrappers", + "../../../test:test_support", + "../../numerics", + ] + absl_deps = [ "//third_party/abseil-cpp/absl/types:optional" ] + } } diff --git a/api/test/metrics/DEPS b/api/test/metrics/DEPS new file mode 100644 index 0000000000..d0def604b7 --- /dev/null +++ b/api/test/metrics/DEPS @@ -0,0 +1,6 @@ +specific_include_rules = { + "metrics_logger_and_exporter\.h": [ + "+rtc_base/synchronization/mutex.h", + "+system_wrappers/include/clock.h", + ], +} diff --git a/api/test/metrics/metrics_logger_and_exporter.cc b/api/test/metrics/metrics_logger_and_exporter.cc new file mode 100644 index 0000000000..d76003b4a8 --- /dev/null +++ b/api/test/metrics/metrics_logger_and_exporter.cc @@ -0,0 +1,120 @@ +/* + * 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/metrics_logger_and_exporter.h" + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metric.h" +#include "rtc_base/checks.h" +#include "rtc_base/logging.h" +#include "rtc_base/synchronization/mutex.h" + +namespace webrtc { +namespace test { + +MetricsLoggerAndExporter::~MetricsLoggerAndExporter() { + bool export_result = Export(); + if (crash_on_export_failure_) { + RTC_CHECK(export_result); + } else { + RTC_LOG(LS_ERROR) << "One of exporters failed to export collected metrics"; + } +} + +void MetricsLoggerAndExporter::LogSingleValueMetric( + absl::string_view name, + absl::string_view test_case_name, + double value, + Unit unit, + ImprovementDirection improvement_direction, + std::map metadata) { + MutexLock lock(&mutex_); + metrics_.push_back(Metric{ + .name = std::string(name), + .unit = unit, + .improvement_direction = improvement_direction, + .test_case = std::string(test_case_name), + .metric_metadata = std::move(metadata), + .time_series = + Metric::TimeSeries{.samples = std::vector{Metric::TimeSeries::Sample{ + .timestamp = Now(), .value = value}}}, + .stats = Metric::Stats{ + .mean = value, .stddev = absl::nullopt, .min = value, .max = value}}); +} + +void MetricsLoggerAndExporter::LogMetric( + absl::string_view name, + absl::string_view test_case_name, + const SamplesStatsCounter& values, + Unit unit, + ImprovementDirection improvement_direction, + std::map metadata) { + MutexLock lock(&mutex_); + Metric::TimeSeries time_series; + for (const SamplesStatsCounter::StatsSample& sample : + values.GetTimedSamples()) { + time_series.samples.push_back( + Metric::TimeSeries::Sample{.timestamp = sample.time, + .value = sample.value, + .sample_metadata = sample.metadata}); + } + + metrics_.push_back( + Metric{.name = std::string(name), + .unit = unit, + .improvement_direction = improvement_direction, + .test_case = std::string(test_case_name), + .metric_metadata = std::move(metadata), + .time_series = std::move(time_series), + .stats = Metric::Stats{.mean = values.GetAverage(), + .stddev = values.GetStandardDeviation(), + .min = values.GetMin(), + .max = values.GetMax()}}); +} + +void MetricsLoggerAndExporter::LogMetric( + absl::string_view name, + absl::string_view test_case_name, + const Metric::Stats& metric_stats, + Unit unit, + ImprovementDirection improvement_direction, + std::map metadata) { + MutexLock lock(&mutex_); + metrics_.push_back(Metric{.name = std::string(name), + .unit = unit, + .improvement_direction = improvement_direction, + .test_case = std::string(test_case_name), + .metric_metadata = std::move(metadata), + .time_series = Metric::TimeSeries{.samples = {}}, + .stats = std::move(metric_stats)}); +} + +Timestamp MetricsLoggerAndExporter::Now() { + return clock_->CurrentTime(); +} + +bool MetricsLoggerAndExporter::Export() { + MutexLock lock(&mutex_); + bool success = true; + for (auto& exporter : exporters_) { + bool export_result = exporter->Export(metrics_); + success = success && export_result; + } + return success; +} + +} // namespace test +} // namespace webrtc diff --git a/api/test/metrics/metrics_logger_and_exporter.h b/api/test/metrics/metrics_logger_and_exporter.h new file mode 100644 index 0000000000..a761f7483c --- /dev/null +++ b/api/test/metrics/metrics_logger_and_exporter.h @@ -0,0 +1,94 @@ +/* + * 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_LOGGER_AND_EXPORTER_H_ +#define API_TEST_METRICS_METRICS_LOGGER_AND_EXPORTER_H_ + +#include +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metric.h" +#include "api/test/metrics/metrics_exporter.h" +#include "rtc_base/synchronization/mutex.h" +#include "system_wrappers/include/clock.h" + +namespace webrtc { +namespace test { + +// Combines metrics logging and exporting to provide simple API to automatically +// export metrics at the end of the scope. +class MetricsLoggerAndExporter { + public: + // `crash_on_export_failure` - makes MetricsLoggerAndExporter to crash if + // any of exporters failed to export data. + MetricsLoggerAndExporter( + webrtc::Clock* clock, + std::vector> exporters, + bool crash_on_export_failure = true) + : clock_(clock), + crash_on_export_failure_(crash_on_export_failure), + exporters_(std::move(exporters)) {} + ~MetricsLoggerAndExporter(); + + // Adds a metric with a single value. + // `metadata` - metric's level metadata to add. + void LogSingleValueMetric(absl::string_view name, + absl::string_view test_case_name, + double value, + Unit unit, + ImprovementDirection improvement_direction, + std::map metadata); + + // Adds metrics with a time series created based on the provided `values`. + // `metadata` - metric's level metadata to add. + void LogMetric(absl::string_view name, + absl::string_view test_case_name, + const SamplesStatsCounter& values, + Unit unit, + ImprovementDirection improvement_direction, + std::map metadata); + + // Adds metric with a time series with only stats object and without actual + // collected values. + // `metadata` - metric's level metadata to add. + void LogMetric(absl::string_view name, + absl::string_view test_case_name, + const Metric::Stats& metric_stats, + Unit unit, + ImprovementDirection improvement_direction, + std::map metadata); + + // Returns all metrics collected by this logger. + std::vector GetCollectedMetrics() const { + MutexLock lock(&mutex_); + return metrics_; + } + + private: + webrtc::Timestamp Now(); + bool Export(); + + webrtc::Clock* const clock_; + const bool crash_on_export_failure_; + + mutable Mutex mutex_; + std::vector metrics_ RTC_GUARDED_BY(mutex_); + std::vector> exporters_; +}; + +} // namespace test +} // namespace webrtc + +#endif // API_TEST_METRICS_METRICS_LOGGER_AND_EXPORTER_H_ diff --git a/api/test/metrics/metrics_logger_and_exporter_test.cc b/api/test/metrics/metrics_logger_and_exporter_test.cc new file mode 100644 index 0000000000..f984a125ac --- /dev/null +++ b/api/test/metrics/metrics_logger_and_exporter_test.cc @@ -0,0 +1,333 @@ +/* + * 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/metrics_logger_and_exporter.h" + +#include +#include +#include +#include + +#include "absl/types/optional.h" +#include "api/numerics/samples_stats_counter.h" +#include "api/test/metrics/metric.h" +#include "api/test/metrics/metrics_exporter.h" +#include "system_wrappers/include/clock.h" +#include "test/gmock.h" +#include "test/gtest.h" + +namespace webrtc { +namespace test { +namespace { + +using ::testing::Eq; +using ::testing::IsEmpty; + +std::map DefaultMetadata() { + return std::map{{"key", "value"}}; +} + +struct TestMetricsExporterFactory { + public: + std::unique_ptr CreateExporter() { + return std::make_unique(this, /*export_result=*/true); + } + + std::unique_ptr CreateFailureExporter() { + return std::make_unique(this, /*export_result=*/false); + } + + std::vector exported_metrics; + + private: + class TestMetricsExporter : public MetricsExporter { + public: + TestMetricsExporter(TestMetricsExporterFactory* factory, bool export_result) + : factory_(factory), export_result_(export_result) {} + ~TestMetricsExporter() override = default; + + bool Export(rtc::ArrayView metrics) override { + factory_->exported_metrics = + std::vector(metrics.begin(), metrics.end()); + return export_result_; + } + + TestMetricsExporterFactory* factory_; + bool export_result_; + }; +}; + +TEST(MetricsLoggerAndExporterTest, LogSingleValueMetricRecordsMetric) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + logger.LogSingleValueMetric( + "metric_name", "test_case_name", + /*value=*/10, Unit::kTimeMs, ImprovementDirection::kBiggerIsBetter, + std::map{{"key", "value"}}); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(1lu)); + const Metric& metric = metrics[0]; + EXPECT_THAT(metric.name, Eq("metric_name")); + EXPECT_THAT(metric.test_case, Eq("test_case_name")); + EXPECT_THAT(metric.unit, Eq(Unit::kTimeMs)); + EXPECT_THAT(metric.improvement_direction, + Eq(ImprovementDirection::kBiggerIsBetter)); + EXPECT_THAT(metric.metric_metadata, + Eq(std::map{{"key", "value"}})); + ASSERT_THAT(metric.time_series.samples.size(), Eq(1lu)); + EXPECT_THAT(metric.time_series.samples[0].value, Eq(10.0)); + EXPECT_THAT(metric.time_series.samples[0].sample_metadata, + Eq(std::map{})); + ASSERT_THAT(metric.stats.mean, absl::optional(10.0)); + ASSERT_THAT(metric.stats.stddev, absl::nullopt); + ASSERT_THAT(metric.stats.min, absl::optional(10.0)); + ASSERT_THAT(metric.stats.max, absl::optional(10.0)); +} + +TEST(MetricsLoggerAndExporterTest, + LogMetricWithSamplesStatsCounterRecordsMetric) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + + SamplesStatsCounter values; + values.AddSample(SamplesStatsCounter::StatsSample{ + .value = 10, + .time = Clock::GetRealTimeClock()->CurrentTime(), + .metadata = + std::map{{"point_key1", "value1"}}}); + values.AddSample(SamplesStatsCounter::StatsSample{ + .value = 20, + .time = Clock::GetRealTimeClock()->CurrentTime(), + .metadata = + std::map{{"point_key2", "value2"}}}); + logger.LogMetric("metric_name", "test_case_name", values, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, + std::map{{"key", "value"}}); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(1lu)); + const Metric& metric = metrics[0]; + EXPECT_THAT(metric.name, Eq("metric_name")); + EXPECT_THAT(metric.test_case, Eq("test_case_name")); + EXPECT_THAT(metric.unit, Eq(Unit::kTimeMs)); + EXPECT_THAT(metric.improvement_direction, + Eq(ImprovementDirection::kBiggerIsBetter)); + EXPECT_THAT(metric.metric_metadata, + Eq(std::map{{"key", "value"}})); + ASSERT_THAT(metric.time_series.samples.size(), Eq(2lu)); + EXPECT_THAT(metric.time_series.samples[0].value, Eq(10.0)); + EXPECT_THAT(metric.time_series.samples[0].sample_metadata, + Eq(std::map{{"point_key1", "value1"}})); + EXPECT_THAT(metric.time_series.samples[1].value, Eq(20.0)); + EXPECT_THAT(metric.time_series.samples[1].sample_metadata, + Eq(std::map{{"point_key2", "value2"}})); + ASSERT_THAT(metric.stats.mean, absl::optional(15.0)); + ASSERT_THAT(metric.stats.stddev, absl::optional(5.0)); + ASSERT_THAT(metric.stats.min, absl::optional(10.0)); + ASSERT_THAT(metric.stats.max, absl::optional(20.0)); +} + +TEST(MetricsLoggerAndExporterTest, LogMetricWithStatsRecordsMetric) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + Metric::Stats metric_stats{.mean = 15, .stddev = 5, .min = 10, .max = 20}; + logger.LogMetric("metric_name", "test_case_name", metric_stats, + Unit::kTimeMs, ImprovementDirection::kBiggerIsBetter, + std::map{{"key", "value"}}); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(1lu)); + const Metric& metric = metrics[0]; + EXPECT_THAT(metric.name, Eq("metric_name")); + EXPECT_THAT(metric.test_case, Eq("test_case_name")); + EXPECT_THAT(metric.unit, Eq(Unit::kTimeMs)); + EXPECT_THAT(metric.improvement_direction, + Eq(ImprovementDirection::kBiggerIsBetter)); + EXPECT_THAT(metric.metric_metadata, + Eq(std::map{{"key", "value"}})); + ASSERT_THAT(metric.time_series.samples.size(), Eq(0lu)); + ASSERT_THAT(metric.stats.mean, absl::optional(15.0)); + ASSERT_THAT(metric.stats.stddev, absl::optional(5.0)); + ASSERT_THAT(metric.stats.min, absl::optional(10.0)); + ASSERT_THAT(metric.stats.max, absl::optional(20.0)); +} + +TEST(MetricsLoggerAndExporterTest, LogSingleValueMetricRecordsMultipleMetrics) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + + logger.LogSingleValueMetric("metric_name1", "test_case_name1", + /*value=*/10, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + logger.LogSingleValueMetric("metric_name2", "test_case_name2", + /*value=*/10, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(2lu)); + EXPECT_THAT(metrics[0].name, Eq("metric_name1")); + EXPECT_THAT(metrics[0].test_case, Eq("test_case_name1")); + EXPECT_THAT(metrics[1].name, Eq("metric_name2")); + EXPECT_THAT(metrics[1].test_case, Eq("test_case_name2")); +} + +TEST(MetricsLoggerAndExporterTest, + LogMetricWithSamplesStatsCounterRecordsMultipleMetrics) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + SamplesStatsCounter values; + values.AddSample(SamplesStatsCounter::StatsSample{ + .value = 10, + .time = Clock::GetRealTimeClock()->CurrentTime(), + .metadata = DefaultMetadata()}); + values.AddSample(SamplesStatsCounter::StatsSample{ + .value = 20, + .time = Clock::GetRealTimeClock()->CurrentTime(), + .metadata = DefaultMetadata()}); + + logger.LogMetric("metric_name1", "test_case_name1", values, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, DefaultMetadata()); + logger.LogMetric("metric_name2", "test_case_name2", values, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, DefaultMetadata()); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(2lu)); + EXPECT_THAT(metrics[0].name, Eq("metric_name1")); + EXPECT_THAT(metrics[0].test_case, Eq("test_case_name1")); + EXPECT_THAT(metrics[1].name, Eq("metric_name2")); + EXPECT_THAT(metrics[1].test_case, Eq("test_case_name2")); +} + +TEST(MetricsLoggerAndExporterTest, LogMetricWithStatsRecordsMultipleMetrics) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + Metric::Stats metric_stats{.mean = 15, .stddev = 5, .min = 10, .max = 20}; + + logger.LogMetric("metric_name1", "test_case_name1", metric_stats, + Unit::kTimeMs, ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + logger.LogMetric("metric_name2", "test_case_name2", metric_stats, + Unit::kTimeMs, ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(2lu)); + EXPECT_THAT(metrics[0].name, Eq("metric_name1")); + EXPECT_THAT(metrics[0].test_case, Eq("test_case_name1")); + EXPECT_THAT(metrics[1].name, Eq("metric_name2")); + EXPECT_THAT(metrics[1].test_case, Eq("test_case_name2")); +} + +TEST(MetricsLoggerAndExporterTest, + LogMetricThroughtAllMethodsAccumulateAllMetrics) { + TestMetricsExporterFactory exporter_factory; + { + std::vector> exporters; + exporters.push_back(exporter_factory.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters)); + SamplesStatsCounter values; + values.AddSample(SamplesStatsCounter::StatsSample{ + .value = 10, + .time = Clock::GetRealTimeClock()->CurrentTime(), + .metadata = DefaultMetadata()}); + values.AddSample(SamplesStatsCounter::StatsSample{ + .value = 20, + .time = Clock::GetRealTimeClock()->CurrentTime(), + .metadata = DefaultMetadata()}); + Metric::Stats metric_stats{.mean = 15, .stddev = 5, .min = 10, .max = 20}; + + logger.LogSingleValueMetric("metric_name1", "test_case_name1", + /*value=*/10, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + logger.LogMetric("metric_name2", "test_case_name2", values, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, DefaultMetadata()); + logger.LogMetric("metric_name3", "test_case_name3", metric_stats, + Unit::kTimeMs, ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + } + + std::vector metrics = exporter_factory.exported_metrics; + ASSERT_THAT(metrics.size(), Eq(3lu)); + EXPECT_THAT(metrics[0].name, Eq("metric_name1")); + EXPECT_THAT(metrics[0].test_case, Eq("test_case_name1")); + EXPECT_THAT(metrics[1].name, Eq("metric_name2")); + EXPECT_THAT(metrics[1].test_case, Eq("test_case_name2")); + EXPECT_THAT(metrics[2].name, Eq("metric_name3")); + EXPECT_THAT(metrics[2].test_case, Eq("test_case_name3")); +} + +TEST(MetricsLoggerAndExporterTest, + OneFailedExporterDoesNotPreventExportToOthers) { + TestMetricsExporterFactory exporter_factory1; + TestMetricsExporterFactory exporter_factory2; + TestMetricsExporterFactory exporter_factory3; + { + std::vector> exporters; + exporters.push_back(exporter_factory1.CreateExporter()); + exporters.push_back(exporter_factory2.CreateFailureExporter()); + exporters.push_back(exporter_factory3.CreateExporter()); + MetricsLoggerAndExporter logger(Clock::GetRealTimeClock(), + std::move(exporters), + /*crash_on_export_failure=*/false); + + logger.LogSingleValueMetric("metric_name", "test_case_name", + /*value=*/10, Unit::kTimeMs, + ImprovementDirection::kBiggerIsBetter, + DefaultMetadata()); + } + + std::vector metrics1 = exporter_factory1.exported_metrics; + std::vector metrics2 = exporter_factory2.exported_metrics; + std::vector metrics3 = exporter_factory3.exported_metrics; + ASSERT_THAT(metrics1.size(), Eq(1lu)); + EXPECT_THAT(metrics1[0].name, Eq("metric_name")); + ASSERT_THAT(metrics2.size(), Eq(1lu)); + EXPECT_THAT(metrics2[0].name, Eq("metric_name")); + ASSERT_THAT(metrics3.size(), Eq(1lu)); + EXPECT_THAT(metrics3[0].name, Eq("metric_name")); +} + +} // namespace +} // namespace test +} // namespace webrtc