blob: 2e16ece8b570f15ba3222f09e0ceea1ebd0f59c9 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <math.h>
#include <algorithm>
#include <net/nqe/network_congestion_analyzer.h>
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_number_conversions.h"
#include "net/nqe/network_quality_estimator.h"
#include "net/url_request/url_request.h"
namespace net {
namespace {
// The threshold for the observed peak queueing delay in milliseconds.
// A peak queueing delay is HIGH if it exceeds this threshold. The value is the
// 98th percentile value of the peak queueing delay observed by all requests.
static constexpr int64_t kHighQueueingDelayMsec = 5000;
// The minimal time interval between two consecutive empty queue observations
// when the number of in-flight requests is relatively low (i.e. 2). This time
// interval is required so that a new measurement period could start.
static constexpr int64_t kMinEmptyQueueObservingTimeMsec = 1500;
// The min and max values for the peak queueing delay level.
static constexpr size_t kQueueingDelayLevelMinVal = 1;
static constexpr size_t kQueueingDelayLevelMaxVal = 10;
// The array of thresholds for bucketizing a peak queueing delay sample.
constexpr base::TimeDelta kQueueingDelayBucketThresholds[] = {
base::TimeDelta::FromMilliseconds(0),
base::TimeDelta::FromMilliseconds(30),
base::TimeDelta::FromMilliseconds(60),
base::TimeDelta::FromMilliseconds(120),
base::TimeDelta::FromMilliseconds(250),
base::TimeDelta::FromMilliseconds(500),
base::TimeDelta::FromMilliseconds(1000),
base::TimeDelta::FromMilliseconds(2000),
base::TimeDelta::FromMilliseconds(4000),
base::TimeDelta::FromMilliseconds(8000)};
// The array of thresholds for determining whether a queueing delay sample is
// low under different effective connection types (ECTs). Based on the initial
// measurement, the queueing delay shows different distributions under different
// ECTs. For example, a 300-msec queueing delay is low in a 2G connection, and
// indicates the network queue is empty. However, the delay is the 90th
// percentile value on a 4G connection, and indicates many packets are in the
// network queue. These thresholds are the 33rd percentile values from these
// delay distributions. A default value (400 msec) is used when the ECT is
// UNKNOWN or OFFLINE.
constexpr base::TimeDelta
kLowQueueingDelayThresholds[EFFECTIVE_CONNECTION_TYPE_LAST] = {
base::TimeDelta::FromMilliseconds(400),
base::TimeDelta::FromMilliseconds(400),
base::TimeDelta::FromMilliseconds(400),
base::TimeDelta::FromMilliseconds(400),
base::TimeDelta::FromMilliseconds(40),
base::TimeDelta::FromMilliseconds(15)};
// The min and max values for the count of in-flight requests in
// |count_inflight_requests_to_queueing_delay_| cache. This range covers more
// than 95% of cases under all types of connection types.
static constexpr size_t kMinCountOfRequests = 1u;
static constexpr size_t kMaxCountOfRequests = 30u;
// The max number of samples that can be hold in a bucket in the
// |count_inflight_requests_to_queueing_delay_| cache.
static constexpr size_t kMaxCountOfSamplesPerBucket = 10u;
// The min value for a count-delay mapping sample to be reasonable enough to be
// inserted into the cache.
static constexpr size_t kMinScoreForValidSamples = 50;
} // namespace
namespace nqe {
namespace internal {
NetworkCongestionAnalyzer::NetworkCongestionAnalyzer(
NetworkQualityEstimator* network_quality_estimator,
const base::TickClock* tick_clock)
: network_quality_estimator_(network_quality_estimator),
tick_clock_(tick_clock),
recent_active_hosts_count_(0u),
count_inflight_requests_for_peak_queueing_delay_(0u),
peak_count_inflight_requests_measurement_period_(0u),
count_peak_queueing_delay_mapping_sample_(0u) {
DCHECK(tick_clock_);
DCHECK(network_quality_estimator_);
DCHECK_EQ(kQueueingDelayLevelMaxVal,
base::size(kQueueingDelayBucketThresholds));
}
NetworkCongestionAnalyzer::~NetworkCongestionAnalyzer() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
size_t NetworkCongestionAnalyzer::GetActiveHostsCount() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return recent_active_hosts_count_;
}
void NetworkCongestionAnalyzer::NotifyStartTransaction(
const URLRequest& request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Starts tracking the peak queueing delay after |request| starts.
TrackPeakQueueingDelayBegin(&request);
}
void NetworkCongestionAnalyzer::NotifyRequestCompleted(
const URLRequest& request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Ends tracking of the peak queueing delay.
base::Optional<base::TimeDelta> peak_observed_delay =
TrackPeakQueueingDelayEnd(&request);
if (peak_observed_delay.has_value()) {
// Records the peak queueing delay keyed by the request priority.
base::UmaHistogramMediumTimes(
"ResourceScheduler.PeakObservedQueueingDelay.Priority" +
base::NumberToString(request.priority()),
peak_observed_delay.value());
// Records the peak queueing delay for all types of requests.
UMA_HISTOGRAM_MEDIUM_TIMES("ResourceScheduler.PeakObservedQueueingDelay",
peak_observed_delay.value());
}
}
void NetworkCongestionAnalyzer::TrackPeakQueueingDelayBegin(
const URLRequest* request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Returns if |request| has already been tracked.
if (request_peak_delay_.find(request) != request_peak_delay_.end())
return;
request_peak_delay_[request] = base::nullopt;
}
base::Optional<base::TimeDelta>
NetworkCongestionAnalyzer::TrackPeakQueueingDelayEnd(
const URLRequest* request) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
auto request_delay = request_peak_delay_.find(request);
if (request_delay == request_peak_delay_.end())
return base::nullopt;
base::Optional<base::TimeDelta> peak_delay = request_delay->second;
request_peak_delay_.erase(request_delay);
return peak_delay;
}
// net::EffectiveConnectionTypeObserver:
void NetworkCongestionAnalyzer::OnEffectiveConnectionTypeChanged(
net::EffectiveConnectionType effective_connection_type) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (effective_connection_type_ == effective_connection_type)
return;
effective_connection_type_ = effective_connection_type;
count_inflight_requests_to_queueing_delay_.clear();
count_peak_queueing_delay_mapping_sample_ = 0;
}
void NetworkCongestionAnalyzer::ComputeRecentQueueingDelay(
const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
recent_rtt_stats,
const std::map<nqe::internal::IPHash, nqe::internal::CanonicalStats>&
historical_rtt_stats,
const int32_t downlink_kbps) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Updates downlink throughput if a new valid observation comes.
if (downlink_kbps != nqe::internal::INVALID_RTT_THROUGHPUT)
set_recent_downlink_throughput_kbps(downlink_kbps);
if (recent_rtt_stats.empty())
return;
int32_t delay_sample_sum = 0;
recent_active_hosts_count_ = 0u;
for (const auto& host_stats : recent_rtt_stats) {
nqe::internal::IPHash host = host_stats.first;
// Skip hosts that do not have historical statistics.
if (historical_rtt_stats.find(host) == historical_rtt_stats.end())
continue;
// Skip hosts that have one or fewer RTT samples or do not have the min
// value. They cannot provide an effective queueing delay sample.
if (historical_rtt_stats.at(host).observation_count <= 1 ||
historical_rtt_stats.at(host).canonical_pcts.find(kStatVal0p) ==
historical_rtt_stats.at(host).canonical_pcts.end())
continue;
++recent_active_hosts_count_;
delay_sample_sum +=
recent_rtt_stats.at(host).most_recent_val -
historical_rtt_stats.at(host).canonical_pcts.at(kStatVal0p);
}
if (recent_active_hosts_count_ == 0u)
return;
DCHECK_LT(0u, recent_active_hosts_count_);
int32_t delay_ms =
delay_sample_sum / static_cast<int>(recent_active_hosts_count_);
recent_queueing_delay_ = base::TimeDelta::FromMilliseconds(delay_ms);
// Updates the peak queueing delay for all tracked in-flight requests.
for (auto& it : request_peak_delay_) {
if (it.second.has_value()) {
it.second = std::max(it.second.value(), recent_queueing_delay_);
} else {
it.second = recent_queueing_delay_;
}
}
if (recent_downlink_per_packet_time_ms_ != base::nullopt) {
recent_queue_length_ = static_cast<float>(delay_ms) /
recent_downlink_per_packet_time_ms_.value();
}
}
size_t NetworkCongestionAnalyzer::ComputePeakQueueingDelayLevel(
const base::TimeDelta& peak_queueing_delay) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_LE(base::TimeDelta(), peak_queueing_delay);
// The range of queueing delay buckets includes all non-negative values. Thus,
// the non-negative peak queueing delay must be found in one of these buckets.
size_t level = kQueueingDelayLevelMaxVal;
while (level > kQueueingDelayLevelMinVal) {
// Stops searching if the peak queueing delay falls in the current bucket.
if (peak_queueing_delay >= kQueueingDelayBucketThresholds[level - 1])
break;
--level;
}
// The queueing delay level is from 1 (LOWEST) to 10 (HIGHEST).
DCHECK_LE(kQueueingDelayLevelMinVal, level);
DCHECK_GE(kQueueingDelayLevelMaxVal, level);
return level;
}
bool NetworkCongestionAnalyzer::IsQueueingDelayLow(
const base::TimeDelta& delay) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
net::EffectiveConnectionType current_ect =
network_quality_estimator_->GetEffectiveConnectionType();
return delay <= kLowQueueingDelayThresholds[current_ect];
}
bool NetworkCongestionAnalyzer::ShouldStartNewMeasurement(
const base::TimeDelta& delay,
size_t count_inflight_requests) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// The queue is not empty if either the queueing delay is high or the number
// of in-flight requests is high.
if (!IsQueueingDelayLow(delay) || count_inflight_requests >= 3) {
observing_empty_queue_timestamp_ = base::nullopt;
return false;
}
// Starts a new measurement period immediately if there is very few number of
// in-flight requests.
if (count_inflight_requests <= 1) {
observing_empty_queue_timestamp_ = base::nullopt;
return true;
}
base::TimeTicks now = tick_clock_->NowTicks();
// Requires a sufficient time interval between consecutive empty queue
// observations to claim the queue is empty.
if (observing_empty_queue_timestamp_.has_value()) {
if (now - observing_empty_queue_timestamp_.value() >=
base::TimeDelta::FromMilliseconds(kMinEmptyQueueObservingTimeMsec)) {
observing_empty_queue_timestamp_ = base::nullopt;
return true;
}
} else {
observing_empty_queue_timestamp_ = now;
}
return false;
}
void NetworkCongestionAnalyzer::UpdatePeakDelayMapping(
const base::TimeDelta& delay,
size_t count_inflight_requests) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Discards an abnormal observation. This high queueing delay is likely
// caused by retransmission packets from a previous measurement period.
if (delay >= base::TimeDelta::FromSeconds(20))
return;
if (ShouldStartNewMeasurement(delay, count_inflight_requests)) {
FinalizeCurrentMeasurementPeriod();
// Resets the tracked data for the new measurement period.
peak_queueing_delay_ = delay;
count_inflight_requests_for_peak_queueing_delay_ = count_inflight_requests;
peak_count_inflight_requests_measurement_period_ = count_inflight_requests;
} else {
// This is the logic to update the tracking data.
// First, updates the pending peak count of in-flight requests if a higher
// number of in-flight requests is observed.
// Second, updates the peak queueing delay and the peak count of inflight
// requests if a higher queueing delay is observed. The new peak queueing
// delay should be mapped to the peak count of in-flight requests that are
// observed before within this measurement period.
peak_count_inflight_requests_measurement_period_ =
std::max(peak_count_inflight_requests_measurement_period_,
count_inflight_requests);
if (delay > peak_queueing_delay_) {
// Updates the peak queueing delay and the count of in-flight requests
// that are responsible for the delay.
peak_queueing_delay_ = delay;
count_inflight_requests_for_peak_queueing_delay_ =
peak_count_inflight_requests_measurement_period_;
}
}
}
void NetworkCongestionAnalyzer::FinalizeCurrentMeasurementPeriod() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Does nothing if the peak count of in-flight requests is less than 3.
if (peak_count_inflight_requests_measurement_period_ < 3)
return;
// Exports the tracked mapping data from the current measurement period.
// Updates the count of in-flight requests that would likely cause a high
// network queueing delay.
if (peak_queueing_delay_ >=
base::TimeDelta::FromMilliseconds(kHighQueueingDelayMsec)) {
count_inflight_requests_causing_high_delay_ =
count_inflight_requests_for_peak_queueing_delay_;
}
UpdateRequestsCountAndPeakQueueingDelayMapping();
}
void NetworkCongestionAnalyzer::
UpdateRequestsCountAndPeakQueueingDelayMapping() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
size_t truncated_count =
std::min(std::max(count_inflight_requests_for_peak_queueing_delay_,
kMinCountOfRequests),
kMaxCountOfRequests);
DCHECK_LE(kMinCountOfRequests, truncated_count);
DCHECK_GE(kMaxCountOfRequests, truncated_count);
base::Optional<size_t> mapping_score =
ComputePeakDelayMappingSampleScore(truncated_count, peak_queueing_delay_);
// Discards the mapping sample if there are at least 10 samples in the cache
// and its score is less than the threshold. The purpose is to make the
// majority of cached samples reasonable so that they can be used to evaluate
// whether a new sample is valid or not.
if (count_peak_queueing_delay_mapping_sample_ >= 10 &&
mapping_score.value_or(0) < kMinScoreForValidSamples) {
return;
}
AddRequestsCountAndPeakQueueingDelaySample(truncated_count,
peak_queueing_delay_);
}
base::Optional<size_t>
NetworkCongestionAnalyzer::ComputePeakDelayMappingSampleScore(
const size_t count_inflight_requests,
const base::TimeDelta& peak_queueing_delay) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (count_inflight_requests < kMinCountOfRequests ||
count_inflight_requests > kMaxCountOfRequests) {
return base::nullopt;
}
if (count_peak_queueing_delay_mapping_sample_ < 5)
return base::nullopt;
size_t count_positive_samples = 0;
size_t delay_level = ComputePeakQueueingDelayLevel(peak_queueing_delay);
DCHECK_LE(1u, delay_level);
for (const auto& cached_entry : count_inflight_requests_to_queueing_delay_) {
if (cached_entry.first < count_inflight_requests) {
for (const auto& it : cached_entry.second) {
if (it < peak_queueing_delay)
++count_positive_samples;
}
} else if (cached_entry.first == count_inflight_requests) {
for (const auto& it : cached_entry.second) {
size_t it_delay_level = ComputePeakQueueingDelayLevel(it);
// Two samples are considered near if the difference in queueing delay
// levels is small. The absolute time difference is small for samples
// whose queueing delay level is from 1 to 5 (max val=500 msec). The two
// samples are also considered near if the absolute time difference is
// less than the 500 msec threshold.
if (it_delay_level <= delay_level + 1 ||
it_delay_level >= delay_level - 1 ||
std::abs(it.InMilliseconds() -
peak_queueing_delay.InMilliseconds()) <= 500) {
++count_positive_samples;
}
}
} else {
for (const auto& it : cached_entry.second) {
if (it > peak_queueing_delay)
++count_positive_samples;
}
}
}
return count_positive_samples * 100 /
count_peak_queueing_delay_mapping_sample_;
}
void NetworkCongestionAnalyzer::AddRequestsCountAndPeakQueueingDelaySample(
const size_t count_inflight_requests,
const base::TimeDelta& peak_queueing_delay) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (count_inflight_requests < kMinCountOfRequests ||
count_inflight_requests > kMaxCountOfRequests) {
return;
}
auto count_map_it =
count_inflight_requests_to_queueing_delay_.find(count_inflight_requests);
// Creates a new entry if no matching record is found.
if (count_map_it == count_inflight_requests_to_queueing_delay_.end()) {
count_inflight_requests_to_queueing_delay_[count_inflight_requests]
.push_front(peak_queueing_delay);
++count_peak_queueing_delay_mapping_sample_;
} else {
count_map_it->second.push_front(peak_queueing_delay);
++count_peak_queueing_delay_mapping_sample_;
if (count_map_it->second.size() > kMaxCountOfSamplesPerBucket) {
count_map_it->second.pop_back();
--count_peak_queueing_delay_mapping_sample_;
}
}
}
void NetworkCongestionAnalyzer::set_recent_downlink_throughput_kbps(
const int32_t downlink_kbps) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
recent_downlink_throughput_kbps_ = downlink_kbps;
// Time in msec to transmit one TCP packet (1500 Bytes).
// |recent_downlink_per_packet_time_ms_| = 1500 * 8 /
// |recent_downlink_throughput_kbps_|.
recent_downlink_per_packet_time_ms_ = 12000 / downlink_kbps;
}
} // namespace internal
} // namespace nqe
} // namespace net