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