Replace network layer stats struct with interface

It is a follow up CL to
https://webrtc-review.googlesource.com/c/src/+/179368.
Now when network stats became more complex structure it's better to hide
its implementation details and provide an interface for read-only
access.

Bug: webrtc:11756
Change-Id: I1980ef938f8de0c6aa90092d1dc90a14a82e0ee1
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/179840
Commit-Queue: Tommi <tommi@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Reviewed-by: Tommi <tommi@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#31784}
diff --git a/api/test/network_emulation/BUILD.gn b/api/test/network_emulation/BUILD.gn
index a3dd961..4780da2 100644
--- a/api/test/network_emulation/BUILD.gn
+++ b/api/test/network_emulation/BUILD.gn
@@ -17,6 +17,7 @@
   ]
 
   deps = [
+    "../..:array_view",
     "../../../rtc_base",
     "../../../rtc_base:checks",
     "../../../rtc_base:rtc_base_approved",
diff --git a/api/test/network_emulation/network_emulation_interfaces.h b/api/test/network_emulation/network_emulation_interfaces.h
index 260ab0e..b1aa0d2 100644
--- a/api/test/network_emulation/network_emulation_interfaces.h
+++ b/api/test/network_emulation/network_emulation_interfaces.h
@@ -11,9 +11,11 @@
 #define API_TEST_NETWORK_EMULATION_NETWORK_EMULATION_INTERFACES_H_
 
 #include <map>
+#include <memory>
 #include <vector>
 
 #include "absl/types/optional.h"
+#include "api/array_view.h"
 #include "api/units/data_rate.h"
 #include "api/units/data_size.h"
 #include "api/units/timestamp.h"
@@ -86,107 +88,48 @@
   }
 };
 
-struct EmulatedNetworkStats {
-  int64_t packets_sent = 0;
-  DataSize bytes_sent = DataSize::Zero();
+class EmulatedNetworkStats {
+ public:
+  virtual ~EmulatedNetworkStats() = default;
 
-  DataSize first_sent_packet_size = DataSize::Zero();
-  Timestamp first_packet_sent_time = Timestamp::PlusInfinity();
-  Timestamp last_packet_sent_time = Timestamp::MinusInfinity();
+  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.
-  std::vector<rtc::IPAddress> local_addresses;
+  virtual std::vector<rtc::IPAddress> LocalAddresses() const = 0;
 
-  std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
-      incoming_stats_per_source;
+  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;
 
-  DataRate AverageSendRate() const {
-    RTC_DCHECK_GE(packets_sent, 2);
-    return (bytes_sent - first_sent_packet_size) /
-           (last_packet_sent_time - first_packet_sent_time);
-  }
-
+  virtual DataRate AverageSendRate() const = 0;
   // Total amount of packets received regardless of the destination address.
-  int64_t PacketsReceived() const {
-    int64_t packets_received = 0;
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      packets_received += incoming_stats.second.packets_received;
-    }
-    return packets_received;
-  }
-
+  virtual int64_t PacketsReceived() const = 0;
   // Total amount of bytes in received packets.
-  DataSize BytesReceived() const {
-    DataSize bytes_received = DataSize::Zero();
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      bytes_received += incoming_stats.second.bytes_received;
-    }
-    return bytes_received;
-  }
-
+  virtual DataSize BytesReceived() const = 0;
   // Total amount of packets that were received, but no destination was found.
-  int64_t PacketsDropped() const {
-    int64_t packets_dropped = 0;
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      packets_dropped += incoming_stats.second.packets_dropped;
-    }
-    return packets_dropped;
-  }
-
+  virtual int64_t PacketsDropped() const = 0;
   // Total amount of bytes in dropped packets.
-  DataSize BytesDropped() const {
-    DataSize bytes_dropped = DataSize::Zero();
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      bytes_dropped += incoming_stats.second.bytes_dropped;
-    }
-    return bytes_dropped;
-  }
+  virtual DataSize BytesDropped() const = 0;
 
-  DataSize FirstReceivedPacketSize() const {
-    Timestamp first_packet_received_time = Timestamp::PlusInfinity();
-    DataSize first_received_packet_size = DataSize::Zero();
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      if (first_packet_received_time >
-          incoming_stats.second.first_packet_received_time) {
-        first_packet_received_time =
-            incoming_stats.second.first_packet_received_time;
-        first_received_packet_size =
-            incoming_stats.second.first_received_packet_size;
-      }
-    }
-    return first_received_packet_size;
-  }
+  virtual DataSize FirstReceivedPacketSize() const = 0;
+  // Returns time of the first packet received or infinite value if no packets
+  // were received.
+  virtual Timestamp FirstPacketReceivedTime() const = 0;
+  // Returns time of the last packet received or infinite value if no packets
+  // were received.
+  virtual Timestamp LastPacketReceivedTime() const = 0;
 
