diff options
-rw-r--r-- | components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc | 7 | ||||
-rw-r--r-- | components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc | 16 | ||||
-rw-r--r-- | net/base/network_quality.cc | 13 | ||||
-rw-r--r-- | net/base/network_quality.h | 9 | ||||
-rw-r--r-- | net/base/network_quality_estimator.cc | 102 | ||||
-rw-r--r-- | net/base/network_quality_estimator.h | 69 | ||||
-rw-r--r-- | net/base/network_quality_estimator_unittest.cc | 140 |
7 files changed, 339 insertions, 17 deletions
diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc index 73f6521..853221f 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config.cc @@ -399,9 +399,10 @@ bool DataReductionProxyConfig::IsNetworkQualityProhibitivelySlow( network_quality_last_updated_ = base::TimeTicks::Now(); - // TODO(tbansal): Set |network_prohibitively_slow| based on medians. - net::NetworkQuality network_quality = - network_quality_estimator->GetPeakEstimate(); + net::NetworkQuality network_quality; + + if (!network_quality_estimator->GetEstimate(&network_quality)) + return false; // Network is prohibitvely slow if either the downlink bandwidth is too low // or the RTT is too high. diff --git a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc index d9f84f4..dbdc85c 100644 --- a/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc +++ b/components/data_reduction_proxy/core/browser/data_reduction_proxy_config_unittest.cc @@ -1218,25 +1218,29 @@ TEST_F(DataReductionProxyConfigTest, LoFiStatusTransition) { } } -// Overrides net::NetworkQualityEstimator::GetPeakEstimate() for testing -// purposes. +// Overrides net::NetworkQualityEstimator for testing. class TestNetworkQualityEstimator : public net::NetworkQualityEstimator { public: TestNetworkQualityEstimator() : rtt_(base::TimeDelta()), kbps_(0) {} ~TestNetworkQualityEstimator() override {} - net::NetworkQuality GetPeakEstimate() const override { - return net::NetworkQuality(rtt_, kbps_); + bool GetEstimate(net::NetworkQuality* median) const override { + // |median| must not be null. + *median = net::NetworkQuality(rtt_, kbps_); + return true; } void SetRtt(base::TimeDelta rtt) { rtt_ = rtt; } - void SetKbps(uint64_t kbps) { kbps_ = kbps; } + void SetKbps(int32_t kbps) { kbps_ = kbps; } private: + // RTT of the network. base::TimeDelta rtt_; - uint64_t kbps_; + + // Downstream throughput of the network. + int32_t kbps_; }; TEST_F(DataReductionProxyConfigTest, AutoLoFiParams) { diff --git a/net/base/network_quality.cc b/net/base/network_quality.cc index a1a1ee0..90f282d 100644 --- a/net/base/network_quality.cc +++ b/net/base/network_quality.cc @@ -8,6 +8,9 @@ namespace net { +NetworkQuality::NetworkQuality() : NetworkQuality(base::TimeDelta::Max(), 0) { +} + NetworkQuality::NetworkQuality(const base::TimeDelta& rtt, int32_t downstream_throughput_kbps) : rtt_(rtt), downstream_throughput_kbps_(downstream_throughput_kbps) { @@ -15,7 +18,17 @@ NetworkQuality::NetworkQuality(const base::TimeDelta& rtt, DCHECK_GE(downstream_throughput_kbps_, 0); } +NetworkQuality::NetworkQuality(const NetworkQuality& other) + : NetworkQuality(other.rtt_, other.downstream_throughput_kbps_) { +} + NetworkQuality::~NetworkQuality() { } +NetworkQuality& NetworkQuality::operator=(const NetworkQuality& other) { + rtt_ = other.rtt_; + downstream_throughput_kbps_ = other.downstream_throughput_kbps_; + return *this; +} + } // namespace net diff --git a/net/base/network_quality.h b/net/base/network_quality.h index 328fb70..315616f 100644 --- a/net/base/network_quality.h +++ b/net/base/network_quality.h @@ -16,13 +16,16 @@ namespace net { // estimated by the NetworkQualityEstimator. class NET_EXPORT_PRIVATE NetworkQuality { public: + NetworkQuality(); // |rtt| is the estimate of the round trip time. // |downstream_throughput_kbps| is the estimate of the downstream throughput. NetworkQuality(const base::TimeDelta& rtt, int32_t downstream_throughput_kbps); - + NetworkQuality(const NetworkQuality& other); ~NetworkQuality(); + NetworkQuality& operator=(const NetworkQuality& other); + // Returns the estimate of the round trip time. const base::TimeDelta& rtt() const { return rtt_; } @@ -34,10 +37,10 @@ class NET_EXPORT_PRIVATE NetworkQuality { private: // Estimated round trip time. - const base::TimeDelta rtt_; + base::TimeDelta rtt_; // Estimated downstream throughput in Kbps. - const int32_t downstream_throughput_kbps_; + int32_t downstream_throughput_kbps_; }; } // namespace net diff --git a/net/base/network_quality_estimator.cc b/net/base/network_quality_estimator.cc index d181d2c..9131f8f 100644 --- a/net/base/network_quality_estimator.cc +++ b/net/base/network_quality_estimator.cc @@ -4,8 +4,12 @@ #include "net/base/network_quality_estimator.h" +#include <float.h> +#include <algorithm> +#include <cmath> #include <limits> #include <string> +#include <vector> #include "base/logging.h" #include "base/metrics/histogram.h" @@ -20,6 +24,13 @@ namespace { // Maximum number of observations that can be held in the ObservationBuffer. const size_t kMaximumObservationsBufferSize = 500; +// Half life (in seconds) for computing time weighted percentiles. +// Every |kHalfLifeSeconds|, the weight of all observations reduces by half. +// Lowering the half life would reduce the weight of older values faster. +// TODO(tbansal): |kHalfLifeSeconds| should be configured through field trial +// in the NetworkQualityEstimator constructor. +const int kHalfLifeSeconds = 60; + } // namespace namespace net { @@ -39,6 +50,7 @@ NetworkQualityEstimator::NetworkQualityEstimator( peak_kbps_since_last_connection_change_(0) { static_assert(kMinRequestDurationMicroseconds > 0, "Minimum request duration must be > 0"); + static_assert(kHalfLifeSeconds > 0, "Half life duration must be > 0"); NetworkChangeNotifier::AddConnectionTypeObserver(this); } @@ -88,7 +100,6 @@ void NetworkQualityEstimator::NotifyDataReceived( // headers were received. base::TimeDelta observed_rtt = headers_received_time - request_start_time; DCHECK_GE(observed_rtt, base::TimeDelta()); - if (observed_rtt < fastest_rtt_since_last_connection_change_) fastest_rtt_since_last_connection_change_ = observed_rtt; @@ -111,7 +122,7 @@ void NetworkQualityEstimator::NotifyDataReceived( since_request_start.InSecondsF(); DCHECK_GE(kbps_f, 0.0); - // Check overflow errors. This may happen if the kbpsF is more than + // Check overflow errors. This may happen if the kbps_f is more than // 2 * 10^9 (= 2000 Gbps). if (kbps_f >= std::numeric_limits<int32_t>::max()) kbps_f = std::numeric_limits<int32_t>::max() - 1; @@ -230,6 +241,15 @@ NetworkQuality NetworkQualityEstimator::GetPeakEstimate() const { peak_kbps_since_last_connection_change_); } +bool NetworkQualityEstimator::GetEstimate(NetworkQuality* median) const { + if (kbps_observations_.Size() == 0 || rtt_msec_observations_.Size() == 0) { + *median = NetworkQuality(); + return false; + } + *median = GetEstimate(50); + return true; +} + size_t NetworkQualityEstimator::GetMaximumObservationBufferSizeForTests() const { return kMaximumObservationsBufferSize; @@ -251,9 +271,12 @@ NetworkQualityEstimator::Observation::Observation(int32_t value, NetworkQualityEstimator::Observation::~Observation() { } -NetworkQualityEstimator::ObservationBuffer::ObservationBuffer() { +NetworkQualityEstimator::ObservationBuffer::ObservationBuffer() + : weight_multiplier_per_second_(exp(log(0.5) / kHalfLifeSeconds)) { static_assert(kMaximumObservationsBufferSize > 0U, "Minimum size of observation buffer must be > 0"); + DCHECK_GE(weight_multiplier_per_second_, 0.0); + DCHECK_LE(weight_multiplier_per_second_, 1.0); } NetworkQualityEstimator::ObservationBuffer::~ObservationBuffer() { @@ -279,4 +302,77 @@ void NetworkQualityEstimator::ObservationBuffer::Clear() { DCHECK(observations_.empty()); } +NetworkQuality NetworkQualityEstimator::GetEstimate(int percentile) const { + DCHECK(thread_checker_.CalledOnValidThread()); + DCHECK_GE(percentile, 0); + DCHECK_LE(percentile, 100); + DCHECK_GT(kbps_observations_.Size(), 0U); + DCHECK_GT(rtt_msec_observations_.Size(), 0U); + + // RTT observations are sorted by duration from shortest to longest, thus + // a higher percentile RTT will have a longer RTT than a lower percentile. + // Throughput observations are sorted by kbps from slowest to fastest, + // thus a higher percentile throughput will be faster than a lower one. + return NetworkQuality(base::TimeDelta::FromMilliseconds( + rtt_msec_observations_.GetPercentile(percentile)), + kbps_observations_.GetPercentile(100 - percentile)); +} + +void NetworkQualityEstimator::ObservationBuffer::ComputeWeightedObservations( + std::vector<WeightedObservation>& weighted_observations, + double* total_weight) const { + weighted_observations.clear(); + double total_weight_observations = 0.0; + base::TimeTicks now = base::TimeTicks::Now(); + + for (const auto& observation : observations_) { + base::TimeDelta time_since_sample_taken = now - observation.timestamp; + double weight = + pow(weight_multiplier_per_second_, time_since_sample_taken.InSeconds()); + weight = std::max(DBL_MIN, std::min(1.0, weight)); + + weighted_observations.push_back( + WeightedObservation(observation.value, weight)); + total_weight_observations += weight; + } + + // Sort the samples by value in ascending order. + std::sort(weighted_observations.begin(), weighted_observations.end()); + *total_weight = total_weight_observations; +} + +int32_t NetworkQualityEstimator::ObservationBuffer::GetPercentile( + int percentile) const { + DCHECK(!observations_.empty()); + + // Stores WeightedObservation in increasing order of value. + std::vector<WeightedObservation> weighted_observations; + + // Total weight of all observations in |weighted_observations|. + double total_weight = 0.0; + + ComputeWeightedObservations(weighted_observations, &total_weight); + DCHECK(!weighted_observations.empty()); + DCHECK_GT(total_weight, 0.0); + DCHECK_EQ(observations_.size(), weighted_observations.size()); + + double desired_weight = percentile / 100.0 * total_weight; + + double cumulative_weight_seen_so_far = 0.0; + for (const auto& weighted_observation : weighted_observations) { + cumulative_weight_seen_so_far += weighted_observation.weight; + + // TODO(tbansal): Consider interpolating between observations. + if (cumulative_weight_seen_so_far >= desired_weight) + return weighted_observation.value; + } + + // Computation may reach here due to floating point errors. This may happen + // if |percentile| was 100 (or close to 100), and |desired_weight| was + // slightly larger than |total_weight| (due to floating point errors). + // In this case, we return the highest |value| among all observations. + // This is same as value of the last observation in the sorted vector. + return weighted_observations.at(weighted_observations.size() - 1).value; +} + } // namespace net diff --git a/net/base/network_quality_estimator.h b/net/base/network_quality_estimator.h index 6f3a467..025ce2c 100644 --- a/net/base/network_quality_estimator.h +++ b/net/base/network_quality_estimator.h @@ -41,6 +41,14 @@ class NET_EXPORT_PRIVATE NetworkQualityEstimator // Virtualized for testing. virtual NetworkQuality GetPeakEstimate() const; + // Sets |median| to the estimate of median network quality. The estimated + // quality is computed using a weighted median algorithm that assigns higher + // weight to the recent observations. |median| must not be nullptr. Returns + // true only if an estimate of the network quality is available (enough + // observations must be available to make an estimate). Virtualized for + // testing. + virtual bool GetEstimate(NetworkQuality* median) const; + // Notifies NetworkQualityEstimator that a response has been received. // |cumulative_prefilter_bytes_read| is the count of the bytes received prior // to applying filters (e.g. decompression, SDCH) from request creation time @@ -57,6 +65,11 @@ class NET_EXPORT_PRIVATE NetworkQualityEstimator TestPeakKbpsFastestRTTUpdates); FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, TestAddObservation); FRIEND_TEST_ALL_PREFIXES(URLRequestTestHTTP, NetworkQualityEstimator); + FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, + PercentileSameTimestamps); + FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, + PercentileDifferentTimestamps); + FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, ComputedPercentiles); // Records the round trip time or throughput observation, along with the time // the observation was made. @@ -72,6 +85,32 @@ class NET_EXPORT_PRIVATE NetworkQualityEstimator const base::TimeTicks timestamp; }; + // Holds an observation and its weight. + struct WeightedObservation { + WeightedObservation(int32_t value, double weight) + : value(value), weight(weight) {} + WeightedObservation(const WeightedObservation& other) + : WeightedObservation(other.value, other.weight) {} + + WeightedObservation& operator=(const WeightedObservation& other) { + value = other.value; + weight = other.weight; + return *this; + } + + // Required for sorting the samples in the ascending order of values. + bool operator<(const WeightedObservation& other) const { + return (value < other.value); + } + + // Value of the sample. + int32_t value; + + // Weight of the sample. This is computed based on how much time has passed + // since the sample was taken. + double weight; + }; + // Stores observations sorted by time. class ObservationBuffer { public: @@ -89,13 +128,31 @@ class NET_EXPORT_PRIVATE NetworkQualityEstimator // Clears the observations stored in this buffer. void Clear(); + // Returns the |percentile| value of the observations in this buffer. + int32_t GetPercentile(int percentile) const; + private: FRIEND_TEST_ALL_PREFIXES(NetworkQualityEstimatorTest, StoreObservations); + // Computes the weighted observations and stores them in + // |weighted_observations| sorted by ascending |WeightedObservation.value|. + // Sets |total_weight| to the total weight of all observations. Should be + // called only when there is at least one observation in the buffer. + void ComputeWeightedObservations( + std::vector<WeightedObservation>& weighted_observations, + double* total_weight) const; + // Holds observations sorted by time, with the oldest observation at the // front of the queue. std::deque<Observation> observations_; + // The factor by which the weight of an observation reduces every second. + // For example, if an observation is 6 seconds old, its weight would be: + // weight_multiplier_per_second_ ^ 6 + // Calculated from |kHalfLifeSeconds| by solving the following equation: + // weight_multiplier_per_second_ ^ kHalfLifeSeconds = 0.5 + const double weight_multiplier_per_second_; + DISALLOW_COPY_AND_ASSIGN(ObservationBuffer); }; @@ -133,6 +190,14 @@ class NET_EXPORT_PRIVATE NetworkQualityEstimator void OnConnectionTypeChanged( NetworkChangeNotifier::ConnectionType type) override; + // Returns an estimate of network quality at the specified |percentile|. + // |percentile| must be between 0 and 100 (both inclusive) with higher + // percentiles indicating less performant networks. For example, if + // |percentile| is 90, then the network is expected to be faster than the + // returned estimate with 0.9 probability. Similarly, network is expected to + // be slower than the returned estimate with 0.1 probability. + NetworkQuality GetEstimate(int percentile) const; + // Determines if the requests to local host can be used in estimating the // network quality. Set to true only for tests. const bool allow_localhost_requests_; @@ -159,10 +224,10 @@ class NET_EXPORT_PRIVATE NetworkQualityEstimator // 2) The transfer time includes at least one RTT while no bytes are read. int32_t peak_kbps_since_last_connection_change_; - // Buffer that holds Kbps observations. + // Buffer that holds Kbps observations sorted by timestamp. ObservationBuffer kbps_observations_; - // Buffer that holds RTT (in milliseconds) observations. + // Buffer that holds RTT (in milliseconds) observations sorted by timestamp. ObservationBuffer rtt_msec_observations_; base::ThreadChecker thread_checker_; diff --git a/net/base/network_quality_estimator_unittest.cc b/net/base/network_quality_estimator_unittest.cc index d07c5b6..c1955ec4 100644 --- a/net/base/network_quality_estimator_unittest.cc +++ b/net/base/network_quality_estimator_unittest.cc @@ -120,4 +120,144 @@ TEST(NetworkQualityEstimatorTest, StoreObservations) { } } +// http://crbug.com/504058 +#if !defined(OS_WIN) +// Verifies that the percentiles are correctly computed. All observations have +// the same timestamp. Kbps percentiles must be in decreasing order. RTT +// percentiles must be in increasing order. +TEST(NetworkQualityEstimatorTest, PercentileSameTimestamps) { + NetworkQualityEstimator estimator; + base::TimeTicks now = base::TimeTicks::Now(); + + // Network quality should be unavailable when no observations are available. + NetworkQuality network_quality; + EXPECT_FALSE(estimator.GetEstimate(&network_quality)); + + // Insert samples from {1,2,3,..., 100}. First insert odd samples, then even + // samples. This helps in verifying that the order of samples does not matter. + for (int i = 1; i <= 99; i += 2) { + estimator.kbps_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, now)); + estimator.rtt_msec_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, now)); + EXPECT_TRUE(estimator.GetEstimate(&network_quality)); + } + + for (int i = 2; i <= 100; i += 2) { + estimator.kbps_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, now)); + estimator.rtt_msec_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, now)); + EXPECT_TRUE(estimator.GetEstimate(&network_quality)); + } + + for (int i = 0; i <= 100; ++i) { + // Checks if the difference between the two integers is less than 1. This is + // required because computed percentiles may be slightly different from + // what is expected due to floating point computation errors and integer + // rounding off errors. + EXPECT_NEAR(estimator.GetEstimate(i).downstream_throughput_kbps(), 100 - i, + 1); + EXPECT_NEAR(estimator.GetEstimate(i).rtt().InMilliseconds(), i, 1); + } + + EXPECT_TRUE(estimator.GetEstimate(&network_quality)); + // |network_quality| should be equal to the 50 percentile value. + EXPECT_EQ(estimator.GetEstimate(50).downstream_throughput_kbps(), + network_quality.downstream_throughput_kbps()); + EXPECT_EQ(estimator.GetEstimate(50).rtt(), network_quality.rtt()); +} + +// Verifies that the percentiles are correctly computed. Observations have +// different timestamps with half the observations being very old and the rest +// of them being very recent. Percentiles should factor in recent observations +// much more heavily than older samples. Kbps percentiles must be in decreasing +// order. RTT percentiles must be in increasing order. +TEST(NetworkQualityEstimatorTest, PercentileDifferentTimestamps) { + NetworkQualityEstimator estimator; + base::TimeTicks now = base::TimeTicks::Now(); + base::TimeTicks very_old = base::TimeTicks::UnixEpoch(); + + // First 50 samples have very old timestamp. + for (int i = 1; i <= 50; ++i) { + estimator.kbps_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, very_old)); + estimator.rtt_msec_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, very_old)); + } + + // Next 50 (i.e., from 51 to 100) have recent timestamp. + for (int i = 51; i <= 100; ++i) { + estimator.kbps_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, now)); + estimator.rtt_msec_observations_.AddObservation( + NetworkQualityEstimator::Observation(i, now)); + } + + // Older samples have very little weight. So, all percentiles are >= 51 + // (lowest value among recent observations). + for (int i = 1; i < 100; ++i) { + // Checks if the difference between the two integers is less than 1. This is + // required because computed percentiles may be slightly different from + // what is expected due to floating point computation errors and integer + // rounding off errors. + EXPECT_NEAR(estimator.GetEstimate(i).downstream_throughput_kbps(), + 51 + 0.49 * (100 - i), 1); + EXPECT_NEAR(estimator.GetEstimate(i).rtt().InMilliseconds(), 51 + 0.49 * i, + 1); + } +} +#endif // !defined(OS_WIN) + +// This test notifies NetworkQualityEstimator of received data. Next, +// throughput and RTT percentiles are checked for correctness by doing simple +// verifications. +TEST(NetworkQualityEstimatorTest, ComputedPercentiles) { + net::test_server::EmbeddedTestServer embedded_test_server; + embedded_test_server.ServeFilesFromDirectory( + base::FilePath(FILE_PATH_LITERAL("net/data/url_request_unittest"))); + ASSERT_TRUE(embedded_test_server.InitializeAndWaitUntilReady()); + + NetworkQualityEstimator estimator(true, true); + NetworkQuality network_quality = estimator.GetPeakEstimate(); + EXPECT_EQ(network_quality.rtt(), base::TimeDelta::Max()); + EXPECT_EQ(network_quality.downstream_throughput_kbps(), 0); + + TestDelegate test_delegate; + TestURLRequestContext context(false); + + uint64 min_transfer_size_in_bytes = + NetworkQualityEstimator::kMinTransferSizeInBytes; + + // Number of observations are more than the maximum buffer size. + for (size_t i = 0; + i < estimator.GetMaximumObservationBufferSizeForTests() + 100U; ++i) { + scoped_ptr<URLRequest> request( + context.CreateRequest(embedded_test_server.GetURL("/echo.html"), + DEFAULT_PRIORITY, &test_delegate)); + request->Start(); + base::RunLoop().Run(); + + // Use different number of bytes to create variation. + estimator.NotifyDataReceived(*request, min_transfer_size_in_bytes + i * 100, + min_transfer_size_in_bytes + i * 100); + } + + // Verify the percentiles through simple tests. + for (int i = 0; i <= 100; ++i) { + EXPECT_GT(estimator.GetEstimate(i).downstream_throughput_kbps(), 0); + EXPECT_LT(estimator.GetEstimate(i).rtt(), base::TimeDelta::Max()); + + if (i != 0) { + // Throughput percentiles are in decreasing order. + EXPECT_LE(estimator.GetEstimate(i).downstream_throughput_kbps(), + estimator.GetEstimate(i - 1).downstream_throughput_kbps()); + + // RTT percentiles are in increasing order. + EXPECT_GE(estimator.GetEstimate(i).rtt(), + estimator.GetEstimate(i - 1).rtt()); + } + } +} + } // namespace net |