Add UMA metrics for ICE regathering reasons.

BUG=webrtc:6462
R=deadbeef@webrtc.org

Review URL: https://codereview.webrtc.org/2386783002 .

Cr-Commit-Position: refs/heads/master@{#14531}
diff --git a/webrtc/BUILD.gn b/webrtc/BUILD.gn
index fe8760f..d47cdf9 100644
--- a/webrtc/BUILD.gn
+++ b/webrtc/BUILD.gn
@@ -337,6 +337,7 @@
   rtc_test("rtc_unittests") {
     testonly = true
     sources = [
+      "api/fakemetricsobserver.cc",
       "base/array_view_unittest.cc",
       "base/atomicops_unittest.cc",
       "base/autodetectproxy_unittest.cc",
diff --git a/webrtc/api/fakemetricsobserver.cc b/webrtc/api/fakemetricsobserver.cc
index 71e71b3..1b6265d 100644
--- a/webrtc/api/fakemetricsobserver.cc
+++ b/webrtc/api/fakemetricsobserver.cc
@@ -45,7 +45,9 @@
 int FakeMetricsObserver::GetEnumCounter(PeerConnectionEnumCounterType type,
                                         int counter) const {
   RTC_DCHECK(thread_checker_.CalledOnValidThread());
-  RTC_CHECK(counters_.size() > static_cast<size_t>(type));
+  if (counters_.size() <= static_cast<size_t>(type)) {
+    return 0;
+  }
   const auto& it = counters_[type].find(counter);
   if (it == counters_[type].end()) {
     return 0;
diff --git a/webrtc/api/peerconnection.cc b/webrtc/api/peerconnection.cc
index 144b1c6..7cc5d00 100644
--- a/webrtc/api/peerconnection.cc
+++ b/webrtc/api/peerconnection.cc
@@ -1300,6 +1300,7 @@
 
   // Send information about IPv4/IPv6 status.
   if (uma_observer_ && port_allocator_) {
+    port_allocator_->SetMetricsObserver(uma_observer_);
     if (port_allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_IPV6) {
       uma_observer_->IncrementEnumCounter(
           kEnumCounterAddressFamily, kPeerConnection_IPv6,
diff --git a/webrtc/api/umametrics.h b/webrtc/api/umametrics.h
index 8dbfa22..93c034f 100644
--- a/webrtc/api/umametrics.h
+++ b/webrtc/api/umametrics.h
@@ -33,6 +33,8 @@
   kEnumCounterDataSrtpCipher,
   kEnumCounterDataSslCipher,
   kEnumCounterDtlsHandshakeError,
+  kEnumCounterIceRegathering,
+  kEnumCounterIceRestart,
   kPeerConnectionEnumCounterMax
 };
 
diff --git a/webrtc/api/webrtcsession.h b/webrtc/api/webrtcsession.h
index c62ea99..94f146f 100644
--- a/webrtc/api/webrtcsession.h
+++ b/webrtc/api/webrtcsession.h
@@ -293,6 +293,7 @@
   void set_metrics_observer(
       webrtc::MetricsObserverInterface* metrics_observer) {
     metrics_observer_ = metrics_observer;
+    transport_controller_->SetMetricsObserver(metrics_observer);
   }
 
   // Called when voice_channel_, video_channel_ and data_channel_ are created
diff --git a/webrtc/p2p/base/dtlstransportchannel.h b/webrtc/p2p/base/dtlstransportchannel.h
index a07c605..5ab3dca 100644
--- a/webrtc/p2p/base/dtlstransportchannel.h
+++ b/webrtc/p2p/base/dtlstransportchannel.h
@@ -186,6 +186,10 @@
     channel_->RemoveRemoteCandidate(candidate);
   }
 
+  void SetMetricsObserver(webrtc::MetricsObserverInterface* observer) override {
+    channel_->SetMetricsObserver(observer);
+  }
+
   void SetIceConfig(const IceConfig& config) override {
     channel_->SetIceConfig(config);
   }
diff --git a/webrtc/p2p/base/faketransportcontroller.h b/webrtc/p2p/base/faketransportcontroller.h
index 5d0ceb4..573d571 100644
--- a/webrtc/p2p/base/faketransportcontroller.h
+++ b/webrtc/p2p/base/faketransportcontroller.h
@@ -299,6 +299,9 @@
     return ssl_max_version_;
   }
 
+  void SetMetricsObserver(webrtc::MetricsObserverInterface* observer) override {
+  }
+
  private:
   void NegotiateSrtpCiphers() {
     for (std::vector<int>::const_iterator it1 = srtp_ciphers_.begin();
diff --git a/webrtc/p2p/base/p2ptransportchannel.cc b/webrtc/p2p/base/p2ptransportchannel.cc
index 3f68d6d..6bcf0d7 100644
--- a/webrtc/p2p/base/p2ptransportchannel.cc
+++ b/webrtc/p2p/base/p2ptransportchannel.cc
@@ -13,6 +13,7 @@
 #include <algorithm>
 #include <set>
 
+#include "webrtc/api/peerconnectioninterface.h"
 #include "webrtc/base/common.h"
 #include "webrtc/base/crc32.h"
 #include "webrtc/base/logging.h"
@@ -435,6 +436,11 @@
   return config_;
 }
 
+void P2PTransportChannel::SetMetricsObserver(
+    webrtc::MetricsObserverInterface* observer) {
+  metrics_observer_ = observer;
+}
+
 void P2PTransportChannel::MaybeStartGathering() {
   if (ice_parameters_.ufrag.empty() || ice_parameters_.pwd.empty()) {
     LOG(LS_ERROR) << "Cannot gather candidates because ICE parameters are empty"
@@ -451,6 +457,21 @@
       gathering_state_ = kIceGatheringGathering;
       SignalGatheringState(this);
     }
+
+    if (metrics_observer_ && !allocator_sessions_.empty()) {
+      IceRestartState state;
+      if (writable()) {
+        state = IceRestartState::CONNECTED;
+      } else if (IsGettingPorts()) {
+        state = IceRestartState::CONNECTING;
+      } else {
+        state = IceRestartState::DISCONNECTED;
+      }
+      metrics_observer_->IncrementEnumCounter(
+          webrtc::kEnumCounterIceRestart, static_cast<int>(state),
+          static_cast<int>(IceRestartState::MAX_VALUE));
+    }
+
     // Time for a new allocator.
     std::unique_ptr<PortAllocatorSession> pooled_session =
         allocator_->TakePooledSession(transport_name(), component(),
@@ -473,7 +494,6 @@
       AddAllocatorSession(allocator_->CreateSession(
           transport_name(), component(), ice_parameters_.ufrag,
           ice_parameters_.pwd));
-      LOG(LS_INFO) << "Start getting ports";
       allocator_sessions_.back()->StartGettingPorts();
     }
   }
diff --git a/webrtc/p2p/base/p2ptransportchannel.h b/webrtc/p2p/base/p2ptransportchannel.h
index 7ffa430..672abb7 100644
--- a/webrtc/p2p/base/p2ptransportchannel.h
+++ b/webrtc/p2p/base/p2ptransportchannel.h
@@ -38,6 +38,10 @@
 
 namespace cricket {
 
+// Enum for UMA metrics, used to record whether the channel is
+// connected/connecting/disconnected when ICE restart happens.
+enum class IceRestartState { CONNECTING, CONNECTED, DISCONNECTED, MAX_VALUE };
+
 extern const int WEAK_PING_INTERVAL;
 extern const int STABILIZING_WRITABLE_CONNECTION_PING_INTERVAL;
 extern const int STABLE_WRITABLE_CONNECTION_PING_INTERVAL;
@@ -95,6 +99,7 @@
   // TODO(deadbeef): Use rtc::Optional instead of negative values.
   void SetIceConfig(const IceConfig& config) override;
   const IceConfig& config() const;
+  void SetMetricsObserver(webrtc::MetricsObserverInterface* observer) override;
 
   // From TransportChannel:
   int SendPacket(const char* data,
@@ -394,6 +399,8 @@
   // connection. A zero-value indicates the connection will not be nominated.
   uint32_t nomination_ = 0;
 
+  webrtc::MetricsObserverInterface* metrics_observer_ = nullptr;
+
   RTC_DISALLOW_COPY_AND_ASSIGN(P2PTransportChannel);
 };
 
diff --git a/webrtc/p2p/base/p2ptransportchannel_unittest.cc b/webrtc/p2p/base/p2ptransportchannel_unittest.cc
index 70ef007..7e65b4b 100644
--- a/webrtc/p2p/base/p2ptransportchannel_unittest.cc
+++ b/webrtc/p2p/base/p2ptransportchannel_unittest.cc
@@ -11,6 +11,7 @@
 #include <algorithm>
 #include <memory>
 
+#include "webrtc/api/fakemetricsobserver.h"
 #include "webrtc/p2p/base/fakeportallocator.h"
 #include "webrtc/p2p/base/p2ptransportchannel.h"
 #include "webrtc/p2p/base/testrelayserver.h"
@@ -198,9 +199,15 @@
     ep1_.allocator_.reset(
         CreateBasicPortAllocator(&ep1_.network_manager_, stun_servers,
                                  kTurnUdpIntAddr, rtc::SocketAddress()));
+    ep1_.metrics_observer_ =
+        new rtc::RefCountedObject<webrtc::FakeMetricsObserver>();
+    ep1_.allocator_->SetMetricsObserver(ep1_.metrics_observer_);
     ep2_.allocator_.reset(
         CreateBasicPortAllocator(&ep2_.network_manager_, stun_servers,
                                  kTurnUdpIntAddr, rtc::SocketAddress()));
+    ep2_.metrics_observer_ =
+        new rtc::RefCountedObject<webrtc::FakeMetricsObserver>();
+    ep2_.allocator_->SetMetricsObserver(ep2_.metrics_observer_);
   }
 
  protected:
@@ -298,6 +305,9 @@
     }
 
     rtc::FakeNetworkManager network_manager_;
+    // |metrics_observer_| should outlive |allocator_| as the former may be
+    // used by the latter.
+    rtc::scoped_refptr<webrtc::FakeMetricsObserver> metrics_observer_;
     std::unique_ptr<BasicPortAllocator> allocator_;
     ChannelData cd1_;
     ChannelData cd2_;
@@ -334,6 +344,8 @@
                                       ice_ep1_cd1_ch, ice_ep2_cd1_ch));
     ep2_.cd1_.ch_.reset(CreateChannel(1, ICE_CANDIDATE_COMPONENT_DEFAULT,
                                       ice_ep2_cd1_ch, ice_ep1_cd1_ch));
+    ep1_.cd1_.ch_->SetMetricsObserver(ep1_.metrics_observer_);
+    ep2_.cd1_.ch_->SetMetricsObserver(ep2_.metrics_observer_);
     ep1_.cd1_.ch_->SetIceConfig(ep1_config);
     ep2_.cd1_.ch_->SetIceConfig(ep2_config);
     ep1_.cd1_.ch_->MaybeStartGathering();
@@ -416,6 +428,9 @@
   PortAllocator* GetAllocator(int endpoint) {
     return GetEndpoint(endpoint)->allocator_.get();
   }
+  webrtc::FakeMetricsObserver* GetMetricsObserver(int endpoint) {
+    return GetEndpoint(endpoint)->metrics_observer_;
+  }
   void AddAddress(int endpoint, const SocketAddress& addr) {
     GetEndpoint(endpoint)->network_manager_.AddInterface(addr);
   }
@@ -958,13 +973,12 @@
                           Config config2,
                           int allocator_flags1,
                           int allocator_flags2) {
-    int delay = kMinimumStepDelay;
     ConfigureEndpoint(0, config1);
     SetAllocatorFlags(0, allocator_flags1);
-    SetAllocationStepDelay(0, delay);
+    SetAllocationStepDelay(0, kMinimumStepDelay);
     ConfigureEndpoint(1, config2);
     SetAllocatorFlags(1, allocator_flags2);
-    SetAllocationStepDelay(1, delay);
+    SetAllocationStepDelay(1, kMinimumStepDelay);
 
     set_remote_ice_parameter_source(FROM_SETICEPARAMETERS);
   }
@@ -1172,6 +1186,169 @@
   DestroyChannels();
 }
 
+// Tests that UMAs are recorded when ICE restarts while the channel
+// is disconnected.
+TEST_F(P2PTransportChannelTest, TestUMAIceRestartWhileDisconnected) {
+  rtc::ScopedFakeClock clock;
+  ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+  CreateChannels();
+  EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+                                 ep2_ch1()->receiving() &&
+                                 ep2_ch1()->writable(),
+                             kDefaultTimeout, clock);
+
+  // Drop all packets so that both channels become not writable.
+  fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+  const int kWriteTimeoutDelay = 6000;
+  EXPECT_TRUE_SIMULATED_WAIT(!ep1_ch1()->writable() && !ep2_ch1()->writable(),
+                             kWriteTimeoutDelay, clock);
+
+  ep1_ch1()->SetIceParameters(kIceParams[2]);
+  ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+  ep1_ch1()->MaybeStartGathering();
+  EXPECT_EQ(1, GetMetricsObserver(0)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRestart,
+                   static_cast<int>(IceRestartState::DISCONNECTED)));
+
+  ep2_ch1()->SetIceParameters(kIceParams[3]);
+  ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+  ep2_ch1()->MaybeStartGathering();
+  EXPECT_EQ(1, GetMetricsObserver(1)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRestart,
+                   static_cast<int>(IceRestartState::DISCONNECTED)));
+
+  DestroyChannels();
+}
+
+// Tests that UMAs are recorded when ICE restarts while the channel
+// is connected.
+TEST_F(P2PTransportChannelTest, TestUMAIceRestartWhileConnected) {
+  rtc::ScopedFakeClock clock;
+  ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+  CreateChannels();
+  EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+                                 ep2_ch1()->receiving() &&
+                                 ep2_ch1()->writable(),
+                             kDefaultTimeout, clock);
+
+  ep1_ch1()->SetIceParameters(kIceParams[2]);
+  ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+  ep1_ch1()->MaybeStartGathering();
+  EXPECT_EQ(1, GetMetricsObserver(0)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRestart,
+                   static_cast<int>(IceRestartState::CONNECTED)));
+
+  ep2_ch1()->SetIceParameters(kIceParams[3]);
+  ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+  ep2_ch1()->MaybeStartGathering();
+  EXPECT_EQ(1, GetMetricsObserver(1)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRestart,
+                   static_cast<int>(IceRestartState::CONNECTED)));
+
+  DestroyChannels();
+}
+
+// Tests that UMAs are recorded when ICE restarts while the channel
+// is connecting.
+TEST_F(P2PTransportChannelTest, TestUMAIceRestartWhileConnecting) {
+  rtc::ScopedFakeClock clock;
+  ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+  // Create the channels without waiting for them to become connected.
+  CreateChannels();
+
+  ep1_ch1()->SetIceParameters(kIceParams[2]);
+  ep1_ch1()->SetRemoteIceParameters(kIceParams[3]);
+  ep1_ch1()->MaybeStartGathering();
+  EXPECT_EQ(1, GetMetricsObserver(0)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRestart,
+                   static_cast<int>(IceRestartState::CONNECTING)));
+
+  ep2_ch1()->SetIceParameters(kIceParams[3]);
+  ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+  ep2_ch1()->MaybeStartGathering();
+  EXPECT_EQ(1, GetMetricsObserver(1)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRestart,
+                   static_cast<int>(IceRestartState::CONNECTING)));
+
+  DestroyChannels();
+}
+
+// Tests that a UMA on ICE regathering is recorded when there is a network
+// change if and only if continual gathering is enabled.
+TEST_F(P2PTransportChannelTest,
+       TestIceRegatheringReasonContinualGatheringByNetworkChange) {
+  rtc::ScopedFakeClock clock;
+  ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+  // ep1 gathers continually but ep2 does not.
+  IceConfig continual_gathering_config =
+      CreateIceConfig(1000, GATHER_CONTINUALLY);
+  IceConfig default_config;
+  CreateChannels(continual_gathering_config, default_config);
+
+  EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+                                 ep2_ch1()->receiving() &&
+                                 ep2_ch1()->writable(),
+                             kDefaultTimeout, clock);
+
+  // Adding address in ep1 will trigger continual gathering.
+  AddAddress(0, kAlternateAddrs[0]);
+  EXPECT_EQ_SIMULATED_WAIT(
+      1, GetMetricsObserver(0)->GetEnumCounter(
+             webrtc::kEnumCounterIceRegathering,
+             static_cast<int>(IceRegatheringReason::NETWORK_CHANGE)),
+      kDefaultTimeout, clock);
+
+  ep2_ch1()->SetIceParameters(kIceParams[3]);
+  ep2_ch1()->SetRemoteIceParameters(kIceParams[2]);
+  ep2_ch1()->MaybeStartGathering();
+
+  AddAddress(1, kAlternateAddrs[1]);
+  SIMULATED_WAIT(false, kDefaultTimeout, clock);
+  // ep2 has not enabled continual gathering.
+  EXPECT_EQ(0, GetMetricsObserver(1)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRegathering,
+                   static_cast<int>(IceRegatheringReason::NETWORK_CHANGE)));
+
+  DestroyChannels();
+}
+
+// Tests that a UMA on ICE regathering is recorded when there is a network
+// failure if and only if continual gathering is enabled.
+TEST_F(P2PTransportChannelTest,
+       TestIceRegatheringReasonContinualGatheringByNetworkFailure) {
+  rtc::ScopedFakeClock clock;
+  ConfigureEndpoints(OPEN, OPEN, kOnlyLocalPorts, kOnlyLocalPorts);
+
+  // ep1 gathers continually but ep2 does not.
+  IceConfig config1 = CreateIceConfig(1000, GATHER_CONTINUALLY);
+  config1.regather_on_failed_networks_interval = rtc::Optional<int>(2000);
+  IceConfig config2;
+  config2.regather_on_failed_networks_interval = rtc::Optional<int>(2000);
+  CreateChannels(config1, config2);
+
+  EXPECT_TRUE_SIMULATED_WAIT(ep1_ch1()->receiving() && ep1_ch1()->writable() &&
+                                 ep2_ch1()->receiving() &&
+                                 ep2_ch1()->writable(),
+                             kDefaultTimeout, clock);
+
+  fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, kPublicAddrs[0]);
+  // Timeout value such that all connections are deleted.
+  const int kNetworkFailureTimeout = 35000;
+  SIMULATED_WAIT(false, kNetworkFailureTimeout, clock);
+  EXPECT_LE(1, GetMetricsObserver(0)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRegathering,
+                   static_cast<int>(IceRegatheringReason::NETWORK_FAILURE)));
+  EXPECT_EQ(0, GetMetricsObserver(1)->GetEnumCounter(
+                   webrtc::kEnumCounterIceRegathering,
+                   static_cast<int>(IceRegatheringReason::NETWORK_FAILURE)));
+
+  DestroyChannels();
+}
+
 // Test that we properly create a connection on a STUN ping from unknown address
 // when the signaling is slow.
 TEST_F(P2PTransportChannelTest, PeerReflexiveCandidateBeforeSignaling) {
diff --git a/webrtc/p2p/base/portallocator.h b/webrtc/p2p/base/portallocator.h
index 8befbad..3c81068 100644
--- a/webrtc/p2p/base/portallocator.h
+++ b/webrtc/p2p/base/portallocator.h
@@ -23,6 +23,10 @@
 #include "webrtc/base/sigslot.h"
 #include "webrtc/base/thread.h"
 
+namespace webrtc {
+class MetricsObserverInterface;
+}
+
 namespace cricket {
 
 // PortAllocator is responsible for allocating Port types for a given
@@ -72,6 +76,9 @@
   PORTALLOCATOR_DISABLE_COSTLY_NETWORKS = 0x2000,
 };
 
+// Defines various reasons that have caused ICE regathering.
+enum class IceRegatheringReason { NETWORK_CHANGE, NETWORK_FAILURE, MAX_VALUE };
+
 const uint32_t kDefaultPortAllocatorFlags = 0;
 
 const uint32_t kDefaultStepDelay = 1000;  // 1 sec step delay.
@@ -216,6 +223,9 @@
       SignalCandidatesRemoved;
   sigslot::signal1<PortAllocatorSession*> SignalCandidatesAllocationDone;
 
+  sigslot::signal2<PortAllocatorSession*, IceRegatheringReason>
+      SignalIceRegathering;
+
   virtual uint32_t generation() { return generation_; }
   virtual void set_generation(uint32_t generation) { generation_ = generation; }
   sigslot::signal1<PortAllocatorSession*> SignalDestroyed;
@@ -370,6 +380,10 @@
   const std::string& origin() const { return origin_; }
   void set_origin(const std::string& origin) { origin_ = origin; }
 
+  void SetMetricsObserver(webrtc::MetricsObserverInterface* observer) {
+    metrics_observer_ = observer;
+  }
+
  protected:
   virtual PortAllocatorSession* CreateSessionInternal(
       const std::string& content_name,
@@ -377,6 +391,14 @@
       const std::string& ice_ufrag,
       const std::string& ice_pwd) = 0;
 
+  webrtc::MetricsObserverInterface* metrics_observer() {
+    return metrics_observer_;
+  }
+
+  const std::deque<std::unique_ptr<PortAllocatorSession>>& pooled_sessions() {
+    return pooled_sessions_;
+  }
+
   uint32_t flags_;
   std::string agent_;
   rtc::ProxyInfo proxy_;
@@ -397,6 +419,8 @@
   int allocated_pooled_session_count_ = 0;
   std::deque<std::unique_ptr<PortAllocatorSession>> pooled_sessions_;
   bool prune_turn_ports_ = false;
+
+  webrtc::MetricsObserverInterface* metrics_observer_ = nullptr;
 };
 
 }  // namespace cricket