-  Timestamp FirstPacketReceivedTime() const {
-    Timestamp first_packet_received_time = Timestamp::PlusInfinity();
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      if (first_packet_received_time >
-          incoming_stats.second.first_packet_received_time) {
-        first_packet_received_time =
-            incoming_stats.second.first_packet_received_time;
-      }
-    }
-    return first_packet_received_time;
-  }
+  virtual DataRate AverageReceiveRate() const = 0;
 
-  Timestamp LastPacketReceivedTime() const {
-    Timestamp last_packet_received_time = Timestamp::MinusInfinity();
-    for (const auto& incoming_stats : incoming_stats_per_source) {
-      if (last_packet_received_time <
-          incoming_stats.second.last_packet_received_time) {
-        last_packet_received_time =
-            incoming_stats.second.last_packet_received_time;
-      }
-    }
-    return last_packet_received_time;
-  }
-
-  DataRate AverageReceiveRate() const {
-    RTC_DCHECK_GE(PacketsReceived(), 2);
-    return (BytesReceived() - FirstReceivedPacketSize()) /
-           (LastPacketReceivedTime() - FirstPacketReceivedTime());
-  }
+  virtual std::map<rtc::IPAddress, EmulatedNetworkIncomingStats>
+  IncomingStatsPerSource() const = 0;
 };
 
 // EmulatedEndpoint is an abstraction for network interface on device. Instances
@@ -218,7 +161,7 @@
   virtual void UnbindReceiver(uint16_t port) = 0;
   virtual rtc::IPAddress GetPeerLocalAddress() const = 0;
 
-  virtual EmulatedNetworkStats stats() = 0;
+  virtual std::unique_ptr<EmulatedNetworkStats> stats() const = 0;
 
  private:
   // Ensure that there can be no other subclass than EmulatedEndpointImpl. This
diff --git a/api/test/network_emulation_manager.h b/api/test/network_emulation_manager.h
index 3e9cf11..bdbc501 100644
--- a/api/test/network_emulation_manager.h
+++ b/api/test/network_emulation_manager.h
@@ -11,6 +11,7 @@
 #ifndef API_TEST_NETWORK_EMULATION_MANAGER_H_
 #define API_TEST_NETWORK_EMULATION_MANAGER_H_
 
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -69,7 +70,8 @@
 
   // Returns summarized network stats for endpoints for this manager.
   virtual void GetStats(
-      std::function<void(EmulatedNetworkStats)> stats_callback) const = 0;
+      std::function<void(std::unique_ptr<EmulatedNetworkStats>)> stats_callback)
+      const = 0;
 };
 
 enum class TimeMode { kRealTime, kSimulated };
