From 538fa813283b96ff8e0b49770b5620d7bf71cdb5 Mon Sep 17 00:00:00 2001 From: Artem Titov Date: Fri, 18 Nov 2022 22:34:46 +0100 Subject: [PATCH] Add collection of EmulatedNetworkNode stats to stats collector Bug: b/240540204 Change-Id: I9c2c2c35d0c3b6a99205e24d8b367fa7dab5d917 Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/283760 Commit-Queue: Artem Titov Reviewed-by: Mirko Bonadei Cr-Commit-Position: refs/heads/main@{#38694} --- test/network/BUILD.gn | 1 + test/network/network_emulation.h | 1 + test/pc/e2e/BUILD.gn | 29 ++ ..._based_network_quality_metrics_reporter.cc | 250 +++++++++++++++--- ...s_based_network_quality_metrics_reporter.h | 25 +- ...d_network_quality_metrics_reporter_test.cc | 150 +++++++++++ 6 files changed, 410 insertions(+), 46 deletions(-) create mode 100644 test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc diff --git a/test/network/BUILD.gn b/test/network/BUILD.gn index 71cf2d79f3..379f6048cd 100644 --- a/test/network/BUILD.gn +++ b/test/network/BUILD.gn @@ -76,6 +76,7 @@ rtc_library("emulated_network") { "../../rtc_base:threading", "../../rtc_base/memory:always_valid_pointer", "../../rtc_base/synchronization:mutex", + "../../rtc_base/system:no_unique_address", "../../rtc_base/task_utils:repeating_task", "../../system_wrappers", "../../test:scoped_key_value_config", diff --git a/test/network/network_emulation.h b/test/network/network_emulation.h index 7a08495b7a..dffabafa7c 100644 --- a/test/network/network_emulation.h +++ b/test/network/network_emulation.h @@ -33,6 +33,7 @@ #include "rtc_base/network_constants.h" #include "rtc_base/socket_address.h" #include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" #include "rtc_base/task_queue_for_test.h" #include "rtc_base/task_utils/repeating_task.h" #include "rtc_base/thread_annotations.h" diff --git a/test/pc/e2e/BUILD.gn b/test/pc/e2e/BUILD.gn index e73c0aa0d7..2b9a69afc5 100644 --- a/test/pc/e2e/BUILD.gn +++ b/test/pc/e2e/BUILD.gn @@ -34,6 +34,7 @@ if (!build_with_chromium) { ":peer_connection_e2e_smoke_test", ":peer_connection_quality_test_metric_names_test", ":peer_connection_quality_test_test", + ":stats_based_network_quality_metrics_reporter_test", ":stats_poller_test", ] } @@ -333,6 +334,32 @@ if (!build_with_chromium) { ] } + rtc_library("stats_based_network_quality_metrics_reporter_test") { + testonly = true + sources = [ "stats_based_network_quality_metrics_reporter_test.cc" ] + deps = [ + ":metric_metadata_keys", + ":peerconnection_quality_test", + ":stats_based_network_quality_metrics_reporter", + "../..:test_support", + "../../../api:array_view", + "../../../api:create_network_emulation_manager", + "../../../api:create_peer_connection_quality_test_frame_generator", + "../../../api:network_emulation_manager_api", + "../../../api:peer_connection_quality_test_fixture_api", + "../../../api/test/metrics:metrics_logger", + "../../../api/test/metrics:stdout_metrics_exporter", + "../../../api/test/pclf:media_configuration", + "../../../api/test/pclf:media_quality_test_params", + "../../../api/test/pclf:peer_configurer", + "../../../api/units:time_delta", + ] + absl_deps = [ + "//third_party/abseil-cpp/absl/strings", + "//third_party/abseil-cpp/absl/types:optional", + ] + } + rtc_library("peer_connection_quality_test_test") { testonly = true sources = [ "peer_connection_quality_test_test.cc" ] @@ -470,6 +497,7 @@ if (!build_with_chromium) { "../../../api:peer_connection_quality_test_fixture_api", "../../../api:rtc_stats_api", "../../../api:scoped_refptr", + "../../../api:sequence_checker", "../../../api/numerics", "../../../api/test/metrics:metric", "../../../api/test/metrics:metrics_logger", @@ -483,6 +511,7 @@ if (!build_with_chromium) { "../../../rtc_base:rtc_event", "../../../rtc_base:stringutils", "../../../rtc_base/synchronization:mutex", + "../../../rtc_base/system:no_unique_address", "../../../system_wrappers:field_trial", ] absl_deps = [ "//third_party/abseil-cpp/absl/strings" ] diff --git a/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc b/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc index d4c2f93b6f..155c81a5e9 100644 --- a/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc +++ b/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc @@ -22,6 +22,7 @@ #include "absl/strings/string_view.h" #include "api/array_view.h" #include "api/scoped_refptr.h" +#include "api/sequence_checker.h" #include "api/stats/rtc_stats.h" #include "api/stats/rtcstats_objects.h" #include "api/test/metrics/metric.h" @@ -34,6 +35,7 @@ #include "rtc_base/ip_address.h" #include "rtc_base/strings/string_builder.h" #include "rtc_base/synchronization/mutex.h" +#include "rtc_base/system/no_unique_address.h" #include "system_wrappers/include/field_trial.h" #include "test/pc/e2e/metric_metadata_keys.h" @@ -44,6 +46,9 @@ namespace { using ::webrtc::test::ImprovementDirection; using ::webrtc::test::Unit; +using NetworkLayerStats = + StatsBasedNetworkQualityMetricsReporter::NetworkLayerStats; + constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1); // Field trial which controls whether to report standard-compliant bytes @@ -78,6 +83,83 @@ std::map PopulateIpToPeer( return out; } +// Accumulates emulated network stats being executed on the network thread. +// When all stats are collected stores it in thread safe variable. +class EmulatedNetworkStatsAccumulator { + public: + // `expected_stats_count` - the number of calls to + // AddEndpointStats/AddUplinkStats/AddDownlinkStats the accumulator is going + // to wait. If called more than expected, the program will crash. + explicit EmulatedNetworkStatsAccumulator(size_t expected_stats_count) + : not_collected_stats_count_(expected_stats_count) { + RTC_DCHECK_GE(not_collected_stats_count_, 0); + if (not_collected_stats_count_ == 0) { + all_stats_collected_.Set(); + } + sequence_checker_.Detach(); + } + + // Has to be executed on network thread. + void AddEndpointStats(std::string peer_name, EmulatedNetworkStats stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + n_stats_[peer_name].endpoints_stats = std::move(stats); + DecrementNotCollectedStatsCount(); + } + + // Has to be executed on network thread. + void AddUplinkStats(std::string peer_name, EmulatedNetworkNodeStats stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + n_stats_[peer_name].uplink_stats = std::move(stats); + DecrementNotCollectedStatsCount(); + } + + // Has to be executed on network thread. + void AddDownlinkStats(std::string peer_name, EmulatedNetworkNodeStats stats) { + RTC_DCHECK_RUN_ON(&sequence_checker_); + n_stats_[peer_name].downlink_stats = std::move(stats); + DecrementNotCollectedStatsCount(); + } + + // Can be executed on any thread. + // Returns true if count down was completed and false if timeout elapsed + // before. + bool Wait(TimeDelta timeout) { return all_stats_collected_.Wait(timeout); } + + // Can be called once. Returns all collected stats by moving underlying + // object. + std::map ReleaseStats() { + RTC_DCHECK(!stats_released_); + stats_released_ = true; + MutexLock lock(&mutex_); + return std::move(stats_); + } + + private: + void DecrementNotCollectedStatsCount() { + RTC_DCHECK_RUN_ON(&sequence_checker_); + RTC_CHECK_GT(not_collected_stats_count_, 0) + << "All stats are already collected"; + not_collected_stats_count_--; + if (not_collected_stats_count_ == 0) { + MutexLock lock(&mutex_); + stats_ = std::move(n_stats_); + all_stats_collected_.Set(); + } + } + + RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_; + size_t not_collected_stats_count_ RTC_GUARDED_BY(sequence_checker_); + // Collected on the network thread. Moved into `stats_` after all stats are + // collected. + std::map n_stats_ + RTC_GUARDED_BY(sequence_checker_); + + rtc::Event all_stats_collected_; + Mutex mutex_; + std::map stats_ RTC_GUARDED_BY(mutex_); + bool stats_released_ = false; +}; + } // namespace StatsBasedNetworkQualityMetricsReporter:: @@ -113,11 +195,15 @@ void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: AddPeer(absl::string_view peer_name, - std::vector endpoints) { + std::vector endpoints, + std::vector uplink, + std::vector downlink) { MutexLock lock(&mutex_); // When new peer is added not in the constructor, don't check if it has empty // stats, because their endpoint could be used for traffic before. peer_endpoints_.emplace(peer_name, std::move(endpoints)); + peer_uplinks_.emplace(peer_name, std::move(uplink)); + peer_downlinks_.emplace(peer_name, std::move(downlink)); for (const EmulatedEndpoint* const endpoint : endpoints) { RTC_CHECK(ip_to_peer_.find(endpoint->GetPeerLocalAddress()) == ip_to_peer_.end()) @@ -126,19 +212,43 @@ void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: } } -std::map +std::map StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: GetStats() { MutexLock lock(&mutex_); - std::map peer_to_stats; + EmulatedNetworkStatsAccumulator stats_accumulator( + peer_endpoints_.size() + peer_uplinks_.size() + peer_downlinks_.size()); + for (const auto& entry : peer_endpoints_) { + network_emulation_->GetStats( + entry.second, [&stats_accumulator, + peer = entry.first](EmulatedNetworkStats s) mutable { + stats_accumulator.AddEndpointStats(std::move(peer), std::move(s)); + }); + } + for (const auto& entry : peer_uplinks_) { + network_emulation_->GetStats( + entry.second, [&stats_accumulator, + peer = entry.first](EmulatedNetworkNodeStats s) mutable { + stats_accumulator.AddUplinkStats(std::move(peer), std::move(s)); + }); + } + for (const auto& entry : peer_downlinks_) { + network_emulation_->GetStats( + entry.second, [&stats_accumulator, + peer = entry.first](EmulatedNetworkNodeStats s) mutable { + stats_accumulator.AddDownlinkStats(std::move(peer), std::move(s)); + }); + } + bool stats_collected = stats_accumulator.Wait(kStatsWaitTimeout); + RTC_CHECK(stats_collected); + std::map peer_to_stats = + stats_accumulator.ReleaseStats(); std::map> sender_to_receivers; for (const auto& entry : peer_endpoints_) { - NetworkLayerStats stats; - stats.stats = PopulateStats(entry.second, network_emulation_); const std::string& peer_name = entry.first; + const NetworkLayerStats& stats = peer_to_stats[peer_name]; for (const auto& income_stats_entry : - stats.stats.incoming_stats_per_source) { + stats.endpoints_stats.incoming_stats_per_source) { const rtc::IPAddress& source_ip = income_stats_entry.first; auto it = ip_to_peer_.find(source_ip); if (it == ip_to_peer_.end()) { @@ -147,7 +257,6 @@ StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: } sender_to_receivers[it->second].push_back(peer_name); } - peer_to_stats.emplace(peer_name, std::move(stats)); } for (auto& entry : peer_to_stats) { const std::vector& receivers = @@ -161,7 +270,17 @@ StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: void StatsBasedNetworkQualityMetricsReporter::AddPeer( absl::string_view peer_name, std::vector endpoints) { - collector_.AddPeer(peer_name, std::move(endpoints)); + collector_.AddPeer(peer_name, std::move(endpoints), /*uplink=*/{}, + /*downlink=*/{}); +} + +void StatsBasedNetworkQualityMetricsReporter::AddPeer( + absl::string_view peer_name, + std::vector endpoints, + std::vector uplink, + std::vector downlink) { + collector_.AddPeer(peer_name, std::move(endpoints), std::move(uplink), + std::move(downlink)); } void StatsBasedNetworkQualityMetricsReporter::Start( @@ -255,12 +374,12 @@ void StatsBasedNetworkQualityMetricsReporter::ReportStats( {MetricMetadataKey::kPeerMetadataKey, pc_label}}; metrics_logger_->LogSingleValueMetric( "bytes_discarded_no_receiver", GetTestCaseName(pc_label), - network_layer_stats.stats.overall_incoming_stats + network_layer_stats.endpoints_stats.overall_incoming_stats .bytes_discarded_no_receiver.bytes(), Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); metrics_logger_->LogSingleValueMetric( "packets_discarded_no_receiver", GetTestCaseName(pc_label), - network_layer_stats.stats.overall_incoming_stats + network_layer_stats.endpoints_stats.overall_incoming_stats .packets_discarded_no_receiver, Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); @@ -312,55 +431,60 @@ void StatsBasedNetworkQualityMetricsReporter::LogNetworkLayerStats( const std::string& peer_name, const NetworkLayerStats& stats) const { DataRate average_send_rate = - stats.stats.overall_outgoing_stats.packets_sent >= 2 - ? stats.stats.overall_outgoing_stats.AverageSendRate() + stats.endpoints_stats.overall_outgoing_stats.packets_sent >= 2 + ? stats.endpoints_stats.overall_outgoing_stats.AverageSendRate() : DataRate::Zero(); DataRate average_receive_rate = - stats.stats.overall_incoming_stats.packets_received >= 2 - ? stats.stats.overall_incoming_stats.AverageReceiveRate() + stats.endpoints_stats.overall_incoming_stats.packets_received >= 2 + ? stats.endpoints_stats.overall_incoming_stats.AverageReceiveRate() : DataRate::Zero(); std::map metric_metadata{ {MetricMetadataKey::kPeerMetadataKey, peer_name}}; rtc::StringBuilder log; log << "Raw network layer statistic for [" << peer_name << "]:\n" << "Local IPs:\n"; - for (size_t i = 0; i < stats.stats.local_addresses.size(); ++i) { - log << " " << stats.stats.local_addresses[i].ToString() << "\n"; + for (size_t i = 0; i < stats.endpoints_stats.local_addresses.size(); ++i) { + log << " " << stats.endpoints_stats.local_addresses[i].ToString() << "\n"; } - if (!stats.stats.overall_outgoing_stats.sent_packets_size.IsEmpty()) { - metrics_logger_->LogMetric( - "sent_packets_size", GetTestCaseName(peer_name), - stats.stats.overall_outgoing_stats.sent_packets_size, Unit::kBytes, - ImprovementDirection::kNeitherIsBetter, metric_metadata); - } - if (!stats.stats.overall_incoming_stats.received_packets_size.IsEmpty()) { - metrics_logger_->LogMetric( - "received_packets_size", GetTestCaseName(peer_name), - stats.stats.overall_incoming_stats.received_packets_size, Unit::kBytes, - ImprovementDirection::kNeitherIsBetter, metric_metadata); - } - if (!stats.stats.overall_incoming_stats.packets_discarded_no_receiver_size + if (!stats.endpoints_stats.overall_outgoing_stats.sent_packets_size .IsEmpty()) { metrics_logger_->LogMetric( - "packets_discarded_no_receiver_size", GetTestCaseName(peer_name), - stats.stats.overall_incoming_stats.packets_discarded_no_receiver_size, + "sent_packets_size", GetTestCaseName(peer_name), + stats.endpoints_stats.overall_outgoing_stats.sent_packets_size, Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); } - if (!stats.stats.sent_packets_queue_wait_time_us.IsEmpty()) { + if (!stats.endpoints_stats.overall_incoming_stats.received_packets_size + .IsEmpty()) { + metrics_logger_->LogMetric( + "received_packets_size", GetTestCaseName(peer_name), + stats.endpoints_stats.overall_incoming_stats.received_packets_size, + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.endpoints_stats.overall_incoming_stats + .packets_discarded_no_receiver_size.IsEmpty()) { + metrics_logger_->LogMetric( + "packets_discarded_no_receiver_size", GetTestCaseName(peer_name), + stats.endpoints_stats.overall_incoming_stats + .packets_discarded_no_receiver_size, + Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.endpoints_stats.sent_packets_queue_wait_time_us.IsEmpty()) { metrics_logger_->LogMetric( "sent_packets_queue_wait_time_us", GetTestCaseName(peer_name), - stats.stats.sent_packets_queue_wait_time_us, Unit::kUnitless, + stats.endpoints_stats.sent_packets_queue_wait_time_us, Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); } log << "Send statistic:\n" - << " packets: " << stats.stats.overall_outgoing_stats.packets_sent - << " bytes: " << stats.stats.overall_outgoing_stats.bytes_sent.bytes() + << " packets: " + << stats.endpoints_stats.overall_outgoing_stats.packets_sent << " bytes: " + << stats.endpoints_stats.overall_outgoing_stats.bytes_sent.bytes() << " avg_rate (bytes/sec): " << average_send_rate.bytes_per_sec() << " avg_rate (bps): " << average_send_rate.bps() << "\n" << "Send statistic per destination:\n"; - for (const auto& entry : stats.stats.outgoing_stats_per_destination) { + for (const auto& entry : + stats.endpoints_stats.outgoing_stats_per_destination) { DataRate source_average_send_rate = entry.second.packets_sent >= 2 ? entry.second.AverageSendRate() : DataRate::Zero(); @@ -378,14 +502,38 @@ void StatsBasedNetworkQualityMetricsReporter::LogNetworkLayerStats( } } + if (!stats.uplink_stats.packet_transport_time.IsEmpty()) { + log << "[Debug stats] packet_transport_time=(" + << stats.uplink_stats.packet_transport_time.GetAverage() << ", " + << stats.uplink_stats.packet_transport_time.GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "uplink_packet_transport_time", GetTestCaseName(peer_name), + stats.uplink_stats.packet_transport_time, Unit::kMilliseconds, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.uplink_stats.size_to_packet_transport_time.IsEmpty()) { + log << "[Debug stats] size_to_packet_transport_time=(" + << stats.uplink_stats.size_to_packet_transport_time.GetAverage() << ", " + << stats.uplink_stats.size_to_packet_transport_time + .GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "uplink_size_to_packet_transport_time", GetTestCaseName(peer_name), + stats.uplink_stats.size_to_packet_transport_time, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + log << "Receive statistic:\n" - << " packets: " << stats.stats.overall_incoming_stats.packets_received - << " bytes: " << stats.stats.overall_incoming_stats.bytes_received.bytes() + << " packets: " + << stats.endpoints_stats.overall_incoming_stats.packets_received + << " bytes: " + << stats.endpoints_stats.overall_incoming_stats.bytes_received.bytes() << " avg_rate (bytes/sec): " << average_receive_rate.bytes_per_sec() << " avg_rate (bps): " << average_receive_rate.bps() << "\n" << "Receive statistic per source:\n"; - for (const auto& entry : stats.stats.incoming_stats_per_source) { + for (const auto& entry : stats.endpoints_stats.incoming_stats_per_source) { DataRate source_average_receive_rate = entry.second.packets_received >= 2 ? entry.second.AverageReceiveRate() : DataRate::Zero(); @@ -410,6 +558,28 @@ void StatsBasedNetworkQualityMetricsReporter::LogNetworkLayerStats( ImprovementDirection::kNeitherIsBetter, metric_metadata); } } + if (!stats.downlink_stats.packet_transport_time.IsEmpty()) { + log << "[Debug stats] packet_transport_time=(" + << stats.downlink_stats.packet_transport_time.GetAverage() << ", " + << stats.downlink_stats.packet_transport_time.GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "downlink_packet_transport_time", GetTestCaseName(peer_name), + stats.downlink_stats.packet_transport_time, Unit::kMilliseconds, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } + if (!stats.downlink_stats.size_to_packet_transport_time.IsEmpty()) { + log << "[Debug stats] size_to_packet_transport_time=(" + << stats.downlink_stats.size_to_packet_transport_time.GetAverage() + << ", " + << stats.downlink_stats.size_to_packet_transport_time + .GetStandardDeviation() + << ")\n"; + metrics_logger_->LogMetric( + "downlink_size_to_packet_transport_time", GetTestCaseName(peer_name), + stats.downlink_stats.size_to_packet_transport_time, Unit::kUnitless, + ImprovementDirection::kNeitherIsBetter, metric_metadata); + } RTC_LOG(LS_INFO) << log.str(); } diff --git a/test/pc/e2e/stats_based_network_quality_metrics_reporter.h b/test/pc/e2e/stats_based_network_quality_metrics_reporter.h index 8516c40f5c..60daf40c8c 100644 --- a/test/pc/e2e/stats_based_network_quality_metrics_reporter.h +++ b/test/pc/e2e/stats_based_network_quality_metrics_reporter.h @@ -37,6 +37,14 @@ namespace webrtc_pc_e2e { class StatsBasedNetworkQualityMetricsReporter : public PeerConnectionE2EQualityTestFixture::QualityMetricsReporter { public: + // Emulated network layer stats for single peer. + struct NetworkLayerStats { + EmulatedNetworkStats endpoints_stats; + EmulatedNetworkNodeStats uplink_stats; + EmulatedNetworkNodeStats downlink_stats; + std::set receivers; + }; + // `networks` map peer name to network to report network layer stability stats // and to log network layer metrics. StatsBasedNetworkQualityMetricsReporter( @@ -47,6 +55,10 @@ class StatsBasedNetworkQualityMetricsReporter void AddPeer(absl::string_view peer_name, std::vector endpoints); + void AddPeer(absl::string_view peer_name, + std::vector endpoints, + std::vector uplink, + std::vector downlink); // Network stats must be empty when this method will be invoked. void Start(absl::string_view test_case_name, @@ -71,11 +83,6 @@ class StatsBasedNetworkQualityMetricsReporter int64_t packets_sent = 0; }; - struct NetworkLayerStats { - EmulatedNetworkStats stats; - std::set receivers; - }; - class NetworkLayerStatsCollector { public: NetworkLayerStatsCollector( @@ -85,7 +92,9 @@ class StatsBasedNetworkQualityMetricsReporter void Start(); void AddPeer(absl::string_view peer_name, - std::vector endpoints); + std::vector endpoints, + std::vector uplink, + std::vector downlink); std::map GetStats(); @@ -93,6 +102,10 @@ class StatsBasedNetworkQualityMetricsReporter Mutex mutex_; std::map> peer_endpoints_ RTC_GUARDED_BY(mutex_); + std::map> peer_uplinks_ + RTC_GUARDED_BY(mutex_); + std::map> peer_downlinks_ + RTC_GUARDED_BY(mutex_); std::map ip_to_peer_ RTC_GUARDED_BY(mutex_); NetworkEmulationManager* const network_emulation_; }; diff --git a/test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc b/test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc new file mode 100644 index 0000000000..be55149482 --- /dev/null +++ b/test/pc/e2e/stats_based_network_quality_metrics_reporter_test.cc @@ -0,0 +1,150 @@ +/* + * 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 "test/pc/e2e/stats_based_network_quality_metrics_reporter.h" + +#include +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "api/array_view.h" +#include "api/test/create_network_emulation_manager.h" +#include "api/test/create_peer_connection_quality_test_frame_generator.h" +#include "api/test/metrics/metrics_logger.h" +#include "api/test/metrics/stdout_metrics_exporter.h" +#include "api/test/network_emulation_manager.h" +#include "api/test/pclf/media_configuration.h" +#include "api/test/pclf/media_quality_test_params.h" +#include "api/test/pclf/peer_configurer.h" +#include "api/test/peerconnection_quality_test_fixture.h" +#include "api/units/time_delta.h" +#include "test/gmock.h" +#include "test/gtest.h" +#include "test/pc/e2e/metric_metadata_keys.h" +#include "test/pc/e2e/peer_connection_quality_test.h" + +namespace webrtc { +namespace webrtc_pc_e2e { +namespace { + +using ::testing::UnorderedElementsAre; + +using ::webrtc::test::DefaultMetricsLogger; +using ::webrtc::test::ImprovementDirection; +using ::webrtc::test::Metric; +using ::webrtc::test::Unit; +using ::webrtc::webrtc_pc_e2e::PeerConfigurer; + +// Adds a peer with some audio and video (the client should not care about +// details about audio and video configs). +void AddDefaultAudioVideoPeer( + absl::string_view peer_name, + absl::string_view audio_stream_label, + absl::string_view video_stream_label, + const PeerNetworkDependencies& network_dependencies, + PeerConnectionE2EQualityTestFixture& fixture) { + AudioConfig audio{std::string(audio_stream_label)}; + audio.sync_group = std::string(peer_name); + VideoConfig video(std::string(video_stream_label), 320, 180, 15); + video.sync_group = std::string(peer_name); + auto peer = std::make_unique(network_dependencies); + peer->SetName(peer_name); + peer->SetAudioConfig(std::move(audio)); + peer->AddVideoConfig(std::move(video)); + peer->SetVideoCodecs({VideoCodecConfig(cricket::kVp8CodecName)}); + fixture.AddPeer(std::move(peer)); +} + +absl::optional FindMeetricByName(absl::string_view name, + rtc::ArrayView metrics) { + for (const Metric& metric : metrics) { + if (metric.name == name) { + return metric; + } + } + return absl::nullopt; +} + +TEST(StatsBasedNetworkQualityMetricsReporterTest, DebugStatsAreCollected) { + std::unique_ptr network_emulation = + CreateNetworkEmulationManager(TimeMode::kSimulated, + EmulatedNetworkStatsGatheringMode::kDebug); + DefaultMetricsLogger metrics_logger( + network_emulation->time_controller()->GetClock()); + PeerConnectionE2EQualityTest fixture( + "test_case", *network_emulation->time_controller(), + /*audio_quality_analyzer=*/nullptr, /*video_quality_analyzer=*/nullptr, + &metrics_logger); + + EmulatedEndpoint* alice_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + EmulatedEndpoint* bob_endpoint = + network_emulation->CreateEndpoint(EmulatedEndpointConfig()); + + EmulatedNetworkNode* alice_link = network_emulation->CreateEmulatedNode( + BuiltInNetworkBehaviorConfig{.link_capacity_kbps = 500}); + network_emulation->CreateRoute(alice_endpoint, {alice_link}, bob_endpoint); + EmulatedNetworkNode* bob_link = network_emulation->CreateEmulatedNode( + BuiltInNetworkBehaviorConfig{.link_capacity_kbps = 500}); + network_emulation->CreateRoute(bob_endpoint, {bob_link}, alice_endpoint); + + EmulatedNetworkManagerInterface* alice_network = + network_emulation->CreateEmulatedNetworkManagerInterface( + {alice_endpoint}); + EmulatedNetworkManagerInterface* bob_network = + network_emulation->CreateEmulatedNetworkManagerInterface({bob_endpoint}); + + AddDefaultAudioVideoPeer("alice", "alice_audio", "alice_video", + alice_network->network_dependencies(), fixture); + AddDefaultAudioVideoPeer("bob", "bob_audio", "bob_video", + bob_network->network_dependencies(), fixture); + + auto network_stats_reporter = + std::make_unique( + /*peer_endpoints=*/std::map>{}, + network_emulation.get(), &metrics_logger); + network_stats_reporter->AddPeer("alice", alice_network->endpoints(), + /*uplink=*/{alice_link}, + /*downlink=*/{bob_link}); + network_stats_reporter->AddPeer("bob", bob_network->endpoints(), + /*uplink=*/{bob_link}, + /*downlink=*/{alice_link}); + fixture.AddQualityMetricsReporter(std::move(network_stats_reporter)); + + fixture.Run(RunParams(TimeDelta::Seconds(4))); + + std::vector metrics = metrics_logger.GetCollectedMetrics(); + absl::optional uplink_packet_transport_time = + FindMeetricByName("uplink_packet_transport_time", metrics); + ASSERT_TRUE(uplink_packet_transport_time.has_value()); + ASSERT_FALSE(uplink_packet_transport_time->time_series.samples.empty()); + absl::optional uplink_size_to_packet_transport_time = + FindMeetricByName("uplink_size_to_packet_transport_time", metrics); + ASSERT_TRUE(uplink_size_to_packet_transport_time.has_value()); + ASSERT_FALSE( + uplink_size_to_packet_transport_time->time_series.samples.empty()); + absl::optional downlink_packet_transport_time = + FindMeetricByName("downlink_packet_transport_time", metrics); + ASSERT_TRUE(downlink_packet_transport_time.has_value()); + ASSERT_FALSE(downlink_packet_transport_time->time_series.samples.empty()); + absl::optional downlink_size_to_packet_transport_time = + FindMeetricByName("downlink_size_to_packet_transport_time", metrics); + ASSERT_TRUE(downlink_size_to_packet_transport_time.has_value()); + ASSERT_FALSE( + downlink_size_to_packet_transport_time->time_series.samples.empty()); +} + +} // namespace +} // namespace webrtc_pc_e2e +} // namespace webrtc