diff --git a/webrtc/p2p/base/transportchannelimpl.h b/webrtc/p2p/base/transportchannelimpl.h
index 0c7fa50..1d43e54 100644
--- a/webrtc/p2p/base/transportchannelimpl.h
+++ b/webrtc/p2p/base/transportchannelimpl.h
@@ -18,6 +18,10 @@
 
 namespace buzz { class XmlElement; }
 
+namespace webrtc {
+class MetricsObserverInterface;
+}
+
 namespace cricket {
 
 class Candidate;
@@ -74,6 +78,9 @@
   // occurred.
   virtual void MaybeStartGathering() = 0;
 
+  virtual void SetMetricsObserver(
+      webrtc::MetricsObserverInterface* observer) = 0;
+
   sigslot::signal1<TransportChannelImpl*> SignalGatheringState;
 
   // Handles sending and receiving of candidates.  The Transport
diff --git a/webrtc/p2p/base/transportcontroller.cc b/webrtc/p2p/base/transportcontroller.cc
index bd6ee66..1dc7ca7 100644
--- a/webrtc/p2p/base/transportcontroller.cc
+++ b/webrtc/p2p/base/transportcontroller.cc
@@ -187,6 +187,7 @@
   // Need to create a new channel.
   Transport* transport = GetOrCreateTransport_n(transport_name);
   TransportChannelImpl* channel = transport->CreateChannel(component);
+  channel->SetMetricsObserver(metrics_observer_);
   channel->SignalWritableState.connect(
       this, &TransportController::OnChannelWritableState_n);
   channel->SignalReceivingState.connect(
@@ -704,4 +705,12 @@
   SignalDtlsHandshakeError(error);
 }
 
+void TransportController::SetMetricsObserver(
+    webrtc::MetricsObserverInterface* metrics_observer) {
+  metrics_observer_ = metrics_observer;
+  for (auto channel : channels_) {
+    channel->SetMetricsObserver(metrics_observer);
+  }
+}
+
 }  // namespace cricket