diff --git a/test/network/BUILD.gn b/test/network/BUILD.gn
index 3567374..058f473 100644
--- a/test/network/BUILD.gn
+++ b/test/network/BUILD.gn
@@ -35,6 +35,7 @@
     "traffic_route.h",
   ]
   deps = [
+    "../../api:array_view",
     "../../api:network_emulation_manager_api",
     "../../api:simulated_network_api",
     "../../api:time_controller",
diff --git a/test/network/emulated_network_manager.cc b/test/network/emulated_network_manager.cc
index 2dc2fad..ec8b2b3 100644
--- a/test/network/emulated_network_manager.cc
+++ b/test/network/emulated_network_manager.cc
@@ -80,7 +80,8 @@
 }
 
 void EmulatedNetworkManager::GetStats(
-    std::function<void(EmulatedNetworkStats)> stats_callback) const {
+    std::function<void(std::unique_ptr<EmulatedNetworkStats>)> stats_callback)
+    const {
   task_queue_->PostTask([stats_callback, this]() {
     stats_callback(endpoints_container_->GetStats());
   });
diff --git a/test/network/emulated_network_manager.h b/test/network/emulated_network_manager.h
index ca85d0b..db88415 100644
--- a/test/network/emulated_network_manager.h
+++ b/test/network/emulated_network_manager.h
@@ -11,6 +11,7 @@
 #ifndef TEST_NETWORK_EMULATED_NETWORK_MANAGER_H_
 #define TEST_NETWORK_EMULATED_NETWORK_MANAGER_H_
 
+#include <functional>
 #include <memory>
 #include <vector>
 
@@ -49,8 +50,8 @@
   // EmulatedNetworkManagerInterface API
   rtc::Thread* network_thread() override { return network_thread_.get(); }
   rtc::NetworkManager* network_manager() override { return this; }
-  void GetStats(
-      std::function<void(EmulatedNetworkStats)> stats_callback) const override;
+  void GetStats(std::function<void(std::unique_ptr<EmulatedNetworkStats>)>
+                    stats_callback) const override;
 
  private:
   void UpdateNetworksOnce();
diff --git a/test/network/network_emulation.cc b/test/network/network_emulation.cc
index f3e2931..c71f6bb 100644
--- a/test/network/network_emulation.cc
+++ b/test/network/network_emulation.cc
@@ -20,6 +20,121 @@
 
 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;
+    }
+  }
+  return stats;
+}
+
+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,
+                                               DataSize packet_size) {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  RTC_CHECK_GE(packet_size, DataSize::Zero());
+  if (first_packet_sent_time_.IsInfinite()) {
+    first_packet_sent_time_ = sent_time;
+    first_sent_packet_size_ = packet_size;
+  }
+  last_packet_sent_time_ = sent_time;
+  packets_sent_++;
+  bytes_sent_ += 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;
+}
+
+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;
+}
+
+void EmulatedNetworkStatsBuilder::AppendEmulatedNetworkStats(
+    std::unique_ptr<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()) {
+    local_addresses_.push_back(addr);
+  }
+
+  const std::map<rtc::IPAddress, 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;
+    }
+  }
+}
+
+std::unique_ptr<EmulatedNetworkStats> EmulatedNetworkStatsBuilder::Build()
+    const {
+  RTC_DCHECK_RUN_ON(&sequence_checker_);
+  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_);
+}
+
 void LinkEmulation::OnPacketReceived(EmulatedIpPacket packet) {
   task_queue_->PostTask([this, packet = std::move(packet)]() mutable {
     RTC_DCHECK_RUN_ON(task_queue_);
@@ -179,7 +294,8 @@
       clock_(clock),
       task_queue_(task_queue),
       router_(task_queue_),
-      next_port_(kFirstEphemeralPort) {
+      next_port_(kFirstEphemeralPort),
+      stats_builder_(peer_local_addr_) {
   constexpr int kIPv4NetworkPrefixLength = 24;
   constexpr int kIPv6NetworkPrefixLength = 64;
 
@@ -196,7 +312,6 @@
   network_->AddIP(ip);
 
   enabled_state_checker_.Detach();
-  stats_.local_addresses.push_back(peer_local_addr_);
 }
 EmulatedEndpointImpl::~EmulatedEndpointImpl() = default;
 
@@ -213,14 +328,8 @@
                           clock_->CurrentTime(), application_overhead);
   task_queue_->PostTask([this, packet = std::move(packet)]() mutable {
     RTC_DCHECK_RUN_ON(task_queue_);
-    Timestamp current_time = clock_->CurrentTime();
-    if (stats_.first_packet_sent_time.IsInfinite()) {
-      stats_.first_packet_sent_time = current_time;
-      stats_.first_sent_packet_size = DataSize::Bytes(packet.ip_packet_size());
-    }
-    stats_.last_packet_sent_time = current_time;
-    stats_.packets_sent++;
-    stats_.bytes_sent += DataSize::Bytes(packet.ip_packet_size());
+    stats_builder_.OnPacketSent(clock_->CurrentTime(),
+                                DataSize::Bytes(packet.ip_packet_size()));
 
     router_.OnPacketReceived(std::move(packet));
   });
@@ -283,7 +392,8 @@
       << packet.to.ipaddr().ToString()
       << "; Receiver peer_local_addr_=" << peer_local_addr_.ToString();
   rtc::CritScope crit(&receiver_lock_);
-  UpdateReceiveStats(packet);
+  stats_builder_.OnPacketReceived(clock_->CurrentTime(), packet.from.ipaddr(),
+                                  DataSize::Bytes(packet.ip_packet_size()));
   auto it = port_to_receiver_.find(packet.to.port());
   if (it == port_to_receiver_.end()) {
     // It can happen, that remote peer closed connection, but there still some
@@ -291,9 +401,8 @@
     // process: one peer closed connection, second still sending data.
     RTC_LOG(INFO) << "Drop packet: no receiver registered in " << id_
                   << " on port " << packet.to.port();
-    stats_.incoming_stats_per_source[packet.from.ipaddr()].packets_dropped++;
-    stats_.incoming_stats_per_source[packet.from.ipaddr()].bytes_dropped +=
-        DataSize::Bytes(packet.ip_packet_size());
+    stats_builder_.OnPacketDropped(packet.from.ipaddr(),
+                                   DataSize::Bytes(packet.ip_packet_size()));
     return;
   }
   // Endpoint assumes frequent calls to bind and unbind methods, so it holds
@@ -319,26 +428,9 @@
   return is_enabled_;
 }
 
-EmulatedNetworkStats EmulatedEndpointImpl::stats() {
+std::unique_ptr<EmulatedNetworkStats> EmulatedEndpointImpl::stats() const {
   RTC_DCHECK_RUN_ON(task_queue_);
-  return stats_;
-}
-
-void EmulatedEndpointImpl::UpdateReceiveStats(const EmulatedIpPacket& packet) {
-  RTC_DCHECK_RUN_ON(task_queue_);
-  Timestamp current_time = clock_->CurrentTime();
-  if (stats_.incoming_stats_per_source[packet.from.ipaddr()]
-          .first_packet_received_time.IsInfinite()) {
-    stats_.incoming_stats_per_source[packet.from.ipaddr()]
-        .first_packet_received_time = current_time;
-    stats_.incoming_stats_per_source[packet.from.ipaddr()]
-        .first_received_packet_size = DataSize::Bytes(packet.ip_packet_size());
-  }
-  stats_.incoming_stats_per_source[packet.from.ipaddr()]
-      .last_packet_received_time = current_time;
-  stats_.incoming_stats_per_source[packet.from.ipaddr()].packets_received++;
-  stats_.incoming_stats_per_source[packet.from.ipaddr()].bytes_received +=
-      DataSize::Bytes(packet.ip_packet_size());
+  return stats_builder_.Build();
 }
 
 EndpointsContainer::EndpointsContainer(
@@ -377,42 +469,12 @@
   return networks;
 }
 
-EmulatedNetworkStats EndpointsContainer::GetStats() const {
-  EmulatedNetworkStats stats;
+std::unique_ptr<EmulatedNetworkStats> EndpointsContainer::GetStats() const {
+  EmulatedNetworkStatsBuilder stats_builder;
   for (auto* endpoint : endpoints_) {
-    EmulatedNetworkStats endpoint_stats = endpoint->stats();
-    stats.packets_sent += endpoint_stats.packets_sent;
-    stats.bytes_sent += endpoint_stats.bytes_sent;
-    if (stats.first_packet_sent_time > endpoint_stats.first_packet_sent_time) {
-      stats.first_packet_sent_time = endpoint_stats.first_packet_sent_time;
-      stats.first_sent_packet_size = endpoint_stats.first_sent_packet_size;
-    }
-    if (stats.last_packet_sent_time < endpoint_stats.last_packet_sent_time) {
-      stats.last_packet_sent_time = endpoint_stats.last_packet_sent_time;
-    }
-    for (const rtc::IPAddress& addr : endpoint_stats.local_addresses) {
-      stats.local_addresses.push_back(addr);
-    }
-    for (auto& entry : endpoint_stats.incoming_stats_per_source) {
-      const EmulatedNetworkIncomingStats& source = entry.second;
-      EmulatedNetworkIncomingStats& in_stats =
-          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;
-      }
-    }
+    stats_builder.AppendEmulatedNetworkStats(endpoint->stats());
   }
-  return stats;
+  return stats_builder.Build();
 }
 
 }  // namespace webrtc
diff --git a/test/network/network_emulation.h b/test/network/network_emulation.h
index a811a10..d2bb121 100644
--- a/test/network/network_emulation.h
+++ b/test/network/network_emulation.h
@@ -20,6 +20,7 @@
 #include <vector>
 
 #include "absl/types/optional.h"
+#include "api/array_view.h"
 #include "api/test/network_emulation_manager.h"
 #include "api/test/simulated_network.h"
 #include "api/units/timestamp.h"
@@ -27,6 +28,7 @@
 #include "rtc_base/network.h"
 #include "rtc_base/network_constants.h"
 #include "rtc_base/socket_address.h"
+#include "rtc_base/synchronization/sequence_checker.h"
 #include "rtc_base/task_queue_for_test.h"
 #include "rtc_base/task_utils/repeating_task.h"
 #include "rtc_base/thread_checker.h"
@@ -34,6 +36,142 @@
 
 namespace webrtc {
 
+// This class is immutable and so is thread safe.
+class EmulatedNetworkStatsImpl final : public EmulatedNetworkStats {
+ 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)
+      : 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;
+
+  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_;
+  }
+
+  Timestamp FirstPacketSentTime() const override {
+    return first_packet_sent_time_;
+  }
+
+  Timestamp LastPacketSentTime() const override {
+    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_;
+  }
+
+ 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>
+      incoming_stats_per_source_;
+};
+
+// All methods of EmulatedNetworkStatsBuilder have to be used on a single
+// thread. It may be created on another thread.
+class EmulatedNetworkStatsBuilder {
+ public:
+  EmulatedNetworkStatsBuilder();
+  explicit EmulatedNetworkStatsBuilder(rtc::IPAddress local_ip);
+
+  void OnPacketSent(Timestamp sent_time, DataSize packet_size);
+
+  void OnPacketDropped(rtc::IPAddress source_ip, DataSize packet_size);
+
+  void OnPacketReceived(Timestamp received_time,
+                        rtc::IPAddress source_ip,
+                        DataSize packet_size);
+
+  void AppendEmulatedNetworkStats(std::unique_ptr<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>
+      incoming_stats_per_source_ RTC_GUARDED_BY(sequence_checker_);
+};
 
 class LinkEmulation : public EmulatedNetworkReceiverInterface {
  public:
@@ -161,12 +299,11 @@
 
   const rtc::Network& network() const { return *network_.get(); }
 
-  EmulatedNetworkStats stats() override;
+  std::unique_ptr<EmulatedNetworkStats> stats() const override;
 
  private:
   static constexpr uint16_t kFirstEphemeralPort = 49152;
   uint16_t NextPort() RTC_EXCLUSIVE_LOCKS_REQUIRED(receiver_lock_);
-  void UpdateReceiveStats(const EmulatedIpPacket& packet);
 
   rtc::RecursiveCriticalSection receiver_lock_;
   rtc::ThreadChecker enabled_state_checker_;
@@ -185,7 +322,7 @@
   std::map<uint16_t, EmulatedNetworkReceiverInterface*> port_to_receiver_
       RTC_GUARDED_BY(receiver_lock_);
 
-  EmulatedNetworkStats stats_ RTC_GUARDED_BY(task_queue_);
+  EmulatedNetworkStatsBuilder stats_builder_ RTC_GUARDED_BY(task_queue_);
 };
 
 class EmulatedRoute {
@@ -212,7 +349,7 @@
   // Returns list of networks for enabled endpoints. Caller takes ownership of
   // returned rtc::Network objects.
   std::vector<std::unique_ptr<rtc::Network>> GetEnabledNetworks() const;
-  EmulatedNetworkStats GetStats() const;
+  std::unique_ptr<EmulatedNetworkStats> GetStats() const;
 
  private:
   const std::vector<EmulatedEndpointImpl*> endpoints_;
diff --git a/test/network/network_emulation_unittest.cc b/test/network/network_emulation_unittest.cc
index ff85390..6914c6e 100644
--- a/test/network/network_emulation_unittest.cc
+++ b/test/network/network_emulation_unittest.cc
@@ -247,59 +247,60 @@
 
   const int64_t single_packet_size = data.size() + kOverheadIpv4Udp;
   std::atomic<int> received_stats_count{0};
-  nt1->GetStats([&](EmulatedNetworkStats st) {
-    EXPECT_EQ(st.packets_sent, 2000l);
-    EXPECT_EQ(st.bytes_sent.bytes(), single_packet_size * 2000l);
-    EXPECT_THAT(st.local_addresses,
+  nt1->GetStats([&](std::unique_ptr<EmulatedNetworkStats> st) {
+    EXPECT_EQ(st->PacketsSent(), 2000l);
+    EXPECT_EQ(st->BytesSent().bytes(), single_packet_size * 2000l);
+    EXPECT_THAT(st->LocalAddresses(),
                 ElementsAreArray({alice_endpoint->GetPeerLocalAddress()}));
-    EXPECT_EQ(st.PacketsReceived(), 2000l);
-    EXPECT_EQ(st.BytesReceived().bytes(), single_packet_size * 2000l);
-    EXPECT_EQ(st.PacketsDropped(), 0l);
-    EXPECT_EQ(st.BytesDropped().bytes(), 0l);
+    EXPECT_EQ(st->PacketsReceived(), 2000l);
+    EXPECT_EQ(st->BytesReceived().bytes(), single_packet_size * 2000l);
+    EXPECT_EQ(st->PacketsDropped(), 0l);
+    EXPECT_EQ(st->BytesDropped().bytes(), 0l);
 
-    EXPECT_EQ(st.incoming_stats_per_source[bob_endpoint->GetPeerLocalAddress()]
-                  .packets_received,
-              2000l);
-    EXPECT_EQ(st.incoming_stats_per_source[bob_endpoint->GetPeerLocalAddress()]
+    std::map<rtc::IPAddress, 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(),
               single_packet_size * 2000l);
-    EXPECT_EQ(st.incoming_stats_per_source[bob_endpoint->GetPeerLocalAddress()]
-                  .packets_dropped,
+    EXPECT_EQ(source_st.at(bob_endpoint->GetPeerLocalAddress()).packets_dropped,
               0l);
-    EXPECT_EQ(st.incoming_stats_per_source[bob_endpoint->GetPeerLocalAddress()]
-                  .bytes_dropped.bytes(),
-              0l);
+    EXPECT_EQ(
+        source_st.at(bob_endpoint->GetPeerLocalAddress()).bytes_dropped.bytes(),
+        0l);
     received_stats_count++;
   });
-  nt2->GetStats([&](EmulatedNetworkStats st) {
-    EXPECT_EQ(st.packets_sent, 2000l);
-    EXPECT_EQ(st.bytes_sent.bytes(), single_packet_size * 2000l);
-    EXPECT_THAT(st.local_addresses,
+  nt2->GetStats([&](std::unique_ptr<EmulatedNetworkStats> st) {
+    EXPECT_EQ(st->PacketsSent(), 2000l);
+    EXPECT_EQ(st->BytesSent().bytes(), single_packet_size * 2000l);
+    EXPECT_THAT(st->LocalAddresses(),
                 ElementsAreArray({bob_endpoint->GetPeerLocalAddress()}));
-    EXPECT_EQ(st.PacketsReceived(), 2000l);
-    EXPECT_EQ(st.BytesReceived().bytes(), single_packet_size * 2000l);
-    EXPECT_EQ(st.PacketsDropped(), 0l);
-    EXPECT_EQ(st.BytesDropped().bytes(), 0l);
-    EXPECT_GT(st.FirstReceivedPacketSize(), DataSize::Zero());
-    EXPECT_TRUE(st.FirstPacketReceivedTime().IsFinite());
-    EXPECT_TRUE(st.LastPacketReceivedTime().IsFinite());
+    EXPECT_EQ(st->PacketsReceived(), 2000l);
+    EXPECT_EQ(st->BytesReceived().bytes(), single_packet_size * 2000l);
+    EXPECT_EQ(st->PacketsDropped(), 0l);
+    EXPECT_EQ(st->BytesDropped().bytes(), 0l);
+    EXPECT_GT(st->FirstReceivedPacketSize(), DataSize::Zero());
+    EXPECT_TRUE(st->FirstPacketReceivedTime().IsFinite());
+    EXPECT_TRUE(st->LastPacketReceivedTime().IsFinite());
 
+    std::map<rtc::IPAddress, EmulatedNetworkIncomingStats> source_st =
+        st->IncomingStatsPerSource();
+    ASSERT_EQ(source_st.size(), 1lu);
     EXPECT_EQ(
-        st.incoming_stats_per_source[alice_endpoint->GetPeerLocalAddress()]
-            .packets_received,
+        source_st.at(alice_endpoint->GetPeerLocalAddress()).packets_received,
         2000l);
+    EXPECT_EQ(source_st.at(alice_endpoint->GetPeerLocalAddress())
+                  .bytes_received.bytes(),
+              single_packet_size * 2000l);
     EXPECT_EQ(
-        st.incoming_stats_per_source[alice_endpoint->GetPeerLocalAddress()]
-            .bytes_received.bytes(),
-        single_packet_size * 2000l);
-    EXPECT_EQ(
-        st.incoming_stats_per_source[alice_endpoint->GetPeerLocalAddress()]
-            .packets_dropped,
+        source_st.at(alice_endpoint->GetPeerLocalAddress()).packets_dropped,
         0l);
-    EXPECT_EQ(
-        st.incoming_stats_per_source[alice_endpoint->GetPeerLocalAddress()]
-            .bytes_dropped.bytes(),
-        0l);
+    EXPECT_EQ(source_st.at(alice_endpoint->GetPeerLocalAddress())
+                  .bytes_dropped.bytes(),
+              0l);
     received_stats_count++;
   });
   ASSERT_EQ_SIMULATED_WAIT(received_stats_count.load(), 2,
@@ -363,14 +364,14 @@
   }
 
   std::atomic<int> received_stats_count{0};
-  nt1->GetStats([&](EmulatedNetworkStats st) {
-    EXPECT_EQ(st.packets_sent, kNumPacketsSent);
-    EXPECT_EQ(st.bytes_sent.bytes(), kSinglePacketSize * kNumPacketsSent);
+  nt1->GetStats([&](std::unique_ptr<EmulatedNetworkStats> st) {
+    EXPECT_EQ(st->PacketsSent(), kNumPacketsSent);
+    EXPECT_EQ(st->BytesSent().bytes(), kSinglePacketSize * kNumPacketsSent);
 
     const double tolerance = 0.95;  // Accept 5% tolerance for timing.
-    EXPECT_GE(st.last_packet_sent_time - st.first_packet_sent_time,
+    EXPECT_GE(st->LastPacketSentTime() - st->FirstPacketSentTime(),
               (kNumPacketsSent - 1) * kDelay * tolerance);
-    EXPECT_GT(st.AverageSendRate().bps(), 0);
+    EXPECT_GT(st->AverageSendRate().bps(), 0);
     received_stats_count++;
   });
 
diff --git a/test/pc/e2e/network_quality_metrics_reporter.cc b/test/pc/e2e/network_quality_metrics_reporter.cc
index cd6dfb5..2df4529 100644
--- a/test/pc/e2e/network_quality_metrics_reporter.cc
+++ b/test/pc/e2e/network_quality_metrics_reporter.cc
@@ -34,12 +34,13 @@
     const TrackIdStreamInfoMap* /*reporter_helper*/) {
   test_case_name_ = std::string(test_case_name);
   // Check that network stats are clean before test execution.
-  EmulatedNetworkStats alice_stats = PopulateStats(alice_network_);
-  RTC_CHECK_EQ(alice_stats.packets_sent, 0);
-  RTC_CHECK_EQ(alice_stats.PacketsReceived(), 0);
-  EmulatedNetworkStats bob_stats = PopulateStats(bob_network_);
-  RTC_CHECK_EQ(bob_stats.packets_sent, 0);
-  RTC_CHECK_EQ(bob_stats.PacketsReceived(), 0);
+  std::unique_ptr<EmulatedNetworkStats> alice_stats =
+      PopulateStats(alice_network_);
+  RTC_CHECK_EQ(alice_stats->PacketsSent(), 0);
+  RTC_CHECK_EQ(alice_stats->PacketsReceived(), 0);
+  std::unique_ptr<EmulatedNetworkStats> bob_stats = PopulateStats(bob_network_);
+  RTC_CHECK_EQ(bob_stats->PacketsSent(), 0);
+  RTC_CHECK_EQ(bob_stats->PacketsReceived(), 0);
 }
 
 void NetworkQualityMetricsReporter::OnStatsReports(
@@ -69,12 +70,15 @@
 }
 
 void NetworkQualityMetricsReporter::StopAndReportResults() {
-  EmulatedNetworkStats alice_stats = PopulateStats(alice_network_);
-  EmulatedNetworkStats bob_stats = PopulateStats(bob_network_);
-  ReportStats("alice", alice_stats,
-              alice_stats.packets_sent - bob_stats.PacketsReceived());
-  ReportStats("bob", bob_stats,
-              bob_stats.packets_sent - alice_stats.PacketsReceived());
+  std::unique_ptr<EmulatedNetworkStats> alice_stats =
+      PopulateStats(alice_network_);
+  std::unique_ptr<EmulatedNetworkStats> bob_stats = PopulateStats(bob_network_);
+  int64_t alice_packets_loss =
+      alice_stats->PacketsSent() - bob_stats->PacketsReceived();
+  int64_t bob_packets_loss =
+      bob_stats->PacketsSent() - alice_stats->PacketsReceived();
+  ReportStats("alice", std::move(alice_stats), alice_packets_loss);
+  ReportStats("bob", std::move(bob_stats), bob_packets_loss);
 
   if (!webrtc::field_trial::IsEnabled(kUseStandardBytesStats)) {
     RTC_LOG(LS_ERROR)
@@ -87,12 +91,13 @@
   }
 }
 
-EmulatedNetworkStats NetworkQualityMetricsReporter::PopulateStats(
+std::unique_ptr<EmulatedNetworkStats>
+NetworkQualityMetricsReporter::PopulateStats(
     EmulatedNetworkManagerInterface* network) {
   rtc::Event wait;
-  EmulatedNetworkStats stats;
-  network->GetStats([&](const EmulatedNetworkStats& s) {
-    stats = s;
+  std::unique_ptr<EmulatedNetworkStats> stats;
+  network->GetStats([&](std::unique_ptr<EmulatedNetworkStats> s) {
+    stats = std::move(s);
     wait.Set();
   });
   bool stats_received = wait.Wait(kStatsWaitTimeoutMs);
@@ -102,26 +107,26 @@
 
 void NetworkQualityMetricsReporter::ReportStats(
     const std::string& network_label,
-    const EmulatedNetworkStats& stats,
+    std::unique_ptr<EmulatedNetworkStats> stats,
     int64_t packet_loss) {
-  ReportResult("bytes_sent", network_label, stats.bytes_sent.bytes(),
+  ReportResult("bytes_sent", network_label, stats->BytesSent().bytes(),
                "sizeInBytes");
-  ReportResult("packets_sent", network_label, stats.packets_sent, "unitless");
+  ReportResult("packets_sent", network_label, stats->PacketsSent(), "unitless");
   ReportResult(
       "average_send_rate", network_label,
-      stats.packets_sent >= 2 ? stats.AverageSendRate().bytes_per_sec() : 0,
+      stats->PacketsSent() >= 2 ? stats->AverageSendRate().bytes_per_sec() : 0,
       "bytesPerSecond");
-  ReportResult("bytes_dropped", network_label, stats.BytesDropped().bytes(),
+  ReportResult("bytes_dropped", network_label, stats->BytesDropped().bytes(),
                "sizeInBytes");
-  ReportResult("packets_dropped", network_label, stats.PacketsDropped(),
+  ReportResult("packets_dropped", network_label, stats->PacketsDropped(),
                "unitless");
-  ReportResult("bytes_received", network_label, stats.BytesReceived().bytes(),
+  ReportResult("bytes_received", network_label, stats->BytesReceived().bytes(),
                "sizeInBytes");
-  ReportResult("packets_received", network_label, stats.PacketsReceived(),
+  ReportResult("packets_received", network_label, stats->PacketsReceived(),
                "unitless");
   ReportResult("average_receive_rate", network_label,
-               stats.PacketsReceived() >= 2
-                   ? stats.AverageReceiveRate().bytes_per_sec()
+               stats->PacketsReceived() >= 2
+                   ? stats->AverageReceiveRate().bytes_per_sec()
                    : 0,
                "bytesPerSecond");
   ReportResult("sent_packets_loss", network_label, packet_loss, "unitless");
diff --git a/test/pc/e2e/network_quality_metrics_reporter.h b/test/pc/e2e/network_quality_metrics_reporter.h
index 4c81f9d..50c3623 100644
--- a/test/pc/e2e/network_quality_metrics_reporter.h
+++ b/test/pc/e2e/network_quality_metrics_reporter.h
@@ -11,6 +11,7 @@
 #ifndef TEST_PC_E2E_NETWORK_QUALITY_METRICS_REPORTER_H_
 #define TEST_PC_E2E_NETWORK_QUALITY_METRICS_REPORTER_H_
 
+#include <memory>
 #include <string>
 
 #include "absl/strings/string_view.h"
@@ -47,10 +48,10 @@
     DataSize payload_sent = DataSize::Zero();
   };
 
-  static EmulatedNetworkStats PopulateStats(
+  static std::unique_ptr<EmulatedNetworkStats> PopulateStats(
       EmulatedNetworkManagerInterface* network);
   void ReportStats(const std::string& network_label,
-                   const EmulatedNetworkStats& stats,
+                   std::unique_ptr<EmulatedNetworkStats> stats,
                    int64_t packet_loss);
   void ReportPCStats(const std::string& pc_label, const PCStats& stats);
   void ReportResult(const std::string& metric_name,