With IPv6 enabled, it's important to know whether IPv6 is really used or not. BestConnection is tracked for this purpose. Also added a test case to verify the end to end behavior.

BUG=411086
R=pthatcher@webrtc.org

Review URL: https://webrtc-codereview.appspot.com/30919005

git-svn-id: http://webrtc.googlecode.com/svn/trunk@7814 4adac7df-926f-26a2-2b94-8c16560cd09d
diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc
index 64ddcad..99a091d 100644
--- a/talk/app/webrtc/peerconnection.cc
+++ b/talk/app/webrtc/peerconnection.cc
@@ -682,6 +682,11 @@
 
 void PeerConnection::RegisterUMAObserver(UMAObserver* observer) {
   uma_observer_ = observer;
+
+  if (session_) {
+    session_->set_metrics_observer(uma_observer_);
+  }
+
   // Send information about IPv4/IPv6 status.
   if (uma_observer_ && port_allocator_) {
     if (port_allocator_->flags() & cricket::PORTALLOCATOR_ENABLE_IPV6) {
diff --git a/talk/app/webrtc/peerconnectioninterface.h b/talk/app/webrtc/peerconnectioninterface.h
index 73a4812..e751f22 100644
--- a/talk/app/webrtc/peerconnectioninterface.h
+++ b/talk/app/webrtc/peerconnectioninterface.h
@@ -130,16 +130,18 @@
   virtual ~StatsObserver() {}
 };
 
-class UMAObserver : public rtc::RefCountInterface {
+class MetricsObserverInterface : public rtc::RefCountInterface {
  public:
-  virtual void IncrementCounter(PeerConnectionUMAMetricsCounter type) = 0;
-  virtual void AddHistogramSample(PeerConnectionUMAMetricsName type,
+  virtual void IncrementCounter(PeerConnectionMetricsCounter type) = 0;
+  virtual void AddHistogramSample(PeerConnectionMetricsName type,
                                   int value) = 0;
 
  protected:
-  virtual ~UMAObserver() {}
+  virtual ~MetricsObserverInterface() {}
 };
 
+typedef MetricsObserverInterface UMAObserver;
+
 class PeerConnectionInterface : public rtc::RefCountInterface {
  public:
   // See http://dev.w3.org/2011/webrtc/editor/webrtc.html#state-definitions .
diff --git a/talk/app/webrtc/umametrics.h b/talk/app/webrtc/umametrics.h
old mode 100755
new mode 100644
index 81f7bac..bbff069
--- a/talk/app/webrtc/umametrics.h
+++ b/talk/app/webrtc/umametrics.h
@@ -35,25 +35,42 @@
 // Currently this contains information related to WebRTC network/transport
 // information.
 
+// The difference between PeerConnectionMetricsCounter and
+// PeerConnectionMetricsName is that the "Counter" is only counting the
+// occurrences of events, while "Name" has a value associated with it which is
+// used to form a histogram.
+
 // This enum is backed by Chromium's histograms.xml,
 // chromium/src/tools/metrics/histograms/histograms.xml
 // Existing values cannot be re-ordered and new enums must be added
 // before kBoundary.
-enum PeerConnectionUMAMetricsCounter {
+enum PeerConnectionMetricsCounter {
   kPeerConnection_IPv4,
   kPeerConnection_IPv6,
   kBestConnections_IPv4,
   kBestConnections_IPv6,
-  kBoundary,
+  kPeerConnectionMetricsCounter_Max,
 };
 
+// TODO(guoweis): Keep previous name here until all references are renamed.
+#define kBoundary kPeerConnectionMetricsCounter_Max
+
+// TODO(guoweis): Keep previous name here until all references are renamed.
+typedef PeerConnectionMetricsCounter PeerConnectionUMAMetricsCounter;
+
 // This enum defines types for UMA samples, which will have a range.
-enum PeerConnectionUMAMetricsName {
-  kNetworkInterfaces_IPv4,   // Number of IPv4 interfaces.
-  kNetworkInterfaces_IPv6,   // Number of IPv6 interfaces.
-  kTimeToConnect,  // In milliseconds.
+enum PeerConnectionMetricsName {
+  kNetworkInterfaces_IPv4,  // Number of IPv4 interfaces.
+  kNetworkInterfaces_IPv6,  // Number of IPv6 interfaces.
+  kTimeToConnect,           // In milliseconds.
+  kLocalCandidates_IPv4,    // Number of IPv4 local candidates.
+  kLocalCandidates_IPv6,    // Number of IPv6 local candidates.
+  kPeerConnectionMetricsName_Max
 };
 
+// TODO(guoweis): Keep previous name here until all references are renamed.
+typedef PeerConnectionMetricsName PeerConnectionUMAMetricsName;
+
 }  // namespace webrtc
 
 #endif  // TALK_APP_WEBRTC_UMA6METRICS_H_
diff --git a/talk/app/webrtc/webrtcsession.cc b/talk/app/webrtc/webrtcsession.cc
index 402edc2..c124237 100644
--- a/talk/app/webrtc/webrtcsession.cc
+++ b/talk/app/webrtc/webrtcsession.cc
@@ -461,16 +461,17 @@
   bool ice_restart_;
 };
 
-WebRtcSession::WebRtcSession(
-    cricket::ChannelManager* channel_manager,
-    rtc::Thread* signaling_thread,
-    rtc::Thread* worker_thread,
-    cricket::PortAllocator* port_allocator,
-    MediaStreamSignaling* mediastream_signaling)
-    : cricket::BaseSession(signaling_thread, worker_thread, port_allocator,
-                           rtc::ToString(rtc::CreateRandomId64() &
-                                               LLONG_MAX),
-                           cricket::NS_JINGLE_RTP, false),
+WebRtcSession::WebRtcSession(cricket::ChannelManager* channel_manager,
+                             rtc::Thread* signaling_thread,
+                             rtc::Thread* worker_thread,
+                             cricket::PortAllocator* port_allocator,
+                             MediaStreamSignaling* mediastream_signaling)
+    : cricket::BaseSession(signaling_thread,
+                           worker_thread,
+                           port_allocator,
+                           rtc::ToString(rtc::CreateRandomId64() & LLONG_MAX),
+                           cricket::NS_JINGLE_RTP,
+                           false),
       // RFC 3264: The numeric value of the session id and version in the
       // o line MUST be representable with a "64 bit signed integer".
       // Due to this constraint session id |sid_| is max limited to LLONG_MAX.
@@ -481,7 +482,8 @@
       older_version_remote_peer_(false),
       dtls_enabled_(false),
       data_channel_type_(cricket::DCT_NONE),
-      ice_restart_latch_(new IceRestartAnswerLatch) {
+      ice_restart_latch_(new IceRestartAnswerLatch),
+      metrics_observer_(NULL) {
 }
 
 WebRtcSession::~WebRtcSession() {
@@ -1299,7 +1301,12 @@
 
 void WebRtcSession::OnTransportCompleted(cricket::Transport* transport) {
   ASSERT(signaling_thread()->IsCurrent());
+  PeerConnectionInterface::IceConnectionState old_state = ice_connection_state_;
   SetIceConnectionState(PeerConnectionInterface::kIceConnectionCompleted);
+  // Only report once when Ice connection is completed.
+  if (old_state != PeerConnectionInterface::kIceConnectionCompleted) {
+    ReportBestConnectionState(transport);
+  }
 }
 
 void WebRtcSession::OnTransportFailed(cricket::Transport* transport) {
@@ -1740,4 +1747,38 @@
       transport_proxy->remote_description_set();
 }
 
+// Walk through the ConnectionInfos to gather best connection usage
+// for IPv4 and IPv6.
+void WebRtcSession::ReportBestConnectionState(cricket::Transport* transport) {
+  if (!metrics_observer_) {
+    return;
+  }
+
+  cricket::TransportStats stats;
+  if (!transport->GetStats(&stats)) {
+    return;
+  }
+
+  for (cricket::TransportChannelStatsList::const_iterator it =
+         stats.channel_stats.begin();
+       it != stats.channel_stats.end(); ++it) {
+    for (cricket::ConnectionInfos::const_iterator it_info =
+           it->connection_infos.begin();
+         it_info != it->connection_infos.end(); ++it_info) {
+      if (!it_info->best_connection) {
+        continue;
+      }
+      if (it_info->local_candidate.address().family() == AF_INET) {
+        metrics_observer_->IncrementCounter(kBestConnections_IPv4);
+      } else if (it_info->local_candidate.address().family() ==
+                 AF_INET6) {
+        metrics_observer_->IncrementCounter(kBestConnections_IPv6);
+      } else {
+        ASSERT(false);
+      }
+      return;
+    }
+  }
+}
+
 }  // namespace webrtc
diff --git a/talk/app/webrtc/webrtcsession.h b/talk/app/webrtc/webrtcsession.h
index 25e9646..3db2682 100644
--- a/talk/app/webrtc/webrtcsession.h
+++ b/talk/app/webrtc/webrtcsession.h
@@ -225,6 +225,11 @@
   // For unit test.
   bool waiting_for_identity() const;
 
+  void set_metrics_observer(
+      webrtc::MetricsObserverInterface* metrics_observer) {
+    metrics_observer_ = metrics_observer;
+  }
+
  private:
   // Indicates the type of SessionDescription in a call to SetLocalDescription
   // and SetRemoteDescription.
@@ -323,6 +328,10 @@
 
   std::string GetSessionErrorMsg();
 
+  // Invoked when OnTransportCompleted is signaled to gather the usage
+  // of IPv4/IPv6 as best connection.
+  void ReportBestConnectionState(cricket::Transport* transport);
+
   rtc::scoped_ptr<cricket::VoiceChannel> voice_channel_;
   rtc::scoped_ptr<cricket::VideoChannel> video_channel_;
   rtc::scoped_ptr<cricket::DataChannel> data_channel_;
@@ -357,6 +366,7 @@
   // Member variables for caching global options.
   cricket::AudioOptions audio_options_;
   cricket::VideoOptions video_options_;
+  MetricsObserverInterface* metrics_observer_;
 
   DISALLOW_COPY_AND_ASSIGN(WebRtcSession);
 };
diff --git a/talk/app/webrtc/webrtcsession_unittest.cc b/talk/app/webrtc/webrtcsession_unittest.cc
index e79001a..ba76ff3 100644
--- a/talk/app/webrtc/webrtcsession_unittest.cc
+++ b/talk/app/webrtc/webrtcsession_unittest.cc
@@ -104,6 +104,8 @@
 
 static const int kClientAddrPort = 0;
 static const char kClientAddrHost1[] = "11.11.11.11";
+static const char kClientIPv6AddrHost1[] =
+    "2620:0:aaaa:bbbb:cccc:dddd:eeee:ffff";
 static const char kClientAddrHost2[] = "22.22.22.22";
 static const char kStunAddrHost[] = "99.99.99.1";
 static const SocketAddress kTurnUdpIntAddr("99.99.99.4", 3478);
@@ -142,6 +144,34 @@
                              tmp.c_str(), tmp.length(), message);
 }
 
+class FakeMetricsObserver : public webrtc::MetricsObserverInterface {
+ public:
+  FakeMetricsObserver() { Reset(); }
+  void Reset() {
+    memset(peer_connection_metrics_counters_, 0,
+           sizeof(peer_connection_metrics_counters_));
+    memset(peer_connection_metrics_name_, 0,
+           sizeof(peer_connection_metrics_name_));
+  }
+
+  virtual void IncrementCounter(
+      webrtc::PeerConnectionMetricsCounter type) OVERRIDE {
+    peer_connection_metrics_counters_[type]++;
+  }
+  virtual void AddHistogramSample(webrtc::PeerConnectionMetricsName type,
+                                  int value) OVERRIDE {
+    ASSERT(peer_connection_metrics_name_[type] == 0);
+    peer_connection_metrics_name_[type] = value;
+  }
+
+  int peer_connection_metrics_counters_
+      [webrtc::kPeerConnectionMetricsCounter_Max];
+  int peer_connection_metrics_name_[webrtc::kPeerConnectionMetricsCounter_Max];
+
+  virtual int AddRef() OVERRIDE { return 1; }
+  virtual int Release() OVERRIDE { return 1; }
+};
+
 class MockIceObserver : public webrtc::IceObserver {
  public:
   MockIceObserver()
@@ -353,6 +383,7 @@
 
     EXPECT_TRUE(session_->Initialize(options_, constraints_.get(),
                                      identity_service, ice_type_));
+    session_->set_metrics_observer(&metrics_observer_);
   }
 
   void InitWithDtmfCodec() {
@@ -919,6 +950,90 @@
     EXPECT_EQ(can, session_->CanInsertDtmf(kAudioTrack1));
   }
 
+  // Helper class to configure loopback network and verify Best
+  // Connection using right IP protocol for TestLoopbackCall
+  // method. LoopbackNetworkManager applies firewall rules to block
+  // all ping traffic once ICE completed, and remove them to observe
+  // ICE reconnected again. This LoopbackNetworkConfiguration struct
+  // verifies the best connection is using the right IP protocol after
+  // initial ICE convergences.
+
+  class LoopbackNetworkConfiguration {
+  public:
+    LoopbackNetworkConfiguration()
+        : test_ipv6_network_(false),
+          test_extra_ipv4_network_(false),
+          best_connection_after_initial_ice_converged_(1, 0) {}
+
+    // Used to track the expected best connection count in each IP protocol.
+    struct ExpectedBestConnection {
+      ExpectedBestConnection(int ipv4_count, int ipv6_count)
+          : ipv4_count_(ipv4_count),
+            ipv6_count_(ipv6_count) {}
+
+      int ipv4_count_;
+      int ipv6_count_;
+    };
+
+    bool test_ipv6_network_;
+    bool test_extra_ipv4_network_;
+    ExpectedBestConnection best_connection_after_initial_ice_converged_;
+
+    void VerifyBestConnectionAfterIceConverge(
+        const FakeMetricsObserver& metrics_observer) const {
+      Verify(metrics_observer, best_connection_after_initial_ice_converged_);
+    }
+
+   private:
+    void Verify(const FakeMetricsObserver& metrics_observer,
+                const ExpectedBestConnection& expected) const {
+      EXPECT_EQ(
+          metrics_observer
+              .peer_connection_metrics_counters_[webrtc::kBestConnections_IPv4],
+          expected.ipv4_count_);
+      EXPECT_EQ(
+          metrics_observer
+              .peer_connection_metrics_counters_[webrtc::kBestConnections_IPv6],
+          expected.ipv6_count_);
+    }
+  };
+
+  class LoopbackNetworkManager {
+   public:
+    LoopbackNetworkManager(WebRtcSessionTest* session,
+                           const LoopbackNetworkConfiguration& config)
+        : config_(config) {
+      session->AddInterface(
+          rtc::SocketAddress(kClientAddrHost1, kClientAddrPort));
+      if (config_.test_extra_ipv4_network_) {
+        session->AddInterface(
+            rtc::SocketAddress(kClientAddrHost2, kClientAddrPort));
+      }
+      if (config_.test_ipv6_network_) {
+        session->AddInterface(
+            rtc::SocketAddress(kClientIPv6AddrHost1, kClientAddrPort));
+      }
+    }
+
+    void ApplyFirewallRules(rtc::FirewallSocketServer* fss) {
+      fss->AddRule(false, rtc::FP_ANY, rtc::FD_ANY,
+                   rtc::SocketAddress(kClientAddrHost1, kClientAddrPort));
+      if (config_.test_extra_ipv4_network_) {
+        fss->AddRule(false, rtc::FP_ANY, rtc::FD_ANY,
+                     rtc::SocketAddress(kClientAddrHost2, kClientAddrPort));
+      }
+      if (config_.test_ipv6_network_) {
+        fss->AddRule(false, rtc::FP_ANY, rtc::FD_ANY,
+                     rtc::SocketAddress(kClientIPv6AddrHost1, kClientAddrPort));
+      }
+    }
+
+    void ClearRules(rtc::FirewallSocketServer* fss) { fss->ClearRules(); }
+
+   private:
+    LoopbackNetworkConfiguration config_;
+  };
+
   // The method sets up a call from the session to itself, in a loopback
   // arrangement.  It also uses a firewall rule to create a temporary
   // disconnection, and then a permanent disconnection.
@@ -931,8 +1046,9 @@
   // New -> Checking -> (Connected) -> Completed -> Disconnected -> Completed
   //     -> Failed.
   // The Gathering state should go: New -> Gathering -> Completed.
-  void TestLoopbackCall() {
-    AddInterface(rtc::SocketAddress(kClientAddrHost1, kClientAddrPort));
+
+  void TestLoopbackCall(const LoopbackNetworkConfiguration& config) {
+    LoopbackNetworkManager loopback_network_manager(this, config);
     Init(NULL);
     mediastream_signaling_.SendAudioVideoStream1();
     SessionDescriptionInterface* offer = CreateOffer();
@@ -966,21 +1082,26 @@
                    observer_.ice_connection_state_,
                    kIceCandidatesTimeout);
 
+    config.VerifyBestConnectionAfterIceConverge(metrics_observer_);
     // Adding firewall rule to block ping requests, which should cause
     // transport channel failure.
-    fss_->AddRule(false,
-                  rtc::FP_ANY,
-                  rtc::FD_ANY,
-                  rtc::SocketAddress(kClientAddrHost1, kClientAddrPort));
+
+    loopback_network_manager.ApplyFirewallRules(fss_.get());
+
+    LOG(LS_INFO) << "Firewall Rules applied";
     EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionDisconnected,
                    observer_.ice_connection_state_,
                    kIceCandidatesTimeout);
 
+    metrics_observer_.Reset();
+
     // Clearing the rules, session should move back to completed state.
-    fss_->ClearRules();
+    loopback_network_manager.ClearRules(fss_.get());
     // Session is automatically calling OnSignalingReady after creation of
     // new portallocator session which will allocate new set of candidates.
 
+    LOG(LS_INFO) << "Firewall Rules cleared";
+
     EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionCompleted,
                    observer_.ice_connection_state_,
                    kIceCandidatesTimeout);
@@ -989,15 +1110,19 @@
     // to the Failed state.  This will take at least 30 seconds because it must
     // wait for the Port to timeout.
     int port_timeout = 30000;
-    fss_->AddRule(false,
-                  rtc::FP_ANY,
-                  rtc::FD_ANY,
-                  rtc::SocketAddress(kClientAddrHost1, kClientAddrPort));
+
+    loopback_network_manager.ApplyFirewallRules(fss_.get());
+    LOG(LS_INFO) << "Firewall Rules applied again";
     EXPECT_EQ_WAIT(PeerConnectionInterface::kIceConnectionFailed,
                    observer_.ice_connection_state_,
                    kIceCandidatesTimeout + port_timeout);
   }
 
+  void TestLoopbackCall() {
+    LoopbackNetworkConfiguration config;
+    TestLoopbackCall(config);
+  }
+
   void VerifyTransportType(const std::string& content_name,
                            cricket::TransportProtocol protocol) {
     const cricket::Transport* transport = session_->GetTransport(content_name);
@@ -1114,6 +1239,7 @@
   cricket::FakeVideoMediaChannel* video_channel_;
   cricket::FakeVoiceMediaChannel* voice_channel_;
   PeerConnectionInterface::IceTransportsType ice_type_;
+  FakeMetricsObserver metrics_observer_;
 };
 
 TEST_F(WebRtcSessionTest, TestInitializeWithDtls) {
@@ -3024,12 +3150,28 @@
 TEST_F(WebRtcSessionTest, TestIceStatesBasic) {
   // Lets try with only UDP ports.
   allocator_->set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
-                       cricket::PORTALLOCATOR_DISABLE_TCP |
-                       cricket::PORTALLOCATOR_DISABLE_STUN |
-                       cricket::PORTALLOCATOR_DISABLE_RELAY);
+                        cricket::PORTALLOCATOR_DISABLE_TCP |
+                        cricket::PORTALLOCATOR_DISABLE_STUN |
+                        cricket::PORTALLOCATOR_DISABLE_RELAY);
   TestLoopbackCall();
 }
 
+TEST_F(WebRtcSessionTest, TestIceStatesBasicIPv6) {
+  allocator_->set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |
+                        cricket::PORTALLOCATOR_DISABLE_TCP |
+                        cricket::PORTALLOCATOR_DISABLE_STUN |
+                        cricket::PORTALLOCATOR_ENABLE_IPV6 |
+                        cricket::PORTALLOCATOR_DISABLE_RELAY);
+
+  // best connection is IPv6 since it has higher network preference.
+  LoopbackNetworkConfiguration config;
+  config.test_ipv6_network_ = true;
+  config.best_connection_after_initial_ice_converged_ =
+      LoopbackNetworkConfiguration::ExpectedBestConnection(0, 1);
+
+  TestLoopbackCall(config);
+}
+
 // Runs the loopback call test with BUNDLE and STUN enabled.
 TEST_F(WebRtcSessionTest, TestIceStatesBundle) {
   allocator_->set_flags(cricket::PORTALLOCATOR_ENABLE_SHARED_UFRAG |