diff --git a/webrtc/p2p/base/transportcontroller.h b/webrtc/p2p/base/transportcontroller.h
index b5d9ac7..5f3fccc 100644
--- a/webrtc/p2p/base/transportcontroller.h
+++ b/webrtc/p2p/base/transportcontroller.h
@@ -25,6 +25,9 @@
 namespace rtc {
 class Thread;
 }
+namespace webrtc {
+class MetricsObserverInterface;
+}
 
 namespace cricket {
 
@@ -129,6 +132,8 @@
 
   sigslot::signal1<rtc::SSLHandshakeError> SignalDtlsHandshakeError;
 
+  void SetMetricsObserver(webrtc::MetricsObserverInterface* metrics_observer);
+
  protected:
   // Protected and virtual so we can override it in unit tests.
   virtual Transport* CreateTransport_n(const std::string& transport_name);
@@ -238,6 +243,8 @@
   rtc::AsyncInvoker invoker_;
   // True if QUIC is used instead of DTLS.
   bool quic_ = false;
+
+  webrtc::MetricsObserverInterface* metrics_observer_ = nullptr;
 };
 
 }  // namespace cricket
diff --git a/webrtc/p2p/client/basicportallocator.cc b/webrtc/p2p/client/basicportallocator.cc
index dba68f1..dbac0d3 100644
--- a/webrtc/p2p/client/basicportallocator.cc
+++ b/webrtc/p2p/client/basicportallocator.cc
@@ -14,6 +14,7 @@
 #include <string>
 #include <vector>
 
