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}
diff --git a/webrtc/p2p/BUILD.gn b/webrtc/p2p/BUILD.gn
index 07fcdf4..b458653 100644
--- a/webrtc/p2p/BUILD.gn
+++ b/webrtc/p2p/BUILD.gn
@@ -37,6 +37,8 @@
     "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 @@
       "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",
diff --git a/webrtc/p2p/base/packetlossestimator.cc b/webrtc/p2p/base/packetlossestimator.cc
new file mode 100644
index 0000000..7dcd5b9
--- /dev/null
+++ b/webrtc/p2p/base/packetlossestimator.cc
@@ -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
diff --git a/webrtc/p2p/base/packetlossestimator.h b/webrtc/p2p/base/packetlossestimator.h
new file mode 100644
index 0000000..dfc5fe5
--- /dev/null
+++ b/webrtc/p2p/base/packetlossestimator.h
@@ -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_
diff --git a/webrtc/p2p/base/packetlossestimator_unittest.cc b/webrtc/p2p/base/packetlossestimator_unittest.cc
new file mode 100644
index 0000000..44cfdbf
--- /dev/null
+++ b/webrtc/p2p/base/packetlossestimator_unittest.cc
@@ -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());
+}
diff --git a/webrtc/p2p/base/port.cc b/webrtc/p2p/base/port.cc
index 7410b20..675d39a 100644
--- a/webrtc/p2p/base/port.cc
+++ b/webrtc/p2p/base/port.cc
@@ -77,6 +77,13 @@
 // 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 @@
       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 @@
   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 @@
   }
   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);
diff --git a/webrtc/p2p/base/port.h b/webrtc/p2p/base/port.h
index 4d15656..fd6d182 100644
--- a/webrtc/p2p/base/port.h
+++ b/webrtc/p2p/base/port.h
@@ -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 @@
   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.