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 |