+#include "webrtc/api/peerconnectioninterface.h"
 #include "webrtc/p2p/base/basicpacketsocketfactory.h"
 #include "webrtc/p2p/base/common.h"
 #include "webrtc/p2p/base/port.h"
@@ -150,14 +151,35 @@
   allow_tcp_listen_ = true;
 }
 
+void BasicPortAllocator::OnIceRegathering(PortAllocatorSession* session,
+                                          IceRegatheringReason reason) {
+  if (!metrics_observer()) {
+    return;
+  }
+  // If the session has not been taken by an active channel, do not report the
+  // metric.
+  for (auto& allocator_session : pooled_sessions()) {
+    if (allocator_session.get() == session) {
+      return;
+    }
+  }
+
+  metrics_observer()->IncrementEnumCounter(
+      webrtc::kEnumCounterIceRegathering, static_cast<int>(reason),
+      static_cast<int>(IceRegatheringReason::MAX_VALUE));
+}
+
 BasicPortAllocator::~BasicPortAllocator() {
 }
 
 PortAllocatorSession* BasicPortAllocator::CreateSessionInternal(
     const std::string& content_name, int component,
     const std::string& ice_ufrag, const std::string& ice_pwd) {
-  return new BasicPortAllocatorSession(
+  PortAllocatorSession* session = new BasicPortAllocatorSession(
       this, content_name, component, ice_ufrag, ice_pwd);
+  session->SignalIceRegathering.connect(this,
+                                        &BasicPortAllocator::OnIceRegathering);
+  return session;
 }
 
 void BasicPortAllocator::AddTurnServer(const RelayServerConfig& turn_server) {
@@ -247,7 +269,7 @@
 
   network_thread_->Post(RTC_FROM_HERE, this, MSG_CONFIG_START);
 
-  LOG(LS_INFO) << "Pruning turn ports "
+  LOG(LS_INFO) << "Start getting ports with prune_turn_ports "
                << (prune_turn_ports_ ? "enabled" : "disabled");
 }
 
@@ -302,6 +324,8 @@
     return;
   }
 
