xref: /aosp_15_r20/external/webrtc/test/pc/e2e/stats_based_network_quality_metrics_reporter.cc (revision d9f758449e529ab9291ac668be2861e7a55c2422)
1 /*
2  *  Copyright (c) 2020 The WebRTC project authors. All Rights Reserved.
3  *
4  *  Use of this source code is governed by a BSD-style license
5  *  that can be found in the LICENSE file in the root of the source
6  *  tree. An additional intellectual property rights grant can be found
7  *  in the file PATENTS.  All contributing project authors may
8  *  be found in the AUTHORS file in the root of the source tree.
9  */
10 
11 #include "test/pc/e2e/stats_based_network_quality_metrics_reporter.h"
12 
13 #include <cstdint>
14 #include <map>
15 #include <memory>
16 #include <set>
17 #include <string>
18 #include <type_traits>
19 #include <utility>
20 #include <vector>
21 
22 #include "absl/strings/string_view.h"
23 #include "api/array_view.h"
24 #include "api/scoped_refptr.h"
25 #include "api/sequence_checker.h"
26 #include "api/stats/rtc_stats.h"
27 #include "api/stats/rtcstats_objects.h"
28 #include "api/test/metrics/metric.h"
29 #include "api/test/network_emulation/network_emulation_interfaces.h"
30 #include "api/test/network_emulation_manager.h"
31 #include "api/units/data_rate.h"
32 #include "api/units/timestamp.h"
33 #include "rtc_base/checks.h"
34 #include "rtc_base/event.h"
35 #include "rtc_base/ip_address.h"
36 #include "rtc_base/strings/string_builder.h"
37 #include "rtc_base/synchronization/mutex.h"
38 #include "rtc_base/system/no_unique_address.h"
39 #include "system_wrappers/include/field_trial.h"
40 #include "test/pc/e2e/metric_metadata_keys.h"
41 
42 namespace webrtc {
43 namespace webrtc_pc_e2e {
44 namespace {
45 
46 using ::webrtc::test::ImprovementDirection;
47 using ::webrtc::test::Unit;
48 
49 using NetworkLayerStats =
50     StatsBasedNetworkQualityMetricsReporter::NetworkLayerStats;
51 
52 constexpr TimeDelta kStatsWaitTimeout = TimeDelta::Seconds(1);
53 
54 // Field trial which controls whether to report standard-compliant bytes
55 // sent/received per stream.  If enabled, padding and headers are not included
56 // in bytes sent or received.
57 constexpr char kUseStandardBytesStats[] = "WebRTC-UseStandardBytesStats";
58 
PopulateStats(std::vector<EmulatedEndpoint * > endpoints,NetworkEmulationManager * network_emulation)59 EmulatedNetworkStats PopulateStats(std::vector<EmulatedEndpoint*> endpoints,
60                                    NetworkEmulationManager* network_emulation) {
61   rtc::Event stats_loaded;
62   EmulatedNetworkStats stats;
63   network_emulation->GetStats(endpoints, [&](EmulatedNetworkStats s) {
64     stats = std::move(s);
65     stats_loaded.Set();
66   });
67   bool stats_received = stats_loaded.Wait(kStatsWaitTimeout);
68   RTC_CHECK(stats_received);
69   return stats;
70 }
71 
PopulateIpToPeer(const std::map<std::string,std::vector<EmulatedEndpoint * >> & peer_endpoints)72 std::map<rtc::IPAddress, std::string> PopulateIpToPeer(
73     const std::map<std::string, std::vector<EmulatedEndpoint*>>&
74         peer_endpoints) {
75   std::map<rtc::IPAddress, std::string> out;
76   for (const auto& entry : peer_endpoints) {
77     for (const EmulatedEndpoint* const endpoint : entry.second) {
78       RTC_CHECK(out.find(endpoint->GetPeerLocalAddress()) == out.end())
79           << "Two peers can't share the same endpoint";
80       out.emplace(endpoint->GetPeerLocalAddress(), entry.first);
81     }
82   }
83   return out;
84 }
85 
86 // Accumulates emulated network stats being executed on the network thread.
87 // When all stats are collected stores it in thread safe variable.
88 class EmulatedNetworkStatsAccumulator {
89  public:
90   // `expected_stats_count` - the number of calls to
91   // AddEndpointStats/AddUplinkStats/AddDownlinkStats the accumulator is going
92   // to wait. If called more than expected, the program will crash.
EmulatedNetworkStatsAccumulator(size_t expected_stats_count)93   explicit EmulatedNetworkStatsAccumulator(size_t expected_stats_count)
94       : not_collected_stats_count_(expected_stats_count) {
95     RTC_DCHECK_GE(not_collected_stats_count_, 0);
96     if (not_collected_stats_count_ == 0) {
97       all_stats_collected_.Set();
98     }
99     sequence_checker_.Detach();
100   }
101 
102   // Has to be executed on network thread.
AddEndpointStats(std::string peer_name,EmulatedNetworkStats stats)103   void AddEndpointStats(std::string peer_name, EmulatedNetworkStats stats) {
104     RTC_DCHECK_RUN_ON(&sequence_checker_);
105     n_stats_[peer_name].endpoints_stats = std::move(stats);
106     DecrementNotCollectedStatsCount();
107   }
108 
109   // Has to be executed on network thread.
AddUplinkStats(std::string peer_name,EmulatedNetworkNodeStats stats)110   void AddUplinkStats(std::string peer_name, EmulatedNetworkNodeStats stats) {
111     RTC_DCHECK_RUN_ON(&sequence_checker_);
112     n_stats_[peer_name].uplink_stats = std::move(stats);
113     DecrementNotCollectedStatsCount();
114   }
115 
116   // Has to be executed on network thread.
AddDownlinkStats(std::string peer_name,EmulatedNetworkNodeStats stats)117   void AddDownlinkStats(std::string peer_name, EmulatedNetworkNodeStats stats) {
118     RTC_DCHECK_RUN_ON(&sequence_checker_);
119     n_stats_[peer_name].downlink_stats = std::move(stats);
120     DecrementNotCollectedStatsCount();
121   }
122 
123   // Can be executed on any thread.
124   // Returns true if count down was completed and false if timeout elapsed
125   // before.
Wait(TimeDelta timeout)126   bool Wait(TimeDelta timeout) { return all_stats_collected_.Wait(timeout); }
127 
128   // Can be called once. Returns all collected stats by moving underlying
129   // object.
ReleaseStats()130   std::map<std::string, NetworkLayerStats> ReleaseStats() {
131     RTC_DCHECK(!stats_released_);
132     stats_released_ = true;
133     MutexLock lock(&mutex_);
134     return std::move(stats_);
135   }
136 
137  private:
DecrementNotCollectedStatsCount()138   void DecrementNotCollectedStatsCount() {
139     RTC_DCHECK_RUN_ON(&sequence_checker_);
140     RTC_CHECK_GT(not_collected_stats_count_, 0)
141         << "All stats are already collected";
142     not_collected_stats_count_--;
143     if (not_collected_stats_count_ == 0) {
144       MutexLock lock(&mutex_);
145       stats_ = std::move(n_stats_);
146       all_stats_collected_.Set();
147     }
148   }
149 
150   RTC_NO_UNIQUE_ADDRESS SequenceChecker sequence_checker_;
151   size_t not_collected_stats_count_ RTC_GUARDED_BY(sequence_checker_);
152   // Collected on the network thread. Moved into `stats_` after all stats are
153   // collected.
154   std::map<std::string, NetworkLayerStats> n_stats_
155       RTC_GUARDED_BY(sequence_checker_);
156 
157   rtc::Event all_stats_collected_;
158   Mutex mutex_;
159   std::map<std::string, NetworkLayerStats> stats_ RTC_GUARDED_BY(mutex_);
160   bool stats_released_ = false;
161 };
162 
163 }  // namespace
164 
165 StatsBasedNetworkQualityMetricsReporter::
StatsBasedNetworkQualityMetricsReporter(std::map<std::string,std::vector<EmulatedEndpoint * >> peer_endpoints,NetworkEmulationManager * network_emulation,test::MetricsLogger * metrics_logger)166     StatsBasedNetworkQualityMetricsReporter(
167         std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints,
168         NetworkEmulationManager* network_emulation,
169         test::MetricsLogger* metrics_logger)
170     : collector_(std::move(peer_endpoints), network_emulation),
171       clock_(network_emulation->time_controller()->GetClock()),
172       metrics_logger_(metrics_logger) {
173   RTC_CHECK(metrics_logger_);
174 }
175 
176 StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector::
NetworkLayerStatsCollector(std::map<std::string,std::vector<EmulatedEndpoint * >> peer_endpoints,NetworkEmulationManager * network_emulation)177     NetworkLayerStatsCollector(
178         std::map<std::string, std::vector<EmulatedEndpoint*>> peer_endpoints,
179         NetworkEmulationManager* network_emulation)
180     : peer_endpoints_(std::move(peer_endpoints)),
181       ip_to_peer_(PopulateIpToPeer(peer_endpoints_)),
182       network_emulation_(network_emulation) {}
183 
184 void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector::
Start()185     Start() {
186   MutexLock lock(&mutex_);
187   // Check that network stats are clean before test execution.
188   for (const auto& entry : peer_endpoints_) {
189     EmulatedNetworkStats stats =
190         PopulateStats(entry.second, network_emulation_);
191     RTC_CHECK_EQ(stats.overall_outgoing_stats.packets_sent, 0);
192     RTC_CHECK_EQ(stats.overall_incoming_stats.packets_received, 0);
193   }
194 }
195 
196 void StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector::
AddPeer(absl::string_view peer_name,std::vector<EmulatedEndpoint * > endpoints,std::vector<EmulatedNetworkNode * > uplink,std::vector<EmulatedNetworkNode * > downlink)197     AddPeer(absl::string_view peer_name,
198             std::vector<EmulatedEndpoint*> endpoints,
199             std::vector<EmulatedNetworkNode*> uplink,
200             std::vector<EmulatedNetworkNode*> downlink) {
201   MutexLock lock(&mutex_);
202   // When new peer is added not in the constructor, don't check if it has empty
203   // stats, because their endpoint could be used for traffic before.
204   peer_endpoints_.emplace(peer_name, std::move(endpoints));
205   peer_uplinks_.emplace(peer_name, std::move(uplink));
206   peer_downlinks_.emplace(peer_name, std::move(downlink));
207   for (const EmulatedEndpoint* const endpoint : endpoints) {
208     RTC_CHECK(ip_to_peer_.find(endpoint->GetPeerLocalAddress()) ==
209               ip_to_peer_.end())
210         << "Two peers can't share the same endpoint";
211     ip_to_peer_.emplace(endpoint->GetPeerLocalAddress(), peer_name);
212   }
213 }
214 
215 std::map<std::string, NetworkLayerStats>
216 StatsBasedNetworkQualityMetricsReporter::NetworkLayerStatsCollector::
GetStats()217     GetStats() {
218   MutexLock lock(&mutex_);
219   EmulatedNetworkStatsAccumulator stats_accumulator(
220       peer_endpoints_.size() + peer_uplinks_.size() + peer_downlinks_.size());
221   for (const auto& entry : peer_endpoints_) {
222     network_emulation_->GetStats(
223         entry.second, [&stats_accumulator,
224                        peer = entry.first](EmulatedNetworkStats s) mutable {
225           stats_accumulator.AddEndpointStats(std::move(peer), std::move(s));
226         });
227   }
228   for (const auto& entry : peer_uplinks_) {
229     network_emulation_->GetStats(
230         entry.second, [&stats_accumulator,
231                        peer = entry.first](EmulatedNetworkNodeStats s) mutable {
232           stats_accumulator.AddUplinkStats(std::move(peer), std::move(s));
233         });
234   }
235   for (const auto& entry : peer_downlinks_) {
236     network_emulation_->GetStats(
237         entry.second, [&stats_accumulator,
238                        peer = entry.first](EmulatedNetworkNodeStats s) mutable {
239           stats_accumulator.AddDownlinkStats(std::move(peer), std::move(s));
240         });
241   }
242   bool stats_collected = stats_accumulator.Wait(kStatsWaitTimeout);
243   RTC_CHECK(stats_collected);
244   std::map<std::string, NetworkLayerStats> peer_to_stats =
245       stats_accumulator.ReleaseStats();
246   std::map<std::string, std::vector<std::string>> sender_to_receivers;
247   for (const auto& entry : peer_endpoints_) {
248     const std::string& peer_name = entry.first;
249     const NetworkLayerStats& stats = peer_to_stats[peer_name];
250     for (const auto& income_stats_entry :
251          stats.endpoints_stats.incoming_stats_per_source) {
252       const rtc::IPAddress& source_ip = income_stats_entry.first;
253       auto it = ip_to_peer_.find(source_ip);
254       if (it == ip_to_peer_.end()) {
255         // Source IP is unknown for this collector, so will be skipped.
256         continue;
257       }
258       sender_to_receivers[it->second].push_back(peer_name);
259     }
260   }
261   for (auto& entry : peer_to_stats) {
262     const std::vector<std::string>& receivers =
263         sender_to_receivers[entry.first];
264     entry.second.receivers =
265         std::set<std::string>(receivers.begin(), receivers.end());
266   }
267   return peer_to_stats;
268 }
269 
AddPeer(absl::string_view peer_name,std::vector<EmulatedEndpoint * > endpoints)270 void StatsBasedNetworkQualityMetricsReporter::AddPeer(
271     absl::string_view peer_name,
272     std::vector<EmulatedEndpoint*> endpoints) {
273   collector_.AddPeer(peer_name, std::move(endpoints), /*uplink=*/{},
274                      /*downlink=*/{});
275 }
276 
AddPeer(absl::string_view peer_name,std::vector<EmulatedEndpoint * > endpoints,std::vector<EmulatedNetworkNode * > uplink,std::vector<EmulatedNetworkNode * > downlink)277 void StatsBasedNetworkQualityMetricsReporter::AddPeer(
278     absl::string_view peer_name,
279     std::vector<EmulatedEndpoint*> endpoints,
280     std::vector<EmulatedNetworkNode*> uplink,
281     std::vector<EmulatedNetworkNode*> downlink) {
282   collector_.AddPeer(peer_name, std::move(endpoints), std::move(uplink),
283                      std::move(downlink));
284 }
285 
Start(absl::string_view test_case_name,const TrackIdStreamInfoMap * reporter_helper)286 void StatsBasedNetworkQualityMetricsReporter::Start(
287     absl::string_view test_case_name,
288     const TrackIdStreamInfoMap* reporter_helper) {
289   test_case_name_ = std::string(test_case_name);
290   collector_.Start();
291   start_time_ = clock_->CurrentTime();
292 }
293 
OnStatsReports(absl::string_view pc_label,const rtc::scoped_refptr<const RTCStatsReport> & report)294 void StatsBasedNetworkQualityMetricsReporter::OnStatsReports(
295     absl::string_view pc_label,
296     const rtc::scoped_refptr<const RTCStatsReport>& report) {
297   PCStats cur_stats;
298 
299   auto inbound_stats = report->GetStatsOfType<RTCInboundRTPStreamStats>();
300   for (const auto& stat : inbound_stats) {
301     cur_stats.payload_received +=
302         DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul) +
303                         stat->header_bytes_received.ValueOrDefault(0ul));
304   }
305 
306   auto outbound_stats = report->GetStatsOfType<RTCOutboundRTPStreamStats>();
307   for (const auto& stat : outbound_stats) {
308     cur_stats.payload_sent +=
309         DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul) +
310                         stat->header_bytes_sent.ValueOrDefault(0ul));
311   }
312 
313   auto candidate_pairs_stats = report->GetStatsOfType<RTCTransportStats>();
314   for (const auto& stat : candidate_pairs_stats) {
315     cur_stats.total_received +=
316         DataSize::Bytes(stat->bytes_received.ValueOrDefault(0ul));
317     cur_stats.total_sent +=
318         DataSize::Bytes(stat->bytes_sent.ValueOrDefault(0ul));
319     cur_stats.packets_received += stat->packets_received.ValueOrDefault(0ul);
320     cur_stats.packets_sent += stat->packets_sent.ValueOrDefault(0ul);
321   }
322 
323   MutexLock lock(&mutex_);
324   pc_stats_[std::string(pc_label)] = cur_stats;
325 }
326 
StopAndReportResults()327 void StatsBasedNetworkQualityMetricsReporter::StopAndReportResults() {
328   Timestamp end_time = clock_->CurrentTime();
329 
330   if (!webrtc::field_trial::IsEnabled(kUseStandardBytesStats)) {
331     RTC_LOG(LS_ERROR)
332         << "Non-standard GetStats; \"payload\" counts include RTP headers";
333   }
334 
335   std::map<std::string, NetworkLayerStats> stats = collector_.GetStats();
336   for (const auto& entry : stats) {
337     LogNetworkLayerStats(entry.first, entry.second);
338   }
339   MutexLock lock(&mutex_);
340   for (const auto& pair : pc_stats_) {
341     auto it = stats.find(pair.first);
342     RTC_CHECK(it != stats.end())
343         << "Peer name used for PeerConnection stats collection and peer name "
344            "used for endpoints naming doesn't match. No endpoints found for "
345            "peer "
346         << pair.first;
347     const NetworkLayerStats& network_layer_stats = it->second;
348     int64_t total_packets_received = 0;
349     bool found = false;
350     for (const auto& dest_peer : network_layer_stats.receivers) {
351       auto pc_stats_it = pc_stats_.find(dest_peer);
352       if (pc_stats_it == pc_stats_.end()) {
353         continue;
354       }
355       found = true;
356       total_packets_received += pc_stats_it->second.packets_received;
357     }
358     int64_t packet_loss = -1;
359     if (found) {
360       packet_loss = pair.second.packets_sent - total_packets_received;
361     }
362     ReportStats(pair.first, pair.second, network_layer_stats, packet_loss,
363                 end_time);
364   }
365 }
366 
ReportStats(const std::string & pc_label,const PCStats & pc_stats,const NetworkLayerStats & network_layer_stats,int64_t packet_loss,const Timestamp & end_time)367 void StatsBasedNetworkQualityMetricsReporter::ReportStats(
368     const std::string& pc_label,
369     const PCStats& pc_stats,
370     const NetworkLayerStats& network_layer_stats,
371     int64_t packet_loss,
372     const Timestamp& end_time) {
373   std::map<std::string, std::string> metric_metadata{
374       {MetricMetadataKey::kPeerMetadataKey, pc_label}};
375   metrics_logger_->LogSingleValueMetric(
376       "bytes_discarded_no_receiver", GetTestCaseName(pc_label),
377       network_layer_stats.endpoints_stats.overall_incoming_stats
378           .bytes_discarded_no_receiver.bytes(),
379       Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata);
380   metrics_logger_->LogSingleValueMetric(
381       "packets_discarded_no_receiver", GetTestCaseName(pc_label),
382       network_layer_stats.endpoints_stats.overall_incoming_stats
383           .packets_discarded_no_receiver,
384       Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata);
385 
386   metrics_logger_->LogSingleValueMetric(
387       "payload_bytes_received", GetTestCaseName(pc_label),
388       pc_stats.payload_received.bytes(), Unit::kBytes,
389       ImprovementDirection::kNeitherIsBetter, metric_metadata);
390   metrics_logger_->LogSingleValueMetric(
391       "payload_bytes_sent", GetTestCaseName(pc_label),
392       pc_stats.payload_sent.bytes(), Unit::kBytes,
393       ImprovementDirection::kNeitherIsBetter, metric_metadata);
394 
395   metrics_logger_->LogSingleValueMetric(
396       "bytes_sent", GetTestCaseName(pc_label), pc_stats.total_sent.bytes(),
397       Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata);
398   metrics_logger_->LogSingleValueMetric(
399       "packets_sent", GetTestCaseName(pc_label), pc_stats.packets_sent,
400       Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata);
401   metrics_logger_->LogSingleValueMetric(
402       "average_send_rate", GetTestCaseName(pc_label),
403       (pc_stats.total_sent / (end_time - start_time_)).kbps<double>(),
404       Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter,
405       metric_metadata);
406   metrics_logger_->LogSingleValueMetric(
407       "bytes_received", GetTestCaseName(pc_label),
408       pc_stats.total_received.bytes(), Unit::kBytes,
409       ImprovementDirection::kNeitherIsBetter, metric_metadata);
410   metrics_logger_->LogSingleValueMetric(
411       "packets_received", GetTestCaseName(pc_label), pc_stats.packets_received,
412       Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata);
413   metrics_logger_->LogSingleValueMetric(
414       "average_receive_rate", GetTestCaseName(pc_label),
415       (pc_stats.total_received / (end_time - start_time_)).kbps<double>(),
416       Unit::kKilobitsPerSecond, ImprovementDirection::kNeitherIsBetter,
417       metric_metadata);
418   metrics_logger_->LogSingleValueMetric(
419       "sent_packets_loss", GetTestCaseName(pc_label), packet_loss,
420       Unit::kUnitless, ImprovementDirection::kNeitherIsBetter, metric_metadata);
421 }
422 
GetTestCaseName(absl::string_view network_label) const423 std::string StatsBasedNetworkQualityMetricsReporter::GetTestCaseName(
424     absl::string_view network_label) const {
425   rtc::StringBuilder builder;
426   builder << test_case_name_ << "/" << network_label.data();
427   return builder.str();
428 }
429 
LogNetworkLayerStats(const std::string & peer_name,const NetworkLayerStats & stats) const430 void StatsBasedNetworkQualityMetricsReporter::LogNetworkLayerStats(
431     const std::string& peer_name,
432     const NetworkLayerStats& stats) const {
433   DataRate average_send_rate =
434       stats.endpoints_stats.overall_outgoing_stats.packets_sent >= 2
435           ? stats.endpoints_stats.overall_outgoing_stats.AverageSendRate()
436           : DataRate::Zero();
437   DataRate average_receive_rate =
438       stats.endpoints_stats.overall_incoming_stats.packets_received >= 2
439           ? stats.endpoints_stats.overall_incoming_stats.AverageReceiveRate()
440           : DataRate::Zero();
441   std::map<std::string, std::string> metric_metadata{
442       {MetricMetadataKey::kPeerMetadataKey, peer_name}};
443   rtc::StringBuilder log;
444   log << "Raw network layer statistic for [" << peer_name << "]:\n"
445       << "Local IPs:\n";
446   for (size_t i = 0; i < stats.endpoints_stats.local_addresses.size(); ++i) {
447     log << "  " << stats.endpoints_stats.local_addresses[i].ToString() << "\n";
448   }
449   if (!stats.endpoints_stats.overall_outgoing_stats.sent_packets_size
450            .IsEmpty()) {
451     metrics_logger_->LogMetric(
452         "sent_packets_size", GetTestCaseName(peer_name),
453         stats.endpoints_stats.overall_outgoing_stats.sent_packets_size,
454         Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata);
455   }
456   if (!stats.endpoints_stats.overall_incoming_stats.received_packets_size
457            .IsEmpty()) {
458     metrics_logger_->LogMetric(
459         "received_packets_size", GetTestCaseName(peer_name),
460         stats.endpoints_stats.overall_incoming_stats.received_packets_size,
461         Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata);
462   }
463   if (!stats.endpoints_stats.overall_incoming_stats
464            .packets_discarded_no_receiver_size.IsEmpty()) {
465     metrics_logger_->LogMetric(
466         "packets_discarded_no_receiver_size", GetTestCaseName(peer_name),
467         stats.endpoints_stats.overall_incoming_stats
468             .packets_discarded_no_receiver_size,
469         Unit::kBytes, ImprovementDirection::kNeitherIsBetter, metric_metadata);
470   }
471   if (!stats.endpoints_stats.sent_packets_queue_wait_time_us.IsEmpty()) {
472     metrics_logger_->LogMetric(
473         "sent_packets_queue_wait_time_us", GetTestCaseName(peer_name),
474         stats.endpoints_stats.sent_packets_queue_wait_time_us, Unit::kUnitless,
475         ImprovementDirection::kNeitherIsBetter, metric_metadata);
476   }
477 
478   log << "Send statistic:\n"
479       << "  packets: "
480       << stats.endpoints_stats.overall_outgoing_stats.packets_sent << " bytes: "
481       << stats.endpoints_stats.overall_outgoing_stats.bytes_sent.bytes()
482       << " avg_rate (bytes/sec): " << average_send_rate.bytes_per_sec()
483       << " avg_rate (bps): " << average_send_rate.bps() << "\n"
484       << "Send statistic per destination:\n";
485 
486   for (const auto& entry :
487        stats.endpoints_stats.outgoing_stats_per_destination) {
488     DataRate source_average_send_rate = entry.second.packets_sent >= 2
489                                             ? entry.second.AverageSendRate()
490                                             : DataRate::Zero();
491     log << "(" << entry.first.ToString() << "):\n"
492         << "  packets: " << entry.second.packets_sent
493         << " bytes: " << entry.second.bytes_sent.bytes()
494         << " avg_rate (bytes/sec): " << source_average_send_rate.bytes_per_sec()
495         << " avg_rate (bps): " << source_average_send_rate.bps() << "\n";
496     if (!entry.second.sent_packets_size.IsEmpty()) {
497       metrics_logger_->LogMetric(
498           "sent_packets_size",
499           GetTestCaseName(peer_name + "/" + entry.first.ToString()),
500           entry.second.sent_packets_size, Unit::kBytes,
501           ImprovementDirection::kNeitherIsBetter, metric_metadata);
502     }
503   }
504 
505   if (!stats.uplink_stats.packet_transport_time.IsEmpty()) {
506     log << "[Debug stats] packet_transport_time=("
507         << stats.uplink_stats.packet_transport_time.GetAverage() << ", "
508         << stats.uplink_stats.packet_transport_time.GetStandardDeviation()
509         << ")\n";
510     metrics_logger_->LogMetric(
511         "uplink_packet_transport_time", GetTestCaseName(peer_name),
512         stats.uplink_stats.packet_transport_time, Unit::kMilliseconds,
513         ImprovementDirection::kNeitherIsBetter, metric_metadata);
514   }
515   if (!stats.uplink_stats.size_to_packet_transport_time.IsEmpty()) {
516     log << "[Debug stats] size_to_packet_transport_time=("
517         << stats.uplink_stats.size_to_packet_transport_time.GetAverage() << ", "
518         << stats.uplink_stats.size_to_packet_transport_time
519                .GetStandardDeviation()
520         << ")\n";
521     metrics_logger_->LogMetric(
522         "uplink_size_to_packet_transport_time", GetTestCaseName(peer_name),
523         stats.uplink_stats.size_to_packet_transport_time, Unit::kUnitless,
524         ImprovementDirection::kNeitherIsBetter, metric_metadata);
525   }
526 
527   log << "Receive statistic:\n"
528       << "  packets: "
529       << stats.endpoints_stats.overall_incoming_stats.packets_received
530       << " bytes: "
531       << stats.endpoints_stats.overall_incoming_stats.bytes_received.bytes()
532       << " avg_rate (bytes/sec): " << average_receive_rate.bytes_per_sec()
533       << " avg_rate (bps): " << average_receive_rate.bps() << "\n"
534       << "Receive statistic per source:\n";
535 
536   for (const auto& entry : stats.endpoints_stats.incoming_stats_per_source) {
537     DataRate source_average_receive_rate =
538         entry.second.packets_received >= 2 ? entry.second.AverageReceiveRate()
539                                            : DataRate::Zero();
540     log << "(" << entry.first.ToString() << "):\n"
541         << "  packets: " << entry.second.packets_received
542         << " bytes: " << entry.second.bytes_received.bytes()
543         << " avg_rate (bytes/sec): "
544         << source_average_receive_rate.bytes_per_sec()
545         << " avg_rate (bps): " << source_average_receive_rate.bps() << "\n";
546     if (!entry.second.received_packets_size.IsEmpty()) {
547       metrics_logger_->LogMetric(
548           "received_packets_size",
549           GetTestCaseName(peer_name + "/" + entry.first.ToString()),
550           entry.second.received_packets_size, Unit::kBytes,
551           ImprovementDirection::kNeitherIsBetter, metric_metadata);
552     }
553     if (!entry.second.packets_discarded_no_receiver_size.IsEmpty()) {
554       metrics_logger_->LogMetric(
555           "packets_discarded_no_receiver_size",
556           GetTestCaseName(peer_name + "/" + entry.first.ToString()),
557           entry.second.packets_discarded_no_receiver_size, Unit::kBytes,
558           ImprovementDirection::kNeitherIsBetter, metric_metadata);
559     }
560   }
561   if (!stats.downlink_stats.packet_transport_time.IsEmpty()) {
562     log << "[Debug stats] packet_transport_time=("
563         << stats.downlink_stats.packet_transport_time.GetAverage() << ", "
564         << stats.downlink_stats.packet_transport_time.GetStandardDeviation()
565         << ")\n";
566     metrics_logger_->LogMetric(
567         "downlink_packet_transport_time", GetTestCaseName(peer_name),
568         stats.downlink_stats.packet_transport_time, Unit::kMilliseconds,
569         ImprovementDirection::kNeitherIsBetter, metric_metadata);
570   }
571   if (!stats.downlink_stats.size_to_packet_transport_time.IsEmpty()) {
572     log << "[Debug stats] size_to_packet_transport_time=("
573         << stats.downlink_stats.size_to_packet_transport_time.GetAverage()
574         << ", "
575         << stats.downlink_stats.size_to_packet_transport_time
576                .GetStandardDeviation()
577         << ")\n";
578     metrics_logger_->LogMetric(
579         "downlink_size_to_packet_transport_time", GetTestCaseName(peer_name),
580         stats.downlink_stats.size_to_packet_transport_time, Unit::kUnitless,
581         ImprovementDirection::kNeitherIsBetter, metric_metadata);
582   }
583 
584   RTC_LOG(LS_INFO) << log.str();
585 }
586 
587 }  // namespace webrtc_pc_e2e
588 }  // namespace webrtc
589