From 7169afd9d53fce803858bc954e6cc5ebbf9b1695 Mon Sep 17 00:00:00 2001 From: "guoweis@webrtc.org" Date: Thu, 4 Dec 2014 17:59:29 +0000 Subject: [PATCH] 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 --- talk/app/webrtc/peerconnection.cc | 5 + talk/app/webrtc/peerconnectioninterface.h | 10 +- talk/app/webrtc/umametrics.h | 29 +++- talk/app/webrtc/webrtcsession.cc | 63 ++++++-- talk/app/webrtc/webrtcsession.h | 10 ++ talk/app/webrtc/webrtcsession_unittest.cc | 170 ++++++++++++++++++++-- 6 files changed, 252 insertions(+), 35 deletions(-) mode change 100755 => 100644 talk/app/webrtc/umametrics.h diff --git a/talk/app/webrtc/peerconnection.cc b/talk/app/webrtc/peerconnection.cc index 64ddcad1b2..99a091db9e 100644 --- a/talk/app/webrtc/peerconnection.cc +++ b/talk/app/webrtc/peerconnection.cc @@ -682,6 +682,11 @@ bool PeerConnection::AddIceCandidate( 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 73a48124ad..e751f22702 100644 --- a/talk/app/webrtc/peerconnectioninterface.h +++ b/talk/app/webrtc/peerconnectioninterface.h @@ -130,16 +130,18 @@ class StatsObserver : public rtc::RefCountInterface { 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 81f7bac400..bbff0699b0 --- a/talk/app/webrtc/umametrics.h +++ b/talk/app/webrtc/umametrics.h @@ -35,25 +35,42 @@ namespace webrtc { // 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 402edc2acb..c124237923 100644 --- a/talk/app/webrtc/webrtcsession.cc +++ b/talk/app/webrtc/webrtcsession.cc @@ -461,16 +461,17 @@ class IceRestartAnswerLatch { 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 @@ WebRtcSession::WebRtcSession( 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::OnTransportWritable(cricket::Transport* transport) { 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 @@ bool WebRtcSession::ReadyToUseRemoteCandidate( 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 25e96461bd..3db268277a 100644 --- a/talk/app/webrtc/webrtcsession.h +++ b/talk/app/webrtc/webrtcsession.h @@ -225,6 +225,11 @@ class WebRtcSession : public cricket::BaseSession, // 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 @@ class WebRtcSession : public cricket::BaseSession, 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 voice_channel_; rtc::scoped_ptr video_channel_; rtc::scoped_ptr data_channel_; @@ -357,6 +366,7 @@ class WebRtcSession : public cricket::BaseSession, // 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 e79001a37f..ba76ff399e 100644 --- a/talk/app/webrtc/webrtcsession_unittest.cc +++ b/talk/app/webrtc/webrtcsession_unittest.cc @@ -104,6 +104,8 @@ typedef PeerConnectionInterface::RTCOfferAnswerOptions RTCOfferAnswerOptions; 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 @@ static void InjectAfter(const std::string& line, 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 @@ class WebRtcSessionTest : public testing::Test { EXPECT_TRUE(session_->Initialize(options_, constraints_.get(), identity_service, ice_type_)); + session_->set_metrics_observer(&metrics_observer_); } void InitWithDtmfCodec() { @@ -919,6 +950,90 @@ class WebRtcSessionTest : public testing::Test { 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 @@ class WebRtcSessionTest : public testing::Test { // 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 @@ class WebRtcSessionTest : public testing::Test { 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 @@ class WebRtcSessionTest : public testing::Test { // 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 @@ class WebRtcSessionTest : public testing::Test { 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, TestSessionContentError) { 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 |