+  LOG(LS_INFO) << "Regather candidates on failed networks";
+
   // Mark a sequence as "network failed" if its network is in the list of failed
   // networks, so that it won't be considered as equivalent when the session
   // regathers ports and candidates.
@@ -321,7 +345,9 @@
     PrunePortsAndRemoveCandidates(ports_to_prune);
   }
 
-  if (allocation_started_ && network_manager_started_) {
+  if (allocation_started_ && network_manager_started_ && !IsStopped()) {
+    SignalIceRegathering(this, IceRegatheringReason::NETWORK_FAILURE);
+
     DoAllocate();
   }
 }
@@ -501,7 +527,7 @@
 }
 
 void BasicPortAllocatorSession::OnAllocate() {
-  if (network_manager_started_)
+  if (network_manager_started_ && !IsStopped())
     DoAllocate();
 
   allocation_started_ = true;
@@ -553,10 +579,6 @@
 void BasicPortAllocatorSession::DoAllocate() {
   bool done_signal_needed = false;
   std::vector<rtc::Network*> networks = GetNetworks();
-
-  if (IsStopped()) {
-    return;
-  }
   if (networks.empty()) {
     LOG(LS_WARNING) << "Machine has no networks; no ports will be allocated";
     done_signal_needed = true;
@@ -627,12 +649,18 @@
     PrunePortsAndRemoveCandidates(ports_to_prune);
   }
 
+  if (allocation_started_ && !IsStopped()) {
+    if (network_manager_started_) {
+      // If the network manager has started, it must be regathering.
+      SignalIceRegathering(this, IceRegatheringReason::NETWORK_CHANGE);
+    }
+    DoAllocate();
+  }
+
   if (!network_manager_started_) {
-    LOG(LS_INFO) << "Network manager is started";
+    LOG(LS_INFO) << "Network manager has started";
     network_manager_started_ = true;
   }
-  if (allocation_started_)
-    DoAllocate();
 }
 
 void BasicPortAllocatorSession::DisableEquivalentPhases(
diff --git a/webrtc/p2p/client/basicportallocator.h b/webrtc/p2p/client/basicportallocator.h
index 3044559..d705fc5 100644
--- a/webrtc/p2p/client/basicportallocator.h
+++ b/webrtc/p2p/client/basicportallocator.h
@@ -65,6 +65,9 @@
  private:
   void Construct();
 
+  void OnIceRegathering(PortAllocatorSession* session,
+                        IceRegatheringReason reason);
+
   rtc::NetworkManager* network_manager_;
   rtc::PacketSocketFactory* socket_factory_;
   bool allow_tcp_listen_;
diff --git a/webrtc/p2p/quic/quictransportchannel.h b/webrtc/p2p/quic/quictransportchannel.h
index 22b69b7..f5a7910 100644
--- a/webrtc/p2p/quic/quictransportchannel.h
+++ b/webrtc/p2p/quic/quictransportchannel.h
@@ -199,6 +199,10 @@
   void OnProofVerifyDetailsAvailable(
       const net::ProofVerifyDetails& verify_details) override;
 
+  void SetMetricsObserver(webrtc::MetricsObserverInterface* observer) override {
+    channel_->SetMetricsObserver(observer);
+  }
+
   // Returns true if |quic_| has queued data which wasn't written due
   // to |channel_| being write blocked.
   bool HasDataToWrite() const;