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
This commit is contained in:
guoweis@webrtc.org 2014-12-04 17:59:29 +00:00
parent 369746bcb8
commit 7169afd9d5
6 changed files with 252 additions and 35 deletions

View File

@ -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) {

View File

@ -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 .

29
talk/app/webrtc/umametrics.h Executable file → Normal file
View File

@ -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_

View File

@ -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

View File

@ -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<cricket::VoiceChannel> voice_channel_;
rtc::scoped_ptr<cricket::VideoChannel> video_channel_;
rtc::scoped_ptr<cricket::DataChannel> 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);
};

View File

@ -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 |