Measure packet loss so we can use it to select ICE candidate pairs.

BUG=webrtc:7028

Review-Url: https://codereview.webrtc.org/2722933002
Cr-Commit-Position: refs/heads/master@{#17313}
This commit is contained in:
zstein 2017-03-20 10:53:12 -07:00 committed by Commit bot
parent a5c18d7312
commit abbacbf489
6 changed files with 409 additions and 7 deletions

View File

@ -37,6 +37,8 @@ rtc_static_library("rtc_p2p") {
"base/p2pconstants.h",
"base/p2ptransportchannel.cc",
"base/p2ptransportchannel.h",
"base/packetlossestimator.cc",
"base/packetlossestimator.h",
"base/packetsocketfactory.h",
"base/packettransportinterface.h",
"base/packettransportinternal.h",
@ -153,6 +155,7 @@ if (rtc_include_tests) {
"base/jseptransport_unittest.cc",
"base/mockicetransport.h",
"base/p2ptransportchannel_unittest.cc",
"base/packetlossestimator_unittest.cc",
"base/port_unittest.cc",
"base/portallocator_unittest.cc",
"base/pseudotcp_unittest.cc",

View File

@ -0,0 +1,126 @@
/*
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <sstream>
#include "webrtc/p2p/base/packetlossestimator.h"
#include "webrtc/base/checks.h"
namespace cricket {
PacketLossEstimator::PacketLossEstimator(int64_t consider_lost_after_ms,
int64_t forget_after_ms)
: consider_lost_after_ms_(consider_lost_after_ms),
forget_after_ms_(forget_after_ms) {
RTC_DCHECK_LT(consider_lost_after_ms, forget_after_ms);
}
void PacketLossEstimator::ExpectResponse(std::string id, int64_t sent_time) {
tracked_packets_[id] = PacketInfo{sent_time, false};
// Called to free memory in case the client hasn't called UpdateResponseRate
// in a while.
MaybeForgetOldRequests(sent_time);
}
void PacketLossEstimator::ReceivedResponse(std::string id,
int64_t received_time) {
auto iter = tracked_packets_.find(id);
if (iter != tracked_packets_.end()) {
auto& packet_info = iter->second;
packet_info.response_received = true;
}
// Called to free memory in case the client hasn't called UpdateResponseRate
// in a while.
MaybeForgetOldRequests(received_time);
}
void PacketLossEstimator::UpdateResponseRate(int64_t now) {
int responses_expected = 0;
int responses_received = 0;
for (auto iter = tracked_packets_.begin(); iter != tracked_packets_.end();) {
const auto& packet_info = iter->second;
if (Forget(packet_info, now)) {
iter = tracked_packets_.erase(iter);
continue;
}
if (packet_info.response_received) {
responses_expected += 1;
responses_received += 1;
} else if (ConsiderLost(packet_info, now)) {
responses_expected += 1;
}
++iter;
}
if (responses_expected > 0) {
response_rate_ =
static_cast<double>(responses_received) / responses_expected;
} else {
response_rate_ = 1.0;
}
last_forgot_at_ = now;
}
void PacketLossEstimator::MaybeForgetOldRequests(int64_t now) {
if (now - last_forgot_at_ <= forget_after_ms_) {
return;
}
for (auto iter = tracked_packets_.begin(); iter != tracked_packets_.end();) {
const auto& packet_info = iter->second;
if (Forget(packet_info, now)) {
iter = tracked_packets_.erase(iter);
} else {
++iter;
}
}
last_forgot_at_ = now;
}
bool PacketLossEstimator::ConsiderLost(const PacketInfo& packet_info,
int64_t now) const {
return packet_info.sent_time < now - consider_lost_after_ms_;
}
bool PacketLossEstimator::Forget(const PacketInfo& packet_info,
int64_t now) const {
return now - packet_info.sent_time > forget_after_ms_;
}
std::size_t PacketLossEstimator::tracked_packet_count_for_testing() const {
return tracked_packets_.size();
}
std::string PacketLossEstimator::TrackedPacketsStringForTesting(
std::size_t max) const {
std::ostringstream oss;
size_t count = 0;
for (const auto& p : tracked_packets_) {
const auto& id = p.first;
const auto& packet_info = p.second;
oss << "{ " << id << ", " << packet_info.sent_time << "}, ";
count += 1;
if (count == max) {
oss << "...";
break;
}
}
return oss.str();
}
} // namespace cricket

View File

@ -0,0 +1,86 @@
/*
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_P2P_BASE_PACKETLOSSESTIMATOR_H_
#define WEBRTC_P2P_BASE_PACKETLOSSESTIMATOR_H_
#include <stdint.h>
#include <string>
#include <unordered_map>
namespace cricket {
// Estimates the response rate for a series of messages expecting responses.
// Messages and their corresponding responses are identified by a string id.
//
// Responses are considered lost if they are not received within
// |consider_lost_after_ms|. If a response is received after
// |consider_lost_after_ms|, it is still considered as a response.
// Messages sent more than |forget_after_ms| ago are not considered
// anymore. The response rate is initially assumed to be 1.0.
//
// If the current time is 7, |forget_after_ms| is 6, and
// |consider_lost_after_ms| is 2, the response rate considers messages sent
// between times 1 and 5, so only messages in the following window can be
// considered lost:
//
// Time: 0 1 2 3 4 5 6 7
// Wind: <-------> |
//
// Responses received to the right of the window are still counted.
class PacketLossEstimator {
public:
explicit PacketLossEstimator(int64_t consider_lost_after_ms,
int64_t forget_after_ms);
// Registers that a message with the given |id| was sent at |sent_time|.
void ExpectResponse(std::string id, int64_t sent_time);
// Registers a response with the given |id| was received at |received_time|.
void ReceivedResponse(std::string id, int64_t received_time);
// Calculates the current response rate based on the expected and received
// messages. Messages sent more than |forget_after| ms ago will be forgotten.
void UpdateResponseRate(int64_t now);
// Gets the current response rate as updated by UpdateResponseRate.
double get_response_rate() const { return response_rate_; }
std::size_t tracked_packet_count_for_testing() const;
// Output tracked packet state as a string.
std::string TrackedPacketsStringForTesting(std::size_t max) const;
private:
struct PacketInfo {
int64_t sent_time;
bool response_received;
};
// Called periodically by ExpectResponse and ReceivedResponse to manage memory
// usage.
void MaybeForgetOldRequests(int64_t now);
bool ConsiderLost(const PacketInfo&, int64_t now) const;
bool Forget(const PacketInfo&, int64_t now) const;
int64_t consider_lost_after_ms_;
int64_t forget_after_ms_;
int64_t last_forgot_at_ = 0;
std::unordered_map<std::string, PacketInfo> tracked_packets_;
double response_rate_ = 1.0;
};
} // namespace cricket
#endif // WEBRTC_P2P_BASE_PACKETLOSSESTIMATOR_H_

View File

@ -0,0 +1,172 @@
/*
* Copyright 2017 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <utility>
#include "webrtc/base/gunit.h"
#include "webrtc/p2p/base/packetlossestimator.h"
using cricket::PacketLossEstimator;
class PacketLossEstimatorTest : public testing::Test {};
TEST_F(PacketLossEstimatorTest, InitialResponseRate) {
PacketLossEstimator ple(5, 100);
EXPECT_EQ(1.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, InitialUpdateResponseRate) {
PacketLossEstimator ple(5, 100);
ple.UpdateResponseRate(10);
EXPECT_EQ(1.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, ResponseReceived) {
PacketLossEstimator ple(5, 100);
ple.ExpectResponse("a", 0);
ple.ReceivedResponse("a", 1);
ple.UpdateResponseRate(2);
EXPECT_EQ(1.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, ResponseNotConsideredLostYet) {
PacketLossEstimator ple(5, 100);
ple.ExpectResponse("a", 0);
ple.UpdateResponseRate(2);
EXPECT_EQ(1.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, ResponseConsideredLost) {
PacketLossEstimator ple(5, 100);
ple.ExpectResponse("a", 0);
ple.UpdateResponseRate(10);
EXPECT_EQ(0.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, ResponseLate) {
PacketLossEstimator ple(5, 100);
ple.ExpectResponse("a", 0);
ple.ReceivedResponse("a", 6);
ple.UpdateResponseRate(10);
EXPECT_EQ(1.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, ResponseForgotten) {
PacketLossEstimator ple(5, 100);
ple.ExpectResponse("a", 0);
ple.UpdateResponseRate(101);
EXPECT_EQ(1.0, ple.get_response_rate());
}
TEST_F(PacketLossEstimatorTest, Lost1_Received1) {
PacketLossEstimator ple(5, 100);
ple.ExpectResponse("a", 0);
ple.ExpectResponse("b", 2);
ple.ReceivedResponse("b", 6);
ple.UpdateResponseRate(7);
EXPECT_EQ(0.5, ple.get_response_rate());
}
static const std::pair<std::string, int64_t> kFivePackets[5] = {{"a", 0},
{"b", 2},
{"c", 4},
{"d", 6},
{"e", 8}};
// Time: 0 1 2 3 4 5 6 7 8 9 10
// Sent: a b c d e |
// Recv: b |
// Wind: --------------> |
TEST_F(PacketLossEstimatorTest, Lost3_Received1_Waiting1) {
PacketLossEstimator ple(3, 100);
for (const auto& p : kFivePackets) {
ple.ExpectResponse(p.first, p.second);
}
ple.ReceivedResponse("b", 3);
ple.UpdateResponseRate(10);
EXPECT_EQ(0.25, ple.get_response_rate());
}
// Time: 0 1 2 3 4 5 6 7 8 9 10
// Sent: a b c d e |
// Recv: e |
// Wind: --------------> |
TEST_F(PacketLossEstimatorTest, Lost4_Early1) {
PacketLossEstimator ple(3, 100);
for (const auto& p : kFivePackets) {
ple.ExpectResponse(p.first, p.second);
}
ple.ReceivedResponse("e", 9);
ple.UpdateResponseRate(10);
// e should be considered, even though its response was received less than
// |consider_lost_after_ms| ago.
EXPECT_EQ(0.2, ple.get_response_rate());
}
// Time: 0 1 2 3 4 5 6 7 8 9 10
// Sent: a b c d e |
// Recv: c |
// Wind: <-------> |
TEST_F(PacketLossEstimatorTest, Forgot2_Received1_Lost1_Waiting1) {
PacketLossEstimator ple(3, 7);
for (const auto& p : kFivePackets) {
ple.ExpectResponse(p.first, p.second);
}
ple.ReceivedResponse("c", 5);
ple.UpdateResponseRate(10);
EXPECT_EQ(0.5, ple.get_response_rate());
}
// Tests that old messages are forgotten if ExpectResponse is called
// |forget_after_ms| after |last_forgot_at|. It is important that ExpectResponse
// and ReceivedResponse forget old messages to avoid |tracked_packets_| growing
// without bound if UpdateResponseRate is never called (or rarely called).
//
// Time: 0 1 2 3 4 5 6
// Sent: a b
// Wind: <------->
TEST_F(PacketLossEstimatorTest, ExpectResponseForgetsOldPackets) {
PacketLossEstimator ple(1, 5);
ple.ExpectResponse("a", 0); // This message will be forgotten.
ple.ExpectResponse("b", 6); // This call should trigger clean up.
// a should be forgotten, b should be tracked.
EXPECT_EQ(1u, ple.tracked_packet_count_for_testing());
}
// Tests that old messages are forgotten if ExpectResponse is called
// |forget_after_ms| after |last_forgot_at|. It is important that ExpectResponse
// and ReceivedResponse forget old messages to avoid |tracked_packets_| growing
// without bound if UpdateResponseRate is never called (or rarely called).
//
// Time: 0 1 2 3 4 5 6
// Sent: a
// Recv: b
// Wind: <------->
TEST_F(PacketLossEstimatorTest, ReceivedResponseForgetsOldPackets) {
PacketLossEstimator ple(1, 5);
ple.ExpectResponse("a", 0); // This message will be forgotten.
ple.ReceivedResponse("b", 6); // This call should trigger clean up.
// a should be forgoten, b should not be tracked (received but not sent).
EXPECT_EQ(0u, ple.tracked_packet_count_for_testing());
}

View File

@ -77,6 +77,13 @@ const int RTT_RATIO = 3; // 3 : 1
// The delay before we begin checking if this port is useless. We set
// it to a little higher than a total STUN timeout.
const int kPortTimeoutDelay = cricket::STUN_TOTAL_TIMEOUT + 5000;
// For packet loss estimation.
const int64_t kConsiderPacketLostAfter = 3000; // 3 seconds
// For packet loss estimation.
const int64_t kForgetPacketAfter = 30000; // 30 seconds
} // namespace
namespace cricket {
@ -885,6 +892,7 @@ Connection::Connection(Port* port,
last_ping_received_(0),
last_data_received_(0),
last_ping_response_received_(0),
packet_loss_estimator_(kConsiderPacketLostAfter, kForgetPacketAfter),
reported_(false),
state_(IceCandidatePairState::WAITING),
receiving_timeout_(WEAK_CONNECTION_RECEIVE_TIMEOUT),
@ -1237,6 +1245,7 @@ void Connection::Ping(int64_t now) {
last_ping_sent_ = now;
ConnectionRequest *req = new ConnectionRequest(this);
pings_since_last_response_.push_back(SentPing(req->id(), now, nomination_));
packet_loss_estimator_.ExpectResponse(req->id(), now);
LOG_J(LS_VERBOSE, this) << "Sending STUN ping "
<< ", id=" << rtc::hex_encode(req->id())
<< ", nomination=" << nomination_;
@ -1391,6 +1400,9 @@ void Connection::OnConnectionRequestResponse(ConnectionRequest* request,
}
ReceivedPingResponse(rtt, request->id());
int64_t time_received = rtc::TimeMillis();
packet_loss_estimator_.ReceivedResponse(request->id(), time_received);
stats_.recv_ping_responses++;
MaybeUpdateLocalCandidate(request, response);

View File

@ -17,13 +17,6 @@
#include <string>
#include <vector>
#include "webrtc/p2p/base/candidate.h"
#include "webrtc/p2p/base/candidatepairinterface.h"
#include "webrtc/p2p/base/jseptransport.h"
#include "webrtc/p2p/base/packetsocketfactory.h"
#include "webrtc/p2p/base/portinterface.h"
#include "webrtc/p2p/base/stun.h"
#include "webrtc/p2p/base/stunrequest.h"
#include "webrtc/base/asyncpacketsocket.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/network.h"
@ -33,6 +26,14 @@
#include "webrtc/base/sigslot.h"
#include "webrtc/base/socketaddress.h"
#include "webrtc/base/thread.h"
#include "webrtc/p2p/base/candidate.h"
#include "webrtc/p2p/base/candidatepairinterface.h"
#include "webrtc/p2p/base/jseptransport.h"
#include "webrtc/p2p/base/packetlossestimator.h"
#include "webrtc/p2p/base/packetsocketfactory.h"
#include "webrtc/p2p/base/portinterface.h"
#include "webrtc/p2p/base/stun.h"
#include "webrtc/p2p/base/stunrequest.h"
namespace cricket {
@ -719,6 +720,8 @@ class Connection : public CandidatePairInterface,
int64_t receiving_unchanged_since_ = 0;
std::vector<SentPing> pings_since_last_response_;
PacketLossEstimator packet_loss_estimator_;
bool reported_;
IceCandidatePairState state_;
// Time duration to switch from receiving to not receiving.