Collect packet loss and RTT stats of STUN binding requests.

STUN candidates use STUN binding requests to keep NAT bindings open.
Related stats including packet loss and RTT can be now collected via the
legacy GetStats in PeerConnection.

Bug: None
Change-Id: I7b0eee1ccb07eb670a32ee303c9590047b25f31c
Reviewed-on: https://webrtc-review.googlesource.com/54100
Commit-Queue: Qingsi Wang <qingsi@google.com>
Reviewed-by: Taylor Brandstetter <deadbeef@webrtc.org>
Cr-Commit-Position: refs/heads/master@{#22113}
This commit is contained in:
Qingsi Wang 2018-02-20 16:03:18 -08:00 committed by Commit Bot
parent 54b8407ee5
commit 72a43a1d2c
26 changed files with 208 additions and 24 deletions

View File

@ -183,6 +183,8 @@ class RTCIceCandidatePairStats final : public RTCStats {
// TODO(hbos): |RTCStatsCollector| only collects candidates that are part of
// ice candidate pairs, but there could be candidates not paired with anything.
// crbug.com/632723
// TODO(qingsi): Add the stats of STUN binding requests (keepalives) and collect
// them in the new PeerConnection::GetStats.
class RTCIceCandidateStats : public RTCStats {
public:
WEBRTC_RTCSTATS_DECL();

View File

@ -430,7 +430,9 @@ const char* StatsReport::Value::display_name() const {
case kStatsValueNameBandwidthLimitedResolution:
return "googBandwidthLimitedResolution";
// STUN ping related attributes.
//
// TODO(zhihuang) Rename these stats to follow the standards.
// Connectivity checks.
case kStatsValueNameSentPingRequestsTotal:
return "requestsSent";
case kStatsValueNameSentPingRequestsBeforeFirstResponse:
@ -441,6 +443,15 @@ const char* StatsReport::Value::display_name() const {
return "requestsReceived";
case kStatsValueNameRecvPingResponses:
return "responsesReceived";
// STUN Keepalive pings.
case kStatsValueNameSentStunKeepaliveRequests:
return "stunKeepaliveRequestsSent";
case kStatsValueNameRecvStunKeepaliveResponses:
return "stunKeepaliveResponsesReceived";
case kStatsValueNameStunKeepaliveRttTotal:
return "stunKeepaliveRttTotal";
case kStatsValueNameStunKeepaliveRttSquaredTotal:
return "stunKeepaliveRttSquaredTotal";
// Candidate related attributes. Values are taken from
// http://w3c.github.io/webrtc-stats/#rtcstatstype-enum*.

View File

@ -129,6 +129,10 @@ class StatsReport {
kStatsValueNameSentPingResponses,
kStatsValueNameRecvPingRequests,
kStatsValueNameRecvPingResponses,
kStatsValueNameSentStunKeepaliveRequests,
kStatsValueNameRecvStunKeepaliveResponses,
kStatsValueNameStunKeepaliveRttTotal,
kStatsValueNameStunKeepaliveRttSquaredTotal,
// Internal StatsValue names.
kStatsValueNameAccelerateRate,

View File

@ -146,10 +146,14 @@ class FakeIceTransport : public IceTransportInternal {
}
void RemoveRemoteCandidate(const Candidate& candidate) override {}
bool GetStats(ConnectionInfos* infos) override {
ConnectionInfo info;
infos->clear();
infos->push_back(info);
bool GetStats(ConnectionInfos* candidate_pair_stats_list,
CandidateStatsList* candidate_stats_list) override {
CandidateStats candidate_stats;
ConnectionInfo candidate_pair_stats;
candidate_stats_list->clear();
candidate_stats_list->push_back(candidate_stats);
candidate_pair_stats_list->clear();
candidate_pair_stats_list->push_back(candidate_pair_stats);
return true;
}

View File

@ -201,7 +201,8 @@ class IceTransportInternal : public rtc::PacketTransportInternal {
virtual IceGatheringState gathering_state() const = 0;
// Returns the current stats for this connection.
virtual bool GetStats(ConnectionInfos* infos) = 0;
virtual bool GetStats(ConnectionInfos* candidate_pair_stats_list,
CandidateStatsList* candidate_stats_list) = 0;
// Returns RTT estimate over the currently active connection, or an empty
// rtc::Optional if there is none.

View File

@ -40,7 +40,9 @@ class MockIceTransport : public IceTransportInternal {
MOCK_METHOD2(SetOption, int(rtc::Socket::Option opt, int value));
MOCK_METHOD0(GetError, int());
MOCK_CONST_METHOD0(GetIceRole, cricket::IceRole());
MOCK_METHOD1(GetStats, bool(cricket::ConnectionInfos* infos));
MOCK_METHOD2(GetStats,
bool(cricket::ConnectionInfos* candidate_pair_stats_list,
cricket::CandidateStatsList* candidate_stats_list));
IceTransportState GetState() const override {
return IceTransportState::STATE_INIT;

View File

@ -1136,15 +1136,22 @@ int P2PTransportChannel::SendPacket(const char *data, size_t len,
return sent;
}
bool P2PTransportChannel::GetStats(ConnectionInfos *infos) {
bool P2PTransportChannel::GetStats(ConnectionInfos* candidate_pair_stats_list,
CandidateStatsList* candidate_stats_list) {
RTC_DCHECK(network_thread_ == rtc::Thread::Current());
// Gather connection infos.
infos->clear();
// Gather candidate and candidate pair stats.
candidate_stats_list->clear();
candidate_pair_stats_list->clear();
if (!allocator_sessions_.empty()) {
allocator_session()->GetCandidateStatsFromReadyPorts(candidate_stats_list);
}
// TODO(qingsi): Remove naming inconsistency for candidate pair/connection.
for (Connection* connection : connections_) {
ConnectionInfo info = connection->stats();
info.best_connection = (selected_connection_ == connection);
infos->push_back(std::move(info));
ConnectionInfo candidate_pair_stats = connection->stats();
candidate_pair_stats.best_connection = (selected_connection_ == connection);
candidate_pair_stats_list->push_back(std::move(candidate_pair_stats));
connection->set_reported(true);
}

View File

@ -118,7 +118,8 @@ class P2PTransportChannel : public IceTransportInternal,
int SetOption(rtc::Socket::Option opt, int value) override;
bool GetOption(rtc::Socket::Option opt, int* value) override;
int GetError() override;
bool GetStats(std::vector<ConnectionInfo>* stats) override;
bool GetStats(std::vector<ConnectionInfo>* candidate_pair_stats_list,
std::vector<CandidateStats>* candidate_stats_list) override;
rtc::Optional<int> GetRttEstimate() override;
// TODO(honghaiz): Remove this method once the reference of it in

View File

@ -1176,8 +1176,10 @@ TEST_F(P2PTransportChannelTest, GetStats) {
kMediumTimeout, clock);
TestSendRecv(&clock);
ConnectionInfos infos;
ASSERT_TRUE(ep1_ch1()->GetStats(&infos));
CandidateStatsList candidate_stats_list;
ASSERT_TRUE(ep1_ch1()->GetStats(&infos, &candidate_stats_list));
ASSERT_GE(infos.size(), 1u);
ASSERT_GE(candidate_stats_list.size(), 1u);
ConnectionInfo* best_conn_info = nullptr;
for (ConnectionInfo& info : infos) {
if (info.best_connection) {

View File

@ -193,6 +193,16 @@ static std::string ComputeFoundation(const std::string& type,
return rtc::ToString<uint32_t>(rtc::ComputeCrc32(ost.str()));
}
CandidateStats::CandidateStats() = default;
CandidateStats::CandidateStats(const CandidateStats&) = default;
CandidateStats::CandidateStats(Candidate candidate) {
this->candidate = candidate;
}
CandidateStats::~CandidateStats() = default;
ConnectionInfo::ConnectionInfo()
: best_connection(false),
writable(false),
@ -1428,7 +1438,7 @@ void Connection::ReceivedPingResponse(int rtt, const std::string& request_id) {
set_write_state(STATE_WRITABLE);
set_state(IceCandidatePairState::SUCCEEDED);
if (rtt_samples_ > 0) {
rtt_ = (RTT_RATIO * rtt_ + rtt) / (RTT_RATIO + 1);
rtt_ = rtc::GetNextMovingAverage(rtt_, rtt, RTT_RATIO);
} else {
rtt_ = rtt;
}

View File

@ -106,6 +106,36 @@ enum class IceCandidatePairState {
// frozen because we have not implemented ICE freezing logic.
};
// Stats that we can return about the port of a connection.
class StunStats {
public:
StunStats() = default;
StunStats(const StunStats&) = default;
~StunStats() = default;
StunStats& operator=(const StunStats& other) = default;
int stun_binding_requests_sent = 0;
int stun_binding_responses_received = 0;
double stun_binding_rtt_ms_total = 0;
double stun_binding_rtt_ms_squared_total = 0;
};
// Stats that we can return about a candidate.
class CandidateStats {
public:
CandidateStats();
explicit CandidateStats(Candidate candidate);
CandidateStats(const CandidateStats&);
~CandidateStats();
Candidate candidate;
// STUN port stats if this candidate is a STUN candidate.
rtc::Optional<StunStats> stun_stats;
};
typedef std::vector<CandidateStats> CandidateStatsList;
// Stats that we can return about the connections for a transport channel.
// TODO(hta): Rename to ConnectionStats
struct ConnectionInfo {
@ -149,7 +179,7 @@ struct ConnectionInfo {
rtc::Optional<uint32_t> current_round_trip_time_ms;
};
// Information about all the connections of a channel.
// Information about all the candidate pairs of a channel.
typedef std::vector<ConnectionInfo> ConnectionInfos;
const char* ProtoToString(ProtocolType proto);
@ -368,6 +398,8 @@ class Port : public PortInterface, public rtc::MessageHandler,
int16_t network_cost() const { return network_cost_; }
void GetStunStats(rtc::Optional<StunStats>* stats) override{};
protected:
enum { MSG_DESTROY_IF_DEAD = 0, MSG_FIRST_AVAILABLE };

View File

@ -79,6 +79,19 @@ bool PortAllocatorSession::IsStopped() const {
return false;
}
void PortAllocatorSession::GetCandidateStatsFromReadyPorts(
CandidateStatsList* candidate_stats_list) const {
auto ports = ReadyPorts();
for (auto* port : ports) {
auto candidates = port->Candidates();
for (const auto& candidate : candidates) {
CandidateStats candidate_stats(candidate);
port->GetStunStats(&candidate_stats.stun_stats);
candidate_stats_list->push_back(std::move(candidate_stats));
}
}
}
uint32_t PortAllocatorSession::generation() {
return generation_;
}
@ -210,4 +223,11 @@ void PortAllocator::DiscardCandidatePool() {
pooled_sessions_.clear();
}
void PortAllocator::GetCandidateStatsFromPooledSessions(
CandidateStatsList* candidate_stats_list) {
for (const auto& session : pooled_sessions()) {
session->GetCandidateStatsFromReadyPorts(candidate_stats_list);
}
}
} // namespace cricket

View File

@ -244,6 +244,10 @@ class PortAllocatorSession : public sigslot::has_slots<> {
virtual void RegatherOnFailedNetworks() {}
// Re-gathers candidates on all networks.
virtual void RegatherOnAllNetworks() {}
// Get candidate-level stats from all candidates on the ready ports and return
// the stats to the given list.
virtual void GetCandidateStatsFromReadyPorts(
CandidateStatsList* candidate_stats_list) const;
// Set the interval at which STUN candidates will resend STUN binding requests
// on the underlying ports to keep NAT bindings open.
// The default value of the interval in implementation is restored if a null
@ -476,6 +480,14 @@ class PortAllocator : public sigslot::has_slots<> {
return turn_customizer_;
}
// Collect candidate stats from pooled allocator sessions. This can be used to
// collect candidate stats without creating an offer/answer or setting local
// description. After the local description is set, the ownership of the
// pooled session is taken by P2PTransportChannel, and the
// candidate stats can be collected from P2PTransportChannel::GetStats.
virtual void GetCandidateStatsFromPooledSessions(
CandidateStatsList* candidate_stats_list);
protected:
virtual PortAllocatorSession* CreateSessionInternal(
const std::string& content_name,

View File

@ -15,6 +15,7 @@
#include <vector>
#include "api/candidate.h"
#include "api/optional.h"
#include "p2p/base/transportdescription.h"
#include "rtc_base/asyncpacketsocket.h"
#include "rtc_base/socketaddress.h"
@ -28,6 +29,7 @@ namespace cricket {
class Connection;
class IceMessage;
class StunMessage;
class StunStats;
enum ProtocolType {
PROTO_UDP,
@ -125,6 +127,8 @@ class PortInterface {
virtual std::string ToString() const = 0;
virtual void GetStunStats(rtc::Optional<StunStats>* stats) = 0;
protected:
PortInterface();
};

View File

@ -52,7 +52,7 @@ class StunBindingRequest : public StunRequest {
RTC_LOG(LS_ERROR) << "Binding address has bad family";
} else {
rtc::SocketAddress addr(addr_attr->ipaddr(), addr_attr->port());
port_->OnStunBindingRequestSucceeded(server_addr_, addr);
port_->OnStunBindingRequestSucceeded(this->Elapsed(), server_addr_, addr);
}
// The keep-alive requests will be stopped after its lifetime has passed.
@ -317,6 +317,10 @@ ProtocolType UDPPort::GetProtocol() const {
return PROTO_UDP;
}
void UDPPort::GetStunStats(rtc::Optional<StunStats>* stats) {
*stats = stats_;
}
void UDPPort::set_stun_keepalive_delay(const rtc::Optional<int>& delay) {
stun_keepalive_delay_ = (delay.has_value() ? delay.value() : KEEPALIVE_DELAY);
}
@ -450,6 +454,7 @@ bool UDPPort::MaybeSetDefaultLocalAddress(rtc::SocketAddress* addr) const {
}
void UDPPort::OnStunBindingRequestSucceeded(
int rtt_ms,
const rtc::SocketAddress& stun_server_addr,
const rtc::SocketAddress& stun_reflected_addr) {
if (bind_request_succeeded_servers_.find(stun_server_addr) !=
@ -458,6 +463,11 @@ void UDPPort::OnStunBindingRequestSucceeded(
}
bind_request_succeeded_servers_.insert(stun_server_addr);
RTC_DCHECK(stats_.stun_binding_responses_received <
stats_.stun_binding_requests_sent);
stats_.stun_binding_responses_received++;
stats_.stun_binding_rtt_ms_total += rtt_ms;
stats_.stun_binding_rtt_ms_squared_total += rtt_ms * rtt_ms;
// If socket is shared and |stun_reflected_addr| is equal to local socket
// address, or if the same address has been added by another STUN server,
// then discarding the stun address.
@ -520,8 +530,10 @@ void UDPPort::MaybeSetPortCompleteOrError() {
void UDPPort::OnSendPacket(const void* data, size_t size, StunRequest* req) {
StunBindingRequest* sreq = static_cast<StunBindingRequest*>(req);
rtc::PacketOptions options(DefaultDscpValue());
if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0)
if (socket_->SendTo(data, size, sreq->server_addr(), options) < 0) {
RTC_LOG_ERR_EX(LERROR, socket_->GetError()) << "sendto";
}
stats_.stun_binding_requests_sent++;
}
bool UDPPort::HasCandidateWithAddress(const rtc::SocketAddress& addr) const {

View File

@ -101,6 +101,8 @@ class UDPPort : public Port {
bool SupportsProtocol(const std::string& protocol) const override;
ProtocolType GetProtocol() const override;
void GetStunStats(rtc::Optional<StunStats>* stats) override;
void set_stun_keepalive_delay(const rtc::Optional<int>& delay);
int stun_keepalive_delay() const {
return stun_keepalive_delay_;
@ -206,6 +208,7 @@ class UDPPort : public Port {
// Below methods handles binding request responses.
void OnStunBindingRequestSucceeded(
int rtt_ms,
const rtc::SocketAddress& stun_server_addr,
const rtc::SocketAddress& stun_reflected_addr);
void OnStunBindingOrResolveRequestFailed(
@ -240,6 +243,8 @@ class UDPPort : public Port {
int stun_keepalive_delay_;
int stun_keepalive_lifetime_ = INFINITE_LIFETIME;
StunStats stats_;
// This is true by default and false when
// PORTALLOCATOR_DISABLE_DEFAULT_LOCAL_CANDIDATE is specified.
bool emit_local_for_anyaddress_;

View File

@ -234,7 +234,7 @@ bool JsepTransport::GetStats(TransportStats* stats) {
dtls_transport->GetSslCipherSuite(&substats.ssl_cipher_suite);
substats.dtls_state = dtls_transport->dtls_state();
if (!dtls_transport->ice_transport()->GetStats(
&substats.connection_infos)) {
&substats.connection_infos, &substats.candidate_stats_list)) {
return false;
}
stats->channel_stats.push_back(substats);

View File

@ -39,6 +39,7 @@ struct TransportChannelStats {
~TransportChannelStats();
int component = 0;
CandidateStatsList candidate_stats_list;
ConnectionInfos connection_infos;
int srtp_crypto_suite = rtc::SRTP_INVALID_CRYPTO_SUITE;
int ssl_cipher_suite = rtc::TLS_NULL_WITH_NULL_NULL;

View File

@ -4937,6 +4937,12 @@ bool PeerConnection::ReadyToSendData() const {
sctp_ready_to_send_data_;
}
cricket::CandidateStatsList PeerConnection::GetPooledCandidateStats() const {
cricket::CandidateStatsList candidate_states_list;
port_allocator_->GetCandidateStatsFromPooledSessions(&candidate_states_list);
return candidate_states_list;
}
std::map<std::string, std::string> PeerConnection::GetTransportNamesByMid()
const {
std::map<std::string, std::string> transport_names_by_mid;

View File

@ -233,6 +233,7 @@ class PeerConnection : public PeerConnectionInternal,
return sctp_transport_name_;
}
cricket::CandidateStatsList GetPooledCandidateStats() const override;
std::map<std::string, std::string> GetTransportNamesByMid() const override;
std::map<std::string, cricket::TransportStats> GetTransportStatsByNames(
const std::set<std::string>& transport_names) override;

View File

@ -56,6 +56,8 @@ class PeerConnectionInternal : public PeerConnectionInterface {
virtual rtc::Optional<std::string> sctp_content_name() const = 0;
virtual rtc::Optional<std::string> sctp_transport_name() const = 0;
virtual cricket::CandidateStatsList GetPooledCandidateStats() const = 0;
// Returns a map from MID to transport name for all active media sections.
virtual std::map<std::string, std::string> GetTransportNamesByMid() const = 0;

View File

@ -670,10 +670,12 @@ StatsReport* StatsCollector::AddConnectionInfoReport(
report->AddBoolean(b.name, b.value);
report->AddId(StatsReport::kStatsValueNameChannelId, channel_report_id);
cricket::CandidateStats local_candidate_stats(info.local_candidate);
cricket::CandidateStats remote_candidate_stats(info.remote_candidate);
report->AddId(StatsReport::kStatsValueNameLocalCandidateId,
AddCandidateReport(info.local_candidate, true)->id());
AddCandidateReport(local_candidate_stats, true)->id());
report->AddId(StatsReport::kStatsValueNameRemoteCandidateId,
AddCandidateReport(info.remote_candidate, false)->id());
AddCandidateReport(remote_candidate_stats, false)->id());
const Int64ForAdd int64s[] = {
{StatsReport::kStatsValueNameBytesReceived, info.recv_total_bytes},
@ -708,8 +710,9 @@ StatsReport* StatsCollector::AddConnectionInfoReport(
}
StatsReport* StatsCollector::AddCandidateReport(
const cricket::Candidate& candidate,
const cricket::CandidateStats& candidate_stats,
bool local) {
const auto& candidate = candidate_stats.candidate;
StatsReport::Id id(StatsReport::NewCandidateId(local, candidate.id()));
StatsReport* report = reports_.Find(id);
if (!report) {
@ -718,6 +721,18 @@ StatsReport* StatsCollector::AddCandidateReport(
if (local) {
report->AddString(StatsReport::kStatsValueNameCandidateNetworkType,
AdapterTypeToStatsType(candidate.network_type()));
if (candidate_stats.stun_stats.has_value()) {
const auto& stun_stats = candidate_stats.stun_stats.value();
report->AddInt64(StatsReport::kStatsValueNameSentStunKeepaliveRequests,
stun_stats.stun_binding_requests_sent);
report->AddInt64(StatsReport::kStatsValueNameRecvStunKeepaliveResponses,
stun_stats.stun_binding_responses_received);
report->AddFloat(StatsReport::kStatsValueNameStunKeepaliveRttTotal,
stun_stats.stun_binding_rtt_ms_total);
report->AddFloat(
StatsReport::kStatsValueNameStunKeepaliveRttSquaredTotal,
stun_stats.stun_binding_rtt_ms_squared_total);
}
}
report->AddString(StatsReport::kStatsValueNameCandidateIPAddress,
candidate.address().ipaddr().ToString());
@ -745,6 +760,13 @@ void StatsCollector::ExtractSessionInfo() {
report->AddBoolean(StatsReport::kStatsValueNameInitiator,
pc_->initial_offerer());
cricket::CandidateStatsList pooled_candidate_stats_list =
pc_->GetPooledCandidateStats();
for (const cricket::CandidateStats& stats : pooled_candidate_stats_list) {
AddCandidateReport(stats, true);
}
std::set<std::string> transport_names;
for (const auto& entry : pc_->GetTransportNamesByMid()) {
transport_names.insert(entry.second);
@ -808,6 +830,16 @@ void StatsCollector::ExtractSessionInfo() {
rtc::SSLStreamAdapter::SslCipherSuiteToName(ssl_cipher_suite));
}
// Collect stats for non-pooled candidates. Note that the reports
// generated here supersedes the candidate reports generated in
// AddConnectionInfoReport below, and they may report candidates that are
// not paired. Also, the candidate report generated in
// AddConnectionInfoReport do not report port stats like StunStats.
for (const cricket::CandidateStats& stats :
channel_iter.candidate_stats_list) {
AddCandidateReport(stats, true);
}
int connection_id = 0;
for (const cricket::ConnectionInfo& info :
channel_iter.connection_infos) {

View File

@ -96,8 +96,9 @@ class StatsCollector {
// Helper method for creating IceCandidate report. |is_local| indicates
// whether this candidate is local or remote.
StatsReport* AddCandidateReport(const cricket::Candidate& candidate,
bool local);
StatsReport* AddCandidateReport(
const cricket::CandidateStats& candidate_stats,
bool local);
// Adds a report for this certificate and every certificate in its chain, and
// returns the leaf certificate's report (|cert|'s report).

View File

@ -272,6 +272,10 @@ class FakePeerConnectionForStats : public FakePeerConnectionBase {
return sctp_data_channels_;
}
cricket::CandidateStatsList GetPooledCandidateStats() const override {
return {};
}
std::map<std::string, std::string> GetTransportNamesByMid() const override {
std::map<std::string, std::string> transport_names_by_mid;
if (voice_channel_) {

View File

@ -215,4 +215,8 @@ double CreateRandomDouble() {
std::numeric_limits<double>::epsilon());
}
double GetNextMovingAverage(double prev_average, double cur, double ratio) {
return (ratio * prev_average + cur) / (ratio + 1);
}
} // namespace rtc

View File

@ -59,6 +59,10 @@ uint32_t CreateRandomNonZeroId();
// Generates a random double between 0.0 (inclusive) and 1.0 (exclusive).
double CreateRandomDouble();
// Compute moving average with the given ratio between the previous average
// value and the current value.
double GetNextMovingAverage(double prev_average, double cur, double ratio);
} // namespace rtc
#endif // RTC_BASE_HELPERS_H_