Provide per destination statistic for network outgoing stats

Network emulation layer provides per source split for incoming stats for
endpoint. Do the same for outgoing stats per destination.

Bug: webrtc:11756
Change-Id: I2369ae8906546c27133273b1be17ce74c253c6e8
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/180500
Reviewed-by: Christoffer Rodbro <crodbro@webrtc.org>
Reviewed-by: Andrey Logvin <landrey@webrtc.org>
Commit-Queue: Artem Titov <titovartem@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31820}
diff --git a/api/test/network_emulation/network_emulation_interfaces.h b/api/test/network_emulation/network_emulation_interfaces.h
index b1aa0d2..ebb1eed 100644
--- a/api/test/network_emulation/network_emulation_interfaces.h
+++ b/api/test/network_emulation/network_emulation_interfaces.h
@@ -61,45 +61,66 @@
   virtual void OnPacketReceived(EmulatedIpPacket packet) = 0;
 };
 
-struct EmulatedNetworkIncomingStats {
+class EmulatedNetworkOutgoingStats {
+ public:
+  virtual ~EmulatedNetworkOutgoingStats() = default;
+
+  virtual int64_t PacketsSent() const = 0;
+
+  virtual DataSize BytesSent() const = 0;
+
+  virtual DataSize FirstSentPacketSize() const = 0;
+
+  // Returns time of the first packet sent or infinite value if no packets were
+  // sent.
+  virtual Timestamp FirstPacketSentTime() const = 0;
+
+  // Returns time of the last packet sent or infinite value if no packets were
+  // sent.
+  virtual Timestamp LastPacketSentTime() const = 0;
+
+  // Returns average send rate. Requires that at least 2 packets were sent.
+  virtual DataRate AverageSendRate() const = 0;
+};
+
+class EmulatedNetworkIncomingStats {
+ public:
+  virtual ~EmulatedNetworkIncomingStats() = default;
+
   // Total amount of packets received with or without destination.
-  int64_t packets_received = 0;
+  virtual int64_t PacketsReceived() const = 0;
   // Total amount of bytes in received packets.
-  DataSize bytes_received = DataSize::Zero();
+  virtual DataSize BytesReceived() const = 0;
   // Total amount of packets that were received, but no destination was found.
-  int64_t packets_dropped = 0;
+  virtual int64_t PacketsDropped() const = 0;
   // Total amount of bytes in dropped packets.
-  DataSize bytes_dropped = DataSize::Zero();
+  virtual DataSize BytesDropped() const = 0;
 
-  DataSize first_received_packet_size = DataSize::Zero();
+  virtual DataSize FirstReceivedPacketSize() const = 0;
 
-  // Timestamps are initialized to different infinities for simplifying
-  // computations. Client have to assume that it is some infinite value
-  // if unset. Client mustn't consider sign of infinit value.
-  Timestamp first_packet_received_time = Timestamp::PlusInfinity();
-  Timestamp last_packet_received_time = Timestamp::MinusInfinity();
+  // Returns time of the first packet received or infinite value if no packets
+  // were received.
+  virtual Timestamp FirstPacketReceivedTime() const = 0;
 
-  DataRate AverageReceiveRate() const {
-    RTC_DCHECK_GE(packets_received, 2);
-    RTC_DCHECK(first_packet_received_time.IsFinite());
-    RTC_DCHECK(last_packet_received_time.IsFinite());
-    return (bytes_received - first_received_packet_size) /
-           (last_packet_received_time - first_packet_received_time);
-  }
+  // Returns time of the last packet received or infinite value if no packets
+  // were received.
+  virtual Timestamp LastPacketReceivedTime() const = 0;
+
+  virtual DataRate AverageReceiveRate() const = 0;
 };
 
 class EmulatedNetworkStats {
  public:
   virtual ~EmulatedNetworkStats() = default;
 
-  virtual int64_t PacketsSent() const = 0;
-
-  virtual DataSize BytesSent() const = 0;
-
   // List of IP addresses that were used to send data considered in this stats
   // object.
   virtual std::vector<rtc::IPAddress> LocalAddresses() const = 0;
 
+  virtual int64_t PacketsSent() const = 0;
+
+  virtual DataSize BytesSent() const = 0;
+
   virtual DataSize FirstSentPacketSize() const = 0;
   // Returns time of the first packet sent or infinite value if no packets were
   // sent.
@@ -128,7 +149,12 @@
 
   virtual DataRate AverageReceiveRate() const = 0;
 
-  virtual std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
+  virtual std::map<rtc::IPAddress,
+                   std::unique_ptr<EmulatedNetworkOutgoingStats>>
+  OutgoingStatsPerDestination() const = 0;
+
+  virtual std::map<rtc::IPAddress,
+                   std::unique_ptr<EmulatedNetworkIncomingStats>>
   IncomingStatsPerSource() const = 0;
 };
 
diff --git a/test/network/network_emulation.cc b/test/network/network_emulation.cc
index 2a76725..8de4cba 100644
--- a/test/network/network_emulation.cc
+++ b/test/network/network_emulation.cc
@@ -20,37 +20,66 @@
 
 namespace webrtc {
 
-EmulatedNetworkIncomingStats EmulatedNetworkStatsImpl::GetOverallIncomingStats()
-    const {
-  EmulatedNetworkIncomingStats stats;
-  for (const auto& entry : incoming_stats_per_source_) {
-    const EmulatedNetworkIncomingStats& source = entry.second;
-    stats.packets_received += source.packets_received;
-    stats.bytes_received += source.bytes_received;
-    stats.packets_dropped += source.packets_dropped;
-    stats.bytes_dropped += source.bytes_dropped;
-    if (stats.first_packet_received_time > source.first_packet_received_time) {
-      stats.first_packet_received_time = source.first_packet_received_time;
-      stats.first_received_packet_size = source.first_received_packet_size;
-    }
-    if (stats.last_packet_received_time < source.last_packet_received_time) {
-      stats.last_packet_received_time = source.last_packet_received_time;
-    }
+DataRate EmulatedNetworkOutgoingStatsImpl::AverageSendRate() const {
+  RTC_DCHECK_GE(packets_sent_, 2);
+  RTC_DCHECK(first_packet_sent_time_.IsFinite());
+  RTC_DCHECK(last_packet_sent_time_.IsFinite());
+  return (bytes_sent_ - first_sent_packet_size_) /
+         (last_packet_sent_time_ - first_packet_sent_time_);
+}
+
+DataRate EmulatedNetworkIncomingStatsImpl::AverageReceiveRate() const {
+  RTC_DCHECK_GE(packets_received_, 2);
+  RTC_DCHECK(first_packet_received_time_.IsFinite());
+  RTC_DCHECK(last_packet_received_time_.IsFinite());
+  return (bytes_received_ - first_received_packet_size_) /
+         (last_packet_received_time_ - first_packet_received_time_);
+}
+
+std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+EmulatedNetworkStatsImpl::OutgoingStatsPerDestination() const {
+  std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>> out;
+  for (const auto& entry : outgoing_stats_per_destination_) {
+    out.emplace(entry.first, std::make_unique<EmulatedNetworkOutgoingStatsImpl>(
+                                 *entry.second));
   }
-  return stats;
+  return out;
 }
 
-EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder() {
-  sequence_checker_.Detach();
+std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+EmulatedNetworkStatsImpl::IncomingStatsPerSource() const {
+  std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>> out;
+  for (const auto& entry : incoming_stats_per_source_) {
+    out.emplace(entry.first, std::make_unique<EmulatedNetworkIncomingStatsImpl>(
+                                 *entry.second));
+  }
+  return out;
 }
-EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder(
-    rtc::IPAddress local_ip) {
-  local_addresses_.push_back(local_ip);
+
+std::unique_ptr<EmulatedNetworkOutgoingStats>
+EmulatedNetworkStatsImpl::GetOverallOutgoingStats() const {
+  EmulatedNetworkOutgoingStatsBuilder builder;
+  for (const auto& entry : outgoing_stats_per_destination_) {
+    builder.AddOutgoingStats(*entry.second);
+  }
+  return builder.Build();
+}
+
+std::unique_ptr<EmulatedNetworkIncomingStats>
+EmulatedNetworkStatsImpl::GetOverallIncomingStats() const {
+  EmulatedNetworkIncomingStatsBuilder builder;
+  for (const auto& entry : incoming_stats_per_source_) {
+    builder.AddIncomingStats(*entry.second);
+  }
+  return builder.Build();
+}
+
+EmulatedNetworkOutgoingStatsBuilder::EmulatedNetworkOutgoingStatsBuilder() {
   sequence_checker_.Detach();
 }
 
-void EmulatedNetworkStatsBuilder::OnPacketSent(Timestamp sent_time,
-                                               DataSize packet_size) {
+void EmulatedNetworkOutgoingStatsBuilder::OnPacketSent(Timestamp sent_time,
+                                                       DataSize packet_size) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
   RTC_CHECK_GE(packet_size, DataSize::Zero());
   if (first_packet_sent_time_.IsInfinite()) {
@@ -62,77 +91,150 @@
   bytes_sent_ += packet_size;
 }
 
+void EmulatedNetworkOutgoingStatsBuilder::AddOutgoingStats(
+    const EmulatedNetworkOutgoingStats& stats) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  packets_sent_ += stats.PacketsSent();
+  bytes_sent_ += stats.BytesSent();
+  if (first_packet_sent_time_ > stats.FirstPacketSentTime()) {
+    first_packet_sent_time_ = stats.FirstPacketSentTime();
+    first_sent_packet_size_ = stats.FirstSentPacketSize();
+  }
+  if (last_packet_sent_time_ < stats.LastPacketSentTime()) {
+    last_packet_sent_time_ = stats.LastPacketSentTime();
+  }
+}
+
+std::unique_ptr<EmulatedNetworkOutgoingStats>
+EmulatedNetworkOutgoingStatsBuilder::Build() const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  return std::make_unique<EmulatedNetworkOutgoingStatsImpl>(
+      packets_sent_, bytes_sent_, first_sent_packet_size_,
+      first_packet_sent_time_, last_packet_sent_time_);
+}
+
+EmulatedNetworkIncomingStatsBuilder::EmulatedNetworkIncomingStatsBuilder() {
+  sequence_checker_.Detach();
+}
+
+void EmulatedNetworkIncomingStatsBuilder::OnPacketDropped(
+    DataSize packet_size) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  packets_dropped_++;
+  bytes_dropped_ += packet_size;
+}
+
+void EmulatedNetworkIncomingStatsBuilder::OnPacketReceived(
+    Timestamp received_time,
+    DataSize packet_size) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  RTC_CHECK_GE(packet_size, DataSize::Zero());
+  if (first_packet_received_time_.IsInfinite()) {
+    first_packet_received_time_ = received_time;
+    first_received_packet_size_ = packet_size;
+  }
+  last_packet_received_time_ = received_time;
+  packets_received_++;
+  bytes_received_ += packet_size;
+}
+
+void EmulatedNetworkIncomingStatsBuilder::AddIncomingStats(
+    const EmulatedNetworkIncomingStats& stats) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  packets_received_ += stats.PacketsReceived();
+  bytes_received_ += stats.BytesReceived();
+  packets_dropped_ += stats.PacketsDropped();
+  bytes_dropped_ += stats.BytesDropped();
+  if (first_packet_received_time_ > stats.FirstPacketReceivedTime()) {
+    first_packet_received_time_ = stats.FirstPacketReceivedTime();
+    first_received_packet_size_ = stats.FirstReceivedPacketSize();
+  }
+  if (last_packet_received_time_ < stats.LastPacketReceivedTime()) {
+    last_packet_received_time_ = stats.LastPacketReceivedTime();
+  }
+}
+
+std::unique_ptr<EmulatedNetworkIncomingStats>
+EmulatedNetworkIncomingStatsBuilder::Build() const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  return std::make_unique<EmulatedNetworkIncomingStatsImpl>(
+      packets_received_, bytes_received_, packets_dropped_, bytes_dropped_,
+      first_received_packet_size_, first_packet_received_time_,
+      last_packet_received_time_);
+}
+
+EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder() {
+  sequence_checker_.Detach();
+}
+
+EmulatedNetworkStatsBuilder::EmulatedNetworkStatsBuilder(
+    rtc::IPAddress local_ip) {
+  local_addresses_.push_back(local_ip);
+  sequence_checker_.Detach();
+}
+
+void EmulatedNetworkStatsBuilder::OnPacketSent(Timestamp sent_time,
+                                               rtc::IPAddress destination_ip,
+                                               DataSize packet_size) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  outgoing_stats_per_destination_[destination_ip].OnPacketSent(sent_time,
+                                                               packet_size);
+}
+
 void EmulatedNetworkStatsBuilder::OnPacketDropped(rtc::IPAddress source_ip,
                                                   DataSize packet_size) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
