| /* |
| * 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/pc/e2e/stats_based_network_quality_metrics_reporter.h" |
| |
| #include <cstdint> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <string> |
| #include <type_traits> |
| #include <utility> |
| #include <vector> |
| |
| #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" |
| #include "api/test/network_emulation/network_emulation_interfaces.h" |
| #include "api/test/network_emulation_manager.h" |
| #include "api/units/data_rate.h" |
| #include "api/units/timestamp.h" |
| #include "rtc_base/checks.h" |
| #include "rtc_base/event.h" |
| #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" |
| |
| namespace webrtc { |
| namespace webrtc_pc_e2e { |
| namespace { |
| |
| using ::webrtc::test::ImprovementDirection; |
| using ::webrtc::test::Unit; |
| |
| using NetworkLayerStats = |
| StatsBasedNetworkQualityMetricsReporter::NetworkLayerStats; |
| |
| constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1); |
| |
| EmulatedNetworkStats PopulateStats(std::vector<EmulatedEndpoint*> endpoints, |
| NetworkEmulationManager* network_emulation) { |
| rtc::Event stats_loaded; |
| EmulatedNetworkStats stats; |
| network_emulation->GetStats(endpoints, [&](EmulatedNetworkStats s) { |
| stats = std::move(s); |
| stats_loaded.Set(); |
| }); |
| bool stats_received = stats_loaded.Wait(kStatsWaitTimeout); |
| RTC_CHECK(stats_received); |
| return stats; |
| } |
| |
| std::map<rtc::IPAddress, std::string> PopulateIpToPeer( |
| const std::map<std::string, std::vector<EmulatedEndpoint*>>& |
| peer_endpoints) { |
| std::map<rtc::IPAddress, std::string> out; |
| for (const auto& entry : peer_endpoints) { |
| for (const EmulatedEndpoint* const endpoint : entry.second) { |
| RTC_CHECK(out.find(endpoint->GetPeerLocalAddress()) == out.end()) |
| << "Two peers can't share the same endpoint"; |
| out.emplace(endpoint->GetPeerLocalAddress(), entry.first); |
| } |
| } |
| 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<std::string, NetworkLayerStats> 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<std::string, NetworkLayerStats> n_stats_ |
| RTC_GUARDED_BY(sequence_checker_); |
| |
| rtc::Event all_stats_collected_; |
| Mutex mutex_; |
| std::map<std::string, NetworkLayerStats> stats_ RTC_GUARDED_BY(mutex_); |
| bool stats_released_ = false; |
| }; |
| |
| } // namespace |
| |
| StatsBasedNetworkQualityMetricsReporter:: |
| StatsBasedNetworkQualityMetricsReporter( |
| std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints, |
| NetworkEmulationManager* network_emulation, |
| test::MetricsLogger* metrics_logger) |
| : collector_(std::move(peer_endpoints), network_emulation), |
| clock_(network_emulation->time_controller()->GetClock()), |
| metrics_logger_(metrics_logger) { |
| RTC_CHECK(metrics_logger_); |
| } |
| |
| StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: |
| NetworkLayerStatsCollector( |
| std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints, |
| NetworkEmulationManager* network_emulation) |
| : peer_endpoints_(std::move(peer_endpoints)), |
| ip_to_peer_(PopulateIpToPeer(peer_endpoints_)), |
| network_emulation_(network_emulation) {} |
| |
| void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: |
| Start() { |
| MutexLock lock(&mutex_); |
| // Check that network stats are clean before test execution. |
| for (const auto& entry : peer_endpoints_) { |
| EmulatedNetworkStats stats = |
| PopulateStats(entry.second, network_emulation_); |
| RTC_CHECK_EQ(stats.overall_outgoing_stats.packets_sent, 0); |
| RTC_CHECK_EQ(stats.overall_incoming_stats.packets_received, 0); |
| } |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: |
| AddPeer(absl::string_view peer_name, |
| std::vector<EmulatedEndpoint*> endpoints, |
| std::vector<EmulatedNetworkNode*> uplink, |
| std::vector<EmulatedNetworkNode*> 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()) |
| << "Two peers can't share the same endpoint"; |
| ip_to_peer_.emplace(endpoint->GetPeerLocalAddress(), peer_name); |
| } |
| } |
| |
| std::map<std::string, NetworkLayerStats> |
| StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector:: |
| GetStats() { |
| MutexLock lock(&mutex_); |
| 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<std::string, NetworkLayerStats> peer_to_stats = |
| stats_accumulator.ReleaseStats(); |
| std::map<std::string, std::vector<std::string>> sender_to_receivers; |
| for (const auto& entry : peer_endpoints_) { |
| const std::string& peer_name = entry.first; |
| const NetworkLayerStats& stats = peer_to_stats[peer_name]; |
| for (const auto& income_stats_entry : |
| 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()) { |
| // Source IP is unknown for this collector, so will be skipped. |
| continue; |
| } |
| sender_to_receivers[it->second].push_back(peer_name); |
| } |
| } |
| for (auto& entry : peer_to_stats) { |
| const std::vector<std::string>& receivers = |
| sender_to_receivers[entry.first]; |
| entry.second.receivers = |
| std::set<std::string>(receivers.begin(), receivers.end()); |
| } |
| return peer_to_stats; |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::AddPeer( |
| absl::string_view peer_name, |
| std::vector<EmulatedEndpoint*> endpoints) { |
| collector_.AddPeer(peer_name, std::move(endpoints), /*uplink=*/{}, |
| /*downlink=*/{}); |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::AddPeer( |
| absl::string_view peer_name, |
| std::vector<EmulatedEndpoint*> endpoints, |
| std::vector<EmulatedNetworkNode*> uplink, |
| std::vector<EmulatedNetworkNode*> downlink) { |
| collector_.AddPeer(peer_name, std::move(endpoints), std::move(uplink), |
| std::move(downlink)); |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::Start( |
| absl::string_view test_case_name, |
| const TrackIdStreamInfoMap* reporter_helper) { |
| test_case_name_ = std::string(test_case_name); |
| collector_.Start(); |
| start_time_ = clock_->CurrentTime(); |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::OnStatsReports( |
| absl::string_view pc_label, |
| const rtc::scoped_refptr<const RTCStatsReport>& report) { |
| PCStats cur_stats; |
| |
| auto inbound_stats = report->GetStatsOfType<RTCInboundRtpStreamStats>(); |
| for (const auto& stat : inbound_stats) { |
| cur_stats.payload_received += |
| DataSize::Bytes(stat->bytes_received.value_or(0ul) + |
| stat->header_bytes_received.value_or(0ul)); |
| } |
| |
| auto outbound_stats = report->GetStatsOfType<RTCOutboundRtpStreamStats>(); |
| for (const auto& stat : outbound_stats) { |
| cur_stats.payload_sent += DataSize::Bytes( |
| stat->bytes_sent.value_or(0ul) + stat->header_bytes_sent.value_or(0ul)); |
| } |
| |
| auto candidate_pairs_stats = report->GetStatsOfType<RTCTransportStats>(); |
| for (const auto& stat : candidate_pairs_stats) { |
| cur_stats.total_received += |
| DataSize::Bytes(stat->bytes_received.value_or(0ul)); |
| cur_stats.total_sent += DataSize::Bytes(stat->bytes_sent.value_or(0ul)); |
| cur_stats.packets_received += stat->packets_received.value_or(0ul); |
| cur_stats.packets_sent += stat->packets_sent.value_or(0ul); |
| } |
| |
| MutexLock lock(&mutex_); |
| pc_stats_[std::string(pc_label)] = cur_stats; |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::StopAndReportResults() { |
| Timestamp end_time = clock_->CurrentTime(); |
| |
| std::map<std::string, NetworkLayerStats> stats = collector_.GetStats(); |
| for (const auto& entry : stats) { |
| LogNetworkLayerStats(entry.first, entry.second); |
| } |
| MutexLock lock(&mutex_); |
| for (const auto& pair : pc_stats_) { |
| auto it = stats.find(pair.first); |
| RTC_CHECK(it != stats.end()) |
| << "Peer name used for PeerConnection stats collection and peer name " |
| "used for endpoints naming doesn't match. No endpoints found for " |
| "peer " |
| << pair.first; |
| const NetworkLayerStats& network_layer_stats = it->second; |
| int64_t total_packets_received = 0; |
| bool found = false; |
| for (const auto& dest_peer : network_layer_stats.receivers) { |
| auto pc_stats_it = pc_stats_.find(dest_peer); |
| if (pc_stats_it == pc_stats_.end()) { |
| continue; |
| } |
| found = true; |
| total_packets_received += pc_stats_it->second.packets_received; |
| } |
| int64_t packet_loss = -1; |
| if (found) { |
| packet_loss = pair.second.packets_sent - total_packets_received; |
| } |
| ReportStats(pair.first, pair.second, network_layer_stats, packet_loss, |
| end_time); |
| } |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::ReportStats( |
| const std::string& pc_label, |
| const PCStats& pc_stats, |
| const NetworkLayerStats& network_layer_stats, |
| int64_t packet_loss, |
| const Timestamp& end_time) { |
| // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. |
| std::map<std::string, std::string> metric_metadata{ |
| {MetricMetadataKey::kPeerMetadataKey, pc_label}, |
| {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; |
| metrics_logger_->LogSingleValueMetric( |
| "bytes_discarded_no_receiver", GetTestCaseName(pc_label), |
| 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.endpoints_stats.overall_incoming_stats |
| .packets_discarded_no_receiver, |
| Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| |
| metrics_logger_->LogSingleValueMetric( |
| "payload_bytes_received", GetTestCaseName(pc_label), |
| pc_stats.payload_received.bytes(), Unit::kBytes, |
| ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "payload_bytes_sent", GetTestCaseName(pc_label), |
| pc_stats.payload_sent.bytes(), Unit::kBytes, |
| ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| |
| metrics_logger_->LogSingleValueMetric( |
| "bytes_sent", GetTestCaseName(pc_label), pc_stats.total_sent.bytes(), |
| Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "packets_sent", GetTestCaseName(pc_label), pc_stats.packets_sent, |
| Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "average_send_rate", GetTestCaseName(pc_label), |
| (pc_stats.total_sent / (end_time - start_time_)).kbps<double>(), |
| Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, |
| metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "bytes_received", GetTestCaseName(pc_label), |
| pc_stats.total_received.bytes(), Unit::kBytes, |
| ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "packets_received", GetTestCaseName(pc_label), pc_stats.packets_received, |
| Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "average_receive_rate", GetTestCaseName(pc_label), |
| (pc_stats.total_received / (end_time - start_time_)).kbps<double>(), |
| Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter, |
| metric_metadata); |
| metrics_logger_->LogSingleValueMetric( |
| "sent_packets_loss", GetTestCaseName(pc_label), packet_loss, |
| Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| } |
| |
| std::string StatsBasedNetworkQualityMetricsReporter::GetTestCaseName( |
| absl::string_view network_label) const { |
| rtc::StringBuilder builder; |
| builder << test_case_name_ << "/" << network_label.data(); |
| return builder.str(); |
| } |
| |
| void StatsBasedNetworkQualityMetricsReporter::LogNetworkLayerStats( |
| const std::string& peer_name, |
| const NetworkLayerStats& stats) const { |
| DataRate average_send_rate = |
| stats.endpoints_stats.overall_outgoing_stats.packets_sent >= 2 |
| ? stats.endpoints_stats.overall_outgoing_stats.AverageSendRate() |
| : DataRate::Zero(); |
| DataRate average_receive_rate = |
| stats.endpoints_stats.overall_incoming_stats.packets_received >= 2 |
| ? stats.endpoints_stats.overall_incoming_stats.AverageReceiveRate() |
| : DataRate::Zero(); |
| // TODO(bugs.webrtc.org/14757): Remove kExperimentalTestNameMetadataKey. |
| std::map<std::string, std::string> metric_metadata{ |
| {MetricMetadataKey::kPeerMetadataKey, peer_name}, |
| {MetricMetadataKey::kExperimentalTestNameMetadataKey, test_case_name_}}; |
| rtc::StringBuilder log; |
| log << "Raw network layer statistic for [" << peer_name << "]:\n" |
| << "Local IPs:\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.endpoints_stats.overall_outgoing_stats.sent_packets_size |
| .IsEmpty()) { |
| metrics_logger_->LogMetric( |
| "sent_packets_size", GetTestCaseName(peer_name), |
| stats.endpoints_stats.overall_outgoing_stats.sent_packets_size, |
| Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| } |
| 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.endpoints_stats.sent_packets_queue_wait_time_us, Unit::kUnitless, |
| ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| } |
| |
| log << "Send statistic:\n" |
| << " 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.endpoints_stats.outgoing_stats_per_destination) { |
| DataRate source_average_send_rate = entry.second.packets_sent >= 2 |
| ? entry.second.AverageSendRate() |
| : DataRate::Zero(); |
| log << "(" << entry.first.ToString() << "):\n" |
| << " packets: " << entry.second.packets_sent |
| << " bytes: " << entry.second.bytes_sent.bytes() |
| << " avg_rate (bytes/sec): " << source_average_send_rate.bytes_per_sec() |
| << " avg_rate (bps): " << source_average_send_rate.bps() << "\n"; |
| if (!entry.second.sent_packets_size.IsEmpty()) { |
| metrics_logger_->LogMetric( |
| "sent_packets_size", |
| GetTestCaseName(peer_name + "/" + entry.first.ToString()), |
| entry.second.sent_packets_size, Unit::kBytes, |
| ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| } |
| } |
| |
| 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.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.endpoints_stats.incoming_stats_per_source) { |
| DataRate source_average_receive_rate = |
| entry.second.packets_received >= 2 ? entry.second.AverageReceiveRate() |
| : DataRate::Zero(); |
| log << "(" << entry.first.ToString() << "):\n" |
| << " packets: " << entry.second.packets_received |
| << " bytes: " << entry.second.bytes_received.bytes() |
| << " avg_rate (bytes/sec): " |
| << source_average_receive_rate.bytes_per_sec() |
| << " avg_rate (bps): " << source_average_receive_rate.bps() << "\n"; |
| if (!entry.second.received_packets_size.IsEmpty()) { |
| metrics_logger_->LogMetric( |
| "received_packets_size", |
| GetTestCaseName(peer_name + "/" + entry.first.ToString()), |
| entry.second.received_packets_size, Unit::kBytes, |
| ImprovementDirection::kNeitherIsBetter, metric_metadata); |
| } |
| if (!entry.second.packets_discarded_no_receiver_size.IsEmpty()) { |
| metrics_logger_->LogMetric( |
| "packets_discarded_no_receiver_size", |
| GetTestCaseName(peer_name + "/" + entry.first.ToString()), |
| entry.second.packets_discarded_no_receiver_size, Unit::kBytes, |
| 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(); |
| } |
| |
| } // namespace webrtc_pc_e2e |
| } // namespace webrtc |