-  RTC_CHECK_GE(packet_size, DataSize::Zero());
-  EmulatedNetworkIncomingStats& source_stats =
-      incoming_stats_per_source_[source_ip];
-  source_stats.packets_dropped++;
-  source_stats.bytes_dropped += packet_size;
+  incoming_stats_per_source_[source_ip].OnPacketDropped(packet_size);
 }
 
 void EmulatedNetworkStatsBuilder::OnPacketReceived(Timestamp received_time,
                                                    rtc::IPAddress source_ip,
                                                    DataSize packet_size) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
-  RTC_CHECK_GE(packet_size, DataSize::Zero());
-  EmulatedNetworkIncomingStats& source_stats =
-      incoming_stats_per_source_[source_ip];
-  if (source_stats.first_packet_received_time.IsInfinite()) {
-    source_stats.first_packet_received_time = received_time;
-    source_stats.first_received_packet_size = packet_size;
-  }
-  source_stats.last_packet_received_time = received_time;
-  source_stats.packets_received++;
-  source_stats.bytes_received += packet_size;
+  incoming_stats_per_source_[source_ip].OnPacketReceived(received_time,
+                                                         packet_size);
 }
 
-void EmulatedNetworkStatsBuilder::AppendEmulatedNetworkStats(
-    std::unique_ptr<EmulatedNetworkStats> stats) {
+void EmulatedNetworkStatsBuilder::AddEmulatedNetworkStats(
+    const EmulatedNetworkStats& stats) {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
-  RTC_CHECK(stats);
-  packets_sent_ += stats->PacketsSent();
-  bytes_sent_ += stats->BytesSent();
-  if (first_packet_sent_time_ > stats->FirstPacketSentTime()) {
-    first_packet_sent_time_ = stats->FirstPacketSentTime();
-    first_sent_packet_size_ = stats->FirstSentPacketSize();
-  }
-  if (last_packet_sent_time_ < stats->LastPacketSentTime()) {
-    last_packet_sent_time_ = stats->LastPacketSentTime();
-  }
-  for (const rtc::IPAddress& addr : stats->LocalAddresses()) {
+
+  // Append IPs from other endpoints stats to the builder.
+  for (const rtc::IPAddress& addr : stats.LocalAddresses()) {
     local_addresses_.push_back(addr);
   }
 
-  const std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
-      incoming_stats_per_source = stats->IncomingStatsPerSource();
+  // Add outgoing stats from other endpoints to the builder.
+  const std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+      outgoing_stats_per_destination = stats.OutgoingStatsPerDestination();
+  for (const auto& entry : outgoing_stats_per_destination) {
+    outgoing_stats_per_destination_[entry.first].AddOutgoingStats(
+        *entry.second);
+  }
+
+  // Add incoming stats from other endpoints to the builder.
+  const std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+      incoming_stats_per_source = stats.IncomingStatsPerSource();
   for (const auto& entry : incoming_stats_per_source) {
-    const EmulatedNetworkIncomingStats& source = entry.second;
-    EmulatedNetworkIncomingStats& in_stats =
-        incoming_stats_per_source_[entry.first];
-    in_stats.packets_received += source.packets_received;
-    in_stats.bytes_received += source.bytes_received;
-    in_stats.packets_dropped += source.packets_dropped;
-    in_stats.bytes_dropped += source.bytes_dropped;
-    if (in_stats.first_packet_received_time >
-        source.first_packet_received_time) {
-      in_stats.first_packet_received_time = source.first_packet_received_time;
-      in_stats.first_received_packet_size = source.first_received_packet_size;
-    }
-    if (in_stats.last_packet_received_time < source.last_packet_received_time) {
-      in_stats.last_packet_received_time = source.last_packet_received_time;
-    }
+    incoming_stats_per_source_[entry.first].AddIncomingStats(*entry.second);
   }
 }
 
 std::unique_ptr<EmulatedNetworkStats> EmulatedNetworkStatsBuilder::Build()
     const {
   RTC_DCHECK_RUN_ON(&sequence_checker_);
+  std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+      outgoing_stats;
+  for (const auto& entry : outgoing_stats_per_destination_) {
+    outgoing_stats.emplace(entry.first, entry.second.Build());
+  }
+  std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+      incoming_stats;
+  for (const auto& entry : incoming_stats_per_source_) {
+    incoming_stats.emplace(entry.first, entry.second.Build());
+  }
   return std::make_unique<EmulatedNetworkStatsImpl>(
-      packets_sent_, bytes_sent_, local_addresses_, first_sent_packet_size_,
-      first_packet_sent_time_, last_packet_sent_time_,
-      incoming_stats_per_source_);
+      local_addresses_, std::move(outgoing_stats), std::move(incoming_stats));
 }
 
 void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) {
@@ -328,7 +430,7 @@
                           clock_->CurrentTime(), application_overhead);
   task_queue_->PostTask([this, packet = std::move(packet)]() mutable {
     RTC_DCHECK_RUN_ON(task_queue_);
-    stats_builder_.OnPacketSent(clock_->CurrentTime(),
+    stats_builder_.OnPacketSent(clock_->CurrentTime(), packet.to.ipaddr(),
                                 DataSize::Bytes(packet.ip_packet_size()));
 
     router_.OnPacketReceived(std::move(packet));
@@ -476,7 +578,7 @@
 std::unique_ptr<EmulatedNetworkStats> EndpointsContainer::GetStats() const {
   EmulatedNetworkStatsBuilder stats_builder;
   for (auto* endpoint : endpoints_) {
-    stats_builder.AppendEmulatedNetworkStats(endpoint->stats());
+    stats_builder.AddEmulatedNetworkStats(*endpoint->stats());
   }
   return stats_builder.Build();
 }
diff --git a/test/network/network_emulation.h b/test/network/network_emulation.h
index 1525d88..5531efd 100644
--- a/test/network/network_emulation.h
+++ b/test/network/network_emulation.h
@@ -36,35 +36,33 @@
 
 namespace webrtc {
 
-// This class is immutable and so is thread safe.
-class EmulatedNetworkStatsImpl final : public EmulatedNetworkStats {
+// This class is immutable and so thread safe.
+class EmulatedNetworkOutgoingStatsImpl final
+    : public EmulatedNetworkOutgoingStats {
  public:
-  EmulatedNetworkStatsImpl(
-      int64_t packets_sent,
-      DataSize bytes_sent,
-      std::vector<rtc::IPAddress> local_addresses,
-      DataSize first_sent_packet_size,
-      Timestamp first_packet_sent_time,
-      Timestamp last_packet_sent_time,
-      std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
-          incoming_stats_per_source)
+  EmulatedNetworkOutgoingStatsImpl(int64_t packets_sent,
+                                   DataSize bytes_sent,
+                                   DataSize first_sent_packet_size,
+                                   Timestamp first_packet_sent_time,
+                                   Timestamp last_packet_sent_time)
       : packets_sent_(packets_sent),
         bytes_sent_(bytes_sent),
-        local_addresses_(std::move(local_addresses)),
         first_sent_packet_size_(first_sent_packet_size),
         first_packet_sent_time_(first_packet_sent_time),
-        last_packet_sent_time_(last_packet_sent_time),
-        incoming_stats_per_source_(std::move(incoming_stats_per_source)) {}
-  ~EmulatedNetworkStatsImpl() override = default;
+        last_packet_sent_time_(last_packet_sent_time) {}
+  explicit EmulatedNetworkOutgoingStatsImpl(
+      const EmulatedNetworkOutgoingStats& stats)
+      : packets_sent_(stats.PacketsSent()),
+        bytes_sent_(stats.BytesSent()),
+        first_sent_packet_size_(stats.FirstSentPacketSize()),
+        first_packet_sent_time_(stats.FirstPacketSentTime()),
+        last_packet_sent_time_(stats.LastPacketSentTime()) {}
+  ~EmulatedNetworkOutgoingStatsImpl() override = default;
 
   int64_t PacketsSent() const override { return packets_sent_; }
 
   DataSize BytesSent() const override { return bytes_sent_; }
 
-  std::vector<rtc::IPAddress> LocalAddresses() const override {
-    return local_addresses_;
-  }
-
   DataSize FirstSentPacketSize() const override {
     return first_sent_packet_size_;
   }
@@ -77,64 +75,220 @@
     return last_packet_sent_time_;
   }
 
-  DataRate AverageSendRate() const override {
-    RTC_DCHECK_GE(packets_sent_, 2);
-    return (bytes_sent_ - first_sent_packet_size_) /
-           (last_packet_sent_time_ - first_packet_sent_time_);
-  }
-
-  int64_t PacketsReceived() const override {
-    return GetOverallIncomingStats().packets_received;
-  }
-
-  DataSize BytesReceived() const override {
-    return GetOverallIncomingStats().bytes_received;
-  }
-
-  int64_t PacketsDropped() const override {
-    return GetOverallIncomingStats().packets_dropped;
-  }
-
-  DataSize BytesDropped() const override {
-    return GetOverallIncomingStats().bytes_dropped;
-  }
-
-  DataSize FirstReceivedPacketSize() const override {
-    return GetOverallIncomingStats().first_received_packet_size;
-  }
-
-  Timestamp FirstPacketReceivedTime() const override {
-    return GetOverallIncomingStats().first_packet_received_time;
-  }
-
-  Timestamp LastPacketReceivedTime() const override {
-    return GetOverallIncomingStats().last_packet_received_time;
-  }
-
-  DataRate AverageReceiveRate() const override {
-    return GetOverallIncomingStats().AverageReceiveRate();
-  }
-
-  std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
-  IncomingStatsPerSource() const override {
-    return incoming_stats_per_source_;
-  }
+  DataRate AverageSendRate() const override;
 
  private:
-  EmulatedNetworkIncomingStats GetOverallIncomingStats() const;
-
   const int64_t packets_sent_;
   const DataSize bytes_sent_;
-  const std::vector<rtc::IPAddress> local_addresses_;
-
   const DataSize first_sent_packet_size_;
   const Timestamp first_packet_sent_time_;
   const Timestamp last_packet_sent_time_;
+};
 
-  const std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
+// This class is immutable and so thread safe.
+class EmulatedNetworkIncomingStatsImpl final
+    : public EmulatedNetworkIncomingStats {
+ public:
+  EmulatedNetworkIncomingStatsImpl(int64_t packets_received,
+                                   DataSize bytes_received,
+                                   int64_t packets_dropped,
+                                   DataSize bytes_dropped,
+                                   DataSize first_received_packet_size,
+                                   Timestamp first_packet_received_time,
+                                   Timestamp last_packet_received_time)
+      : packets_received_(packets_received),
+        bytes_received_(bytes_received),
+        packets_dropped_(packets_dropped),
+        bytes_dropped_(bytes_dropped),
+        first_received_packet_size_(first_received_packet_size),
+        first_packet_received_time_(first_packet_received_time),
+        last_packet_received_time_(last_packet_received_time) {}
+  explicit EmulatedNetworkIncomingStatsImpl(
+      const EmulatedNetworkIncomingStats& stats)
+      : packets_received_(stats.PacketsReceived()),
+        bytes_received_(stats.BytesReceived()),
+        packets_dropped_(stats.PacketsDropped()),
+        bytes_dropped_(stats.BytesDropped()),
+        first_received_packet_size_(stats.FirstReceivedPacketSize()),
+        first_packet_received_time_(stats.FirstPacketReceivedTime()),
+        last_packet_received_time_(stats.LastPacketReceivedTime()) {}
+  ~EmulatedNetworkIncomingStatsImpl() override = default;
+
+  int64_t PacketsReceived() const override { return packets_received_; }
+
+  DataSize BytesReceived() const override { return bytes_received_; }
+
+  int64_t PacketsDropped() const override { return packets_dropped_; }
+
+  DataSize BytesDropped() const override { return bytes_dropped_; }
+
+  DataSize FirstReceivedPacketSize() const override {
+    return first_received_packet_size_;
+  }
+
+  Timestamp FirstPacketReceivedTime() const override {
+    return first_packet_received_time_;
+  }
+
+  Timestamp LastPacketReceivedTime() const override {
+    return last_packet_received_time_;
+  }
+
+  DataRate AverageReceiveRate() const override;
+
+ private:
+  const int64_t packets_received_;
+  const DataSize bytes_received_;
+  const int64_t packets_dropped_;
+  const DataSize bytes_dropped_;
+  const DataSize first_received_packet_size_;
+  const Timestamp first_packet_received_time_;
+  const Timestamp last_packet_received_time_;
+};
+
+// This class is immutable and so is thread safe.
+class EmulatedNetworkStatsImpl final : public EmulatedNetworkStats {
+ public:
+  EmulatedNetworkStatsImpl(
+      std::vector<rtc::IPAddress> local_addresses,
+      std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+          outgoing_stats_per_destination,
+      std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+          incoming_stats_per_source)
+      : local_addresses_(std::move(local_addresses)),
+        outgoing_stats_per_destination_(
+            std::move(outgoing_stats_per_destination)),
+        incoming_stats_per_source_(std::move(incoming_stats_per_source)) {}
+  ~EmulatedNetworkStatsImpl() override = default;
+
+  std::vector<rtc::IPAddress> LocalAddresses() const override {
+    return local_addresses_;
+  }
+
+  int64_t PacketsSent() const override {
+    return GetOverallOutgoingStats()->PacketsSent();
+  }
+
+  DataSize BytesSent() const override {
+    return GetOverallOutgoingStats()->BytesSent();
+  }
+
+  DataSize FirstSentPacketSize() const override {
+    return GetOverallOutgoingStats()->FirstSentPacketSize();
+  }
+
+  Timestamp FirstPacketSentTime() const override {
+    return GetOverallOutgoingStats()->FirstPacketSentTime();
+  }
+
+  Timestamp LastPacketSentTime() const override {
+    return GetOverallOutgoingStats()->LastPacketSentTime();
+  }
+
+  DataRate AverageSendRate() const override {
+    return GetOverallOutgoingStats()->AverageSendRate();
+  }
+
+  int64_t PacketsReceived() const override {
+    return GetOverallIncomingStats()->PacketsReceived();
+  }
+
+  DataSize BytesReceived() const override {
+    return GetOverallIncomingStats()->BytesReceived();
+  }
+
+  int64_t PacketsDropped() const override {
+    return GetOverallIncomingStats()->PacketsDropped();
+  }
+
+  DataSize BytesDropped() const override {
+    return GetOverallIncomingStats()->BytesDropped();
+  }
+
+  DataSize FirstReceivedPacketSize() const override {
+    return GetOverallIncomingStats()->FirstReceivedPacketSize();
+  }
+
+  Timestamp FirstPacketReceivedTime() const override {
+    return GetOverallIncomingStats()->FirstPacketReceivedTime();
+  }
+
+  Timestamp LastPacketReceivedTime() const override {
+    return GetOverallIncomingStats()->LastPacketReceivedTime();
+  }
+
+  DataRate AverageReceiveRate() const override {
+    return GetOverallIncomingStats()->AverageReceiveRate();
+  }
+
+  std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+  OutgoingStatsPerDestination() const override;
+
+  std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+  IncomingStatsPerSource() const override;
+
+ private:
+  std::unique_ptr<EmulatedNetworkOutgoingStats> GetOverallOutgoingStats() const;
+  std::unique_ptr<EmulatedNetworkIncomingStats> GetOverallIncomingStats() const;
+
+  const std::vector<rtc::IPAddress> local_addresses_;
+  const std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+      outgoing_stats_per_destination_;
+  const std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
       incoming_stats_per_source_;
 };
 
+class EmulatedNetworkOutgoingStatsBuilder {
+ public:
+  EmulatedNetworkOutgoingStatsBuilder();
+
+  void OnPacketSent(Timestamp sent_time, DataSize packet_size);
+
+  void AddOutgoingStats(const EmulatedNetworkOutgoingStats& stats);
+
+  std::unique_ptr<EmulatedNetworkOutgoingStats> Build() const;
+
+ private:
+  SequenceChecker sequence_checker_;
+
+  int64_t packets_sent_ RTC_GUARDED_BY(sequence_checker_) = 0;
+  DataSize bytes_sent_ RTC_GUARDED_BY(sequence_checker_) = DataSize::Zero();
+  DataSize first_sent_packet_size_ RTC_GUARDED_BY(sequence_checker_) =
+      DataSize::Zero();
+  Timestamp first_packet_sent_time_ RTC_GUARDED_BY(sequence_checker_) =
+      Timestamp::PlusInfinity();
+  Timestamp last_packet_sent_time_ RTC_GUARDED_BY(sequence_checker_) =
+      Timestamp::MinusInfinity();
+};
+
+class EmulatedNetworkIncomingStatsBuilder {
+ public:
+  EmulatedNetworkIncomingStatsBuilder();
+
+  void OnPacketDropped(DataSize packet_size);
+
+  void OnPacketReceived(Timestamp received_time, DataSize packet_size);
+
+  // Adds stats collected from another endpoints to the builder.
+  void AddIncomingStats(const EmulatedNetworkIncomingStats& stats);
+
+  std::unique_ptr<EmulatedNetworkIncomingStats> Build() const;
+
+ private:
+  SequenceChecker sequence_checker_;
+
+  int64_t packets_received_ RTC_GUARDED_BY(sequence_checker_) = 0;
+  DataSize bytes_received_ RTC_GUARDED_BY(sequence_checker_) = DataSize::Zero();
+  int64_t packets_dropped_ RTC_GUARDED_BY(sequence_checker_) = 0;
+  DataSize bytes_dropped_ RTC_GUARDED_BY(sequence_checker_) = DataSize::Zero();
+  DataSize first_received_packet_size_ RTC_GUARDED_BY(sequence_checker_) =
+      DataSize::Zero();
+  Timestamp first_packet_received_time_ RTC_GUARDED_BY(sequence_checker_) =
+      Timestamp::PlusInfinity();
+  Timestamp last_packet_received_time_ RTC_GUARDED_BY(sequence_checker_) =
+      Timestamp::MinusInfinity();
+};
+
 // All methods of EmulatedNetworkStatsBuilder have to be used on a single
 // thread. It may be created on another thread.
 class EmulatedNetworkStatsBuilder {
@@ -142,7 +296,9 @@
   EmulatedNetworkStatsBuilder();
   explicit EmulatedNetworkStatsBuilder(rtc::IPAddress local_ip);
 
-  void OnPacketSent(Timestamp sent_time, DataSize packet_size);
+  void OnPacketSent(Timestamp sent_time,
+                    rtc::IPAddress destination_ip,
+                    DataSize packet_size);
 
   void OnPacketDropped(rtc::IPAddress source_ip, DataSize packet_size);
 
@@ -150,26 +306,18 @@
                         rtc::IPAddress source_ip,
                         DataSize packet_size);
 
-  void AppendEmulatedNetworkStats(std::unique_ptr<EmulatedNetworkStats> stats);
+  void AddEmulatedNetworkStats(const EmulatedNetworkStats& stats);
 
   std::unique_ptr<EmulatedNetworkStats> Build() const;
 
  private:
   SequenceChecker sequence_checker_;
 
-  int64_t packets_sent_ RTC_GUARDED_BY(sequence_checker_) = 0;
-  DataSize bytes_sent_ RTC_GUARDED_BY(sequence_checker_) = DataSize::Zero();
   std::vector<rtc::IPAddress> local_addresses_
       RTC_GUARDED_BY(sequence_checker_);
-
-  DataSize first_sent_packet_size_ RTC_GUARDED_BY(sequence_checker_) =
-      DataSize::Zero();
-  Timestamp first_packet_sent_time_ RTC_GUARDED_BY(sequence_checker_) =
-      Timestamp::PlusInfinity();
-  Timestamp last_packet_sent_time_ RTC_GUARDED_BY(sequence_checker_) =
-      Timestamp::MinusInfinity();
-
-  std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
+  std::map<rtc::IPAddress, EmulatedNetworkOutgoingStatsBuilder>
+      outgoing_stats_per_destination_ RTC_GUARDED_BY(sequence_checker_);
+  std::map<rtc::IPAddress, EmulatedNetworkIncomingStatsBuilder>
       incoming_stats_per_source_ RTC_GUARDED_BY(sequence_checker_);
 };
 
diff --git a/test/network/network_emulation_manager.cc b/test/network/network_emulation_manager.cc
index afe5033..b5ba454 100644
--- a/test/network/network_emulation_manager.cc
+++ b/test/network/network_emulation_manager.cc
@@ -301,7 +301,7 @@
   task_queue_.PostTask([endpoints, stats_callback]() {
     EmulatedNetworkStatsBuilder stats_builder;
     for (auto* endpoint : endpoints) {
-      stats_builder.AppendEmulatedNetworkStats(endpoint->stats());
+      stats_builder.AddEmulatedNetworkStats(*endpoint->stats());
     }
     stats_callback(stats_builder.Build());
   });
diff --git a/test/network/network_emulation_unittest.cc b/test/network/network_emulation_unittest.cc
index 6914c6e..f9f088a 100644
--- a/test/network/network_emulation_unittest.cc
+++ b/test/network/network_emulation_unittest.cc
@@ -257,20 +257,22 @@
     EXPECT_EQ(st->PacketsDropped(), 0l);
     EXPECT_EQ(st->BytesDropped().bytes(), 0l);
 
-    std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st =
-        st->IncomingStatsPerSource();
+    rtc::IPAddress bob_ip = bob_endpoint->GetPeerLocalAddress();
+    std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+        source_st = st->IncomingStatsPerSource();
     ASSERT_EQ(source_st.size(), 1lu);
-    EXPECT_EQ(
-        source_st.at(bob_endpoint->GetPeerLocalAddress()).packets_received,
-        2000l);
-    EXPECT_EQ(source_st.at(bob_endpoint->GetPeerLocalAddress())
-                  .bytes_received.bytes(),
+    EXPECT_EQ(source_st.at(bob_ip)->PacketsReceived(), 2000l);
+    EXPECT_EQ(source_st.at(bob_ip)->BytesReceived().bytes(),
               single_packet_size * 2000l);
-    EXPECT_EQ(source_st.at(bob_endpoint->GetPeerLocalAddress()).packets_dropped,
-              0l);
-    EXPECT_EQ(
-        source_st.at(bob_endpoint->GetPeerLocalAddress()).bytes_dropped.bytes(),
-        0l);
+    EXPECT_EQ(source_st.at(bob_ip)->PacketsDropped(), 0l);
+    EXPECT_EQ(source_st.at(bob_ip)->BytesDropped().bytes(), 0l);
+
+    std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+        dest_st = st->OutgoingStatsPerDestination();
+    ASSERT_EQ(dest_st.size(), 1lu);
+    EXPECT_EQ(dest_st.at(bob_ip)->PacketsSent(), 2000l);
+    EXPECT_EQ(dest_st.at(bob_ip)->BytesSent().bytes(),
+              single_packet_size * 2000l);
     received_stats_count++;
   });
   nt2->GetStats([&](std::unique_ptr<EmulatedNetworkStats> st) {
@@ -286,21 +288,22 @@
     EXPECT_TRUE(st->FirstPacketReceivedTime().IsFinite());
     EXPECT_TRUE(st->LastPacketReceivedTime().IsFinite());
 
-    std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st =
-        st->IncomingStatsPerSource();
+    rtc::IPAddress alice_ip = alice_endpoint->GetPeerLocalAddress();
+    std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkIncomingStats>>
+        source_st = st->IncomingStatsPerSource();
     ASSERT_EQ(source_st.size(), 1lu);
-    EXPECT_EQ(
-        source_st.at(alice_endpoint->GetPeerLocalAddress()).packets_received,
-        2000l);
-    EXPECT_EQ(source_st.at(alice_endpoint->GetPeerLocalAddress())
-                  .bytes_received.bytes(),
+    EXPECT_EQ(source_st.at(alice_ip)->PacketsReceived(), 2000l);
+    EXPECT_EQ(source_st.at(alice_ip)->BytesReceived().bytes(),
               single_packet_size * 2000l);
-    EXPECT_EQ(
-        source_st.at(alice_endpoint->GetPeerLocalAddress()).packets_dropped,
-        0l);
-    EXPECT_EQ(source_st.at(alice_endpoint->GetPeerLocalAddress())
-                  .bytes_dropped.bytes(),
-              0l);
+    EXPECT_EQ(source_st.at(alice_ip)->PacketsDropped(), 0l);
+    EXPECT_EQ(source_st.at(alice_ip)->BytesDropped().bytes(), 0l);
+
+    std::map<rtc::IPAddress, std::unique_ptr<EmulatedNetworkOutgoingStats>>
+        dest_st = st->OutgoingStatsPerDestination();
+    ASSERT_EQ(dest_st.size(), 1lu);
+    EXPECT_EQ(dest_st.at(alice_ip)->PacketsSent(), 2000l);
+    EXPECT_EQ(dest_st.at(alice_ip)->BytesSent().bytes(),
+              single_packet_size * 2000l);
     received_stats_count++;
   });
   ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 2,