1437 lines
55 KiB
C++
1437 lines
55 KiB
C++
// Copyright 2015 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "net/nqe/network_quality_estimator.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <limits>
|
|
#include <memory>
|
|
#include <utility>
|
|
|
|
#include "base/check_op.h"
|
|
#include "base/functional/bind.h"
|
|
#include "base/functional/callback_helpers.h"
|
|
#include "base/location.h"
|
|
#include "base/metrics/field_trial_params.h"
|
|
#include "base/metrics/histogram.h"
|
|
#include "base/metrics/histogram_base.h"
|
|
#include "base/metrics/histogram_functions.h"
|
|
#include "base/metrics/histogram_macros.h"
|
|
#include "base/metrics/histogram_macros_local.h"
|
|
#include "base/notreached.h"
|
|
#include "base/observer_list.h"
|
|
#include "base/strings/string_number_conversions.h"
|
|
#include "base/strings/string_piece.h"
|
|
#include "base/task/lazy_thread_pool_task_runner.h"
|
|
#include "base/task/single_thread_task_runner.h"
|
|
#include "base/time/default_tick_clock.h"
|
|
#include "build/build_config.h"
|
|
#include "build/chromeos_buildflags.h"
|
|
#include "net/base/features.h"
|
|
#include "net/base/host_port_pair.h"
|
|
#include "net/base/load_flags.h"
|
|
#include "net/base/load_timing_info.h"
|
|
#include "net/base/network_interfaces.h"
|
|
#include "net/base/trace_constants.h"
|
|
#include "net/base/tracing.h"
|
|
#include "net/http/http_response_headers.h"
|
|
#include "net/http/http_response_info.h"
|
|
#include "net/http/http_status_code.h"
|
|
#include "net/nqe/network_quality_estimator_util.h"
|
|
#include "net/nqe/throughput_analyzer.h"
|
|
#include "net/nqe/weighted_observation.h"
|
|
#include "net/url_request/url_request.h"
|
|
#include "net/url_request/url_request_context.h"
|
|
#include "url/gurl.h"
|
|
|
|
namespace net {
|
|
|
|
namespace {
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
|
// SequencedTaskRunner to get the network id. A SequencedTaskRunner is used
|
|
// rather than parallel tasks to avoid having many threads getting the network
|
|
// id concurrently.
|
|
base::LazyThreadPoolSequencedTaskRunner g_get_network_id_task_runner =
|
|
LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(
|
|
base::TaskTraits(base::MayBlock(),
|
|
base::TaskPriority::BEST_EFFORT,
|
|
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN));
|
|
#endif
|
|
|
|
NetworkQualityObservationSource ProtocolSourceToObservationSource(
|
|
SocketPerformanceWatcherFactory::Protocol protocol) {
|
|
switch (protocol) {
|
|
case SocketPerformanceWatcherFactory::PROTOCOL_TCP:
|
|
return NETWORK_QUALITY_OBSERVATION_SOURCE_TCP;
|
|
case SocketPerformanceWatcherFactory::PROTOCOL_QUIC:
|
|
return NETWORK_QUALITY_OBSERVATION_SOURCE_QUIC;
|
|
}
|
|
NOTREACHED();
|
|
return NETWORK_QUALITY_OBSERVATION_SOURCE_TCP;
|
|
}
|
|
|
|
// Returns true if the scheme of the |request| is either HTTP or HTTPS.
|
|
bool RequestSchemeIsHTTPOrHTTPS(const URLRequest& request) {
|
|
return request.url().is_valid() && request.url().SchemeIsHTTPOrHTTPS();
|
|
}
|
|
|
|
nqe::internal::NetworkID DoGetCurrentNetworkID(
|
|
NetworkQualityEstimatorParams* params) {
|
|
nqe::internal::NetworkID network_id(
|
|
NetworkChangeNotifier::GetConnectionType(), std::string(), INT32_MIN);
|
|
|
|
return network_id;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
NetworkQualityEstimator::NetworkQualityEstimator(
|
|
std::unique_ptr<NetworkQualityEstimatorParams> params,
|
|
NetLog* net_log)
|
|
: params_(std::move(params)),
|
|
tick_clock_(base::DefaultTickClock::GetInstance()),
|
|
last_connection_change_(tick_clock_->NowTicks()),
|
|
current_network_id_(nqe::internal::NetworkID(
|
|
NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN,
|
|
std::string(),
|
|
INT32_MIN)),
|
|
http_downstream_throughput_kbps_observations_(
|
|
params_.get(),
|
|
tick_clock_,
|
|
params_->weight_multiplier_per_second(),
|
|
1.0 /*params_->weight_multiplier_per_signal_strength_level()*/),
|
|
rtt_ms_observations_{
|
|
ObservationBuffer(
|
|
params_.get(),
|
|
tick_clock_,
|
|
params_->weight_multiplier_per_second(),
|
|
1.0 /*params_->weight_multiplier_per_signal_strength_level()*/),
|
|
ObservationBuffer(
|
|
params_.get(),
|
|
tick_clock_,
|
|
params_->weight_multiplier_per_second(),
|
|
1.0 /*params_->weight_multiplier_per_signal_strength_level()*/),
|
|
ObservationBuffer(
|
|
params_.get(),
|
|
tick_clock_,
|
|
params_->weight_multiplier_per_second(),
|
|
1.0 /*params_->weight_multiplier_per_signal_strength_level()*/)},
|
|
net_log_(NetLogWithSource::Make(
|
|
net_log,
|
|
net::NetLogSourceType::NETWORK_QUALITY_ESTIMATOR)),
|
|
event_creator_(net_log_) {
|
|
DCHECK_EQ(nqe::internal::OBSERVATION_CATEGORY_COUNT,
|
|
std::size(rtt_ms_observations_));
|
|
|
|
network_quality_store_ =
|
|
std::make_unique<nqe::internal::NetworkQualityStore>();
|
|
NetworkChangeNotifier::AddConnectionTypeObserver(this);
|
|
throughput_analyzer_ = std::make_unique<nqe::internal::ThroughputAnalyzer>(
|
|
this, params_.get(), base::SingleThreadTaskRunner::GetCurrentDefault(),
|
|
base::BindRepeating(
|
|
&NetworkQualityEstimator::OnNewThroughputObservationAvailable,
|
|
weak_ptr_factory_.GetWeakPtr()),
|
|
tick_clock_, net_log_);
|
|
|
|
watcher_factory_ = std::make_unique<nqe::internal::SocketWatcherFactory>(
|
|
base::SingleThreadTaskRunner::GetCurrentDefault(),
|
|
params_->min_socket_watcher_notification_interval(),
|
|
// OnUpdatedTransportRTTAvailable() may be called via PostTask() by
|
|
// socket watchers that live on a different thread than the current thread
|
|
// (i.e., base::SingleThreadTaskRunner::GetCurrentDefault()).
|
|
// Use WeakPtr() to avoid crashes where the socket watcher is destroyed
|
|
// after |this| is destroyed.
|
|
base::BindRepeating(
|
|
&NetworkQualityEstimator::OnUpdatedTransportRTTAvailable,
|
|
weak_ptr_factory_.GetWeakPtr()),
|
|
// ShouldSocketWatcherNotifyRTT() below is called by only the socket
|
|
// watchers that live on the same thread as the current thread
|
|
// (i.e., base::SingleThreadTaskRunner::GetCurrentDefault()). Also,
|
|
// network quality estimator is destroyed after network contexts and
|
|
// URLRequestContexts. It's safe to use base::Unretained() below since the
|
|
// socket watcher (owned by sockets) would be destroyed before |this|.
|
|
base::BindRepeating(
|
|
&NetworkQualityEstimator::ShouldSocketWatcherNotifyRTT,
|
|
base::Unretained(this)),
|
|
tick_clock_);
|
|
|
|
GatherEstimatesForNextConnectionType();
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddDefaultEstimates() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!params_->add_default_platform_observations())
|
|
return;
|
|
|
|
if (params_->DefaultObservation(current_network_id_.type).http_rtt() !=
|
|
nqe::internal::InvalidRTT()) {
|
|
Observation rtt_observation(
|
|
params_->DefaultObservation(current_network_id_.type)
|
|
.http_rtt()
|
|
.InMilliseconds(),
|
|
tick_clock_->NowTicks(), INT32_MIN,
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_HTTP_FROM_PLATFORM);
|
|
AddAndNotifyObserversOfRTT(rtt_observation);
|
|
}
|
|
|
|
if (params_->DefaultObservation(current_network_id_.type).transport_rtt() !=
|
|
nqe::internal::InvalidRTT()) {
|
|
Observation rtt_observation(
|
|
params_->DefaultObservation(current_network_id_.type)
|
|
.transport_rtt()
|
|
.InMilliseconds(),
|
|
tick_clock_->NowTicks(), INT32_MIN,
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_TRANSPORT_FROM_PLATFORM);
|
|
AddAndNotifyObserversOfRTT(rtt_observation);
|
|
}
|
|
|
|
if (params_->DefaultObservation(current_network_id_.type)
|
|
.downstream_throughput_kbps() !=
|
|
nqe::internal::INVALID_RTT_THROUGHPUT) {
|
|
Observation throughput_observation(
|
|
params_->DefaultObservation(current_network_id_.type)
|
|
.downstream_throughput_kbps(),
|
|
tick_clock_->NowTicks(), INT32_MIN,
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_HTTP_FROM_PLATFORM);
|
|
AddAndNotifyObserversOfThroughput(throughput_observation);
|
|
}
|
|
}
|
|
|
|
NetworkQualityEstimator::~NetworkQualityEstimator() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyStartTransaction(
|
|
const URLRequest& request) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!RequestSchemeIsHTTPOrHTTPS(request))
|
|
return;
|
|
|
|
// TODO(tbansal): Refactor this to a separate method.
|
|
if (request.load_flags() & LOAD_MAIN_FRAME_DEPRECATED) {
|
|
ComputeEffectiveConnectionType();
|
|
} else {
|
|
MaybeComputeEffectiveConnectionType();
|
|
}
|
|
throughput_analyzer_->NotifyStartTransaction(request);
|
|
}
|
|
|
|
bool NetworkQualityEstimator::IsHangingRequest(
|
|
base::TimeDelta observed_http_rtt) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// If there are sufficient number of end to end RTT samples available, use
|
|
// the end to end RTT estimate to determine if the request is hanging.
|
|
// If |observed_http_rtt| is within a fixed multiplier of |end_to_end_rtt_|,
|
|
// then |observed_http_rtt| is determined to be not a hanging-request RTT.
|
|
if (params_->use_end_to_end_rtt() && end_to_end_rtt_.has_value() &&
|
|
end_to_end_rtt_observation_count_at_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count() &&
|
|
params_->hanging_request_http_rtt_upper_bound_transport_rtt_multiplier() >
|
|
0 &&
|
|
observed_http_rtt <
|
|
params_->hanging_request_http_rtt_upper_bound_transport_rtt_multiplier() *
|
|
end_to_end_rtt_.value()) {
|
|
return false;
|
|
}
|
|
|
|
DCHECK_LT(
|
|
0,
|
|
params_->hanging_request_http_rtt_upper_bound_transport_rtt_multiplier());
|
|
|
|
if (transport_rtt_observation_count_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count() &&
|
|
(observed_http_rtt <
|
|
params_->hanging_request_http_rtt_upper_bound_transport_rtt_multiplier() *
|
|
GetTransportRTT().value_or(base::Seconds(10)))) {
|
|
// If there are sufficient number of transport RTT samples available, use
|
|
// the transport RTT estimate to determine if the request is hanging.
|
|
return false;
|
|
}
|
|
|
|
DCHECK_LT(
|
|
0, params_->hanging_request_http_rtt_upper_bound_http_rtt_multiplier());
|
|
|
|
if (observed_http_rtt <
|
|
params_->hanging_request_http_rtt_upper_bound_http_rtt_multiplier() *
|
|
GetHttpRTT().value_or(base::Seconds(10))) {
|
|
// Use the HTTP RTT estimate to determine if the request is hanging.
|
|
return false;
|
|
}
|
|
|
|
if (observed_http_rtt <=
|
|
params_->hanging_request_upper_bound_min_http_rtt()) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyHeadersReceived(
|
|
const URLRequest& request,
|
|
int64_t prefilter_total_bytes_read) {
|
|
TRACE_EVENT0(NetTracingCategory(),
|
|
"NetworkQualityEstimator::NotifyHeadersReceived");
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!RequestSchemeIsHTTPOrHTTPS(request) ||
|
|
!RequestProvidesRTTObservation(request)) {
|
|
return;
|
|
}
|
|
|
|
if (request.load_flags() & LOAD_MAIN_FRAME_DEPRECATED) {
|
|
ComputeEffectiveConnectionType();
|
|
}
|
|
|
|
LoadTimingInfo load_timing_info;
|
|
request.GetLoadTimingInfo(&load_timing_info);
|
|
|
|
// If the load timing info is unavailable, it probably means that the request
|
|
// did not go over the network.
|
|
if (load_timing_info.send_start.is_null() ||
|
|
load_timing_info.receive_headers_end.is_null()) {
|
|
return;
|
|
}
|
|
DCHECK(!request.response_info().was_cached);
|
|
|
|
// Duration between when the resource was requested and when the response
|
|
// headers were received.
|
|
const base::TimeDelta observed_http_rtt =
|
|
load_timing_info.receive_headers_end - load_timing_info.send_start;
|
|
if (observed_http_rtt <= base::TimeDelta())
|
|
return;
|
|
DCHECK_GE(observed_http_rtt, base::TimeDelta());
|
|
|
|
if (IsHangingRequest(observed_http_rtt))
|
|
return;
|
|
|
|
Observation http_rtt_observation(observed_http_rtt.InMilliseconds(),
|
|
tick_clock_->NowTicks(),
|
|
current_network_id_.signal_strength,
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP);
|
|
AddAndNotifyObserversOfRTT(http_rtt_observation);
|
|
throughput_analyzer_->NotifyBytesRead(request);
|
|
throughput_analyzer_->NotifyExpectedResponseContentSize(
|
|
request, request.GetExpectedContentSize());
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyBytesRead(
|
|
const URLRequest& request,
|
|
int64_t prefilter_total_bytes_read) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
throughput_analyzer_->NotifyBytesRead(request);
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyRequestCompleted(
|
|
const URLRequest& request) {
|
|
TRACE_EVENT0(NetTracingCategory(),
|
|
"NetworkQualityEstimator::NotifyRequestCompleted");
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!RequestSchemeIsHTTPOrHTTPS(request))
|
|
return;
|
|
|
|
throughput_analyzer_->NotifyRequestCompleted(request);
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyURLRequestDestroyed(
|
|
const URLRequest& request) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!RequestSchemeIsHTTPOrHTTPS(request))
|
|
return;
|
|
|
|
throughput_analyzer_->NotifyRequestCompleted(request);
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddRTTObserver(RTTObserver* rtt_observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
rtt_observer_list_.AddObserver(rtt_observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::RemoveRTTObserver(RTTObserver* rtt_observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
rtt_observer_list_.RemoveObserver(rtt_observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddThroughputObserver(
|
|
ThroughputObserver* throughput_observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
throughput_observer_list_.AddObserver(throughput_observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::RemoveThroughputObserver(
|
|
ThroughputObserver* throughput_observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
throughput_observer_list_.RemoveObserver(throughput_observer);
|
|
}
|
|
|
|
SocketPerformanceWatcherFactory*
|
|
NetworkQualityEstimator::GetSocketPerformanceWatcherFactory() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
return watcher_factory_.get();
|
|
}
|
|
|
|
void NetworkQualityEstimator::SetUseLocalHostRequestsForTesting(
|
|
bool use_localhost_requests) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
use_localhost_requests_ = use_localhost_requests;
|
|
watcher_factory_->SetUseLocalHostRequestsForTesting(use_localhost_requests_);
|
|
throughput_analyzer_->SetUseLocalHostRequestsForTesting(
|
|
use_localhost_requests_);
|
|
}
|
|
|
|
void NetworkQualityEstimator::SetUseSmallResponsesForTesting(
|
|
bool use_small_responses) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
params_->SetUseSmallResponsesForTesting(use_small_responses);
|
|
}
|
|
|
|
void NetworkQualityEstimator::DisableOfflineCheckForTesting(
|
|
bool disable_offline_check) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
disable_offline_check_ = disable_offline_check;
|
|
}
|
|
|
|
void NetworkQualityEstimator::ReportEffectiveConnectionTypeForTesting(
|
|
EffectiveConnectionType effective_connection_type) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
event_creator_.MaybeAddNetworkQualityChangedEventToNetLog(
|
|
effective_connection_type_,
|
|
params_->TypicalNetworkQuality(effective_connection_type));
|
|
|
|
for (auto& observer : effective_connection_type_observer_list_)
|
|
observer.OnEffectiveConnectionTypeChanged(effective_connection_type);
|
|
|
|
network_quality_store_->Add(current_network_id_,
|
|
nqe::internal::CachedNetworkQuality(
|
|
tick_clock_->NowTicks(), network_quality_,
|
|
effective_connection_type));
|
|
}
|
|
|
|
void NetworkQualityEstimator::ReportRTTsAndThroughputForTesting(
|
|
base::TimeDelta http_rtt,
|
|
base::TimeDelta transport_rtt,
|
|
int32_t downstream_throughput_kbps) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
for (auto& observer : rtt_and_throughput_estimates_observer_list_)
|
|
observer.OnRTTOrThroughputEstimatesComputed(http_rtt, transport_rtt,
|
|
downstream_throughput_kbps);
|
|
}
|
|
|
|
bool NetworkQualityEstimator::RequestProvidesRTTObservation(
|
|
const URLRequest& request) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
bool private_network_request =
|
|
nqe::internal::IsRequestForPrivateHost(request, net_log_);
|
|
|
|
return (use_localhost_requests_ || !private_network_request) &&
|
|
// Verify that response headers are received, so it can be ensured that
|
|
// response is not cached.
|
|
!request.response_info().response_time.is_null() &&
|
|
!request.was_cached() &&
|
|
request.creation_time() >= last_connection_change_ &&
|
|
request.method() == "GET";
|
|
}
|
|
|
|
void NetworkQualityEstimator::OnConnectionTypeChanged(
|
|
NetworkChangeNotifier::ConnectionType type) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// It's possible that |type| has the same value as |current_network_id_.type|.
|
|
// This can happen if the device switches from one WiFi SSID to another.
|
|
|
|
DCHECK_EQ(nqe::internal::OBSERVATION_CATEGORY_COUNT,
|
|
std::size(rtt_ms_observations_));
|
|
|
|
// Write the estimates of the previous network to the cache.
|
|
network_quality_store_->Add(
|
|
current_network_id_, nqe::internal::CachedNetworkQuality(
|
|
last_effective_connection_type_computation_,
|
|
network_quality_, effective_connection_type_));
|
|
|
|
// Clear the local state.
|
|
last_connection_change_ = tick_clock_->NowTicks();
|
|
http_downstream_throughput_kbps_observations_.Clear();
|
|
for (auto& rtt_ms_observation : rtt_ms_observations_)
|
|
rtt_ms_observation.Clear();
|
|
|
|
current_network_id_.signal_strength = INT32_MIN;
|
|
network_quality_ = nqe::internal::NetworkQuality();
|
|
end_to_end_rtt_ = absl::nullopt;
|
|
effective_connection_type_ = EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
|
|
rtt_observations_size_at_last_ect_computation_ = 0;
|
|
throughput_observations_size_at_last_ect_computation_ = 0;
|
|
new_rtt_observations_since_last_ect_computation_ = 0;
|
|
new_throughput_observations_since_last_ect_computation_ = 0;
|
|
transport_rtt_observation_count_last_ect_computation_ = 0;
|
|
end_to_end_rtt_observation_count_at_last_ect_computation_ = 0;
|
|
last_socket_watcher_rtt_notification_ = base::TimeTicks();
|
|
cached_estimate_applied_ = false;
|
|
|
|
GatherEstimatesForNextConnectionType();
|
|
throughput_analyzer_->OnConnectionTypeChanged();
|
|
}
|
|
|
|
void NetworkQualityEstimator::GatherEstimatesForNextConnectionType() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
|
if (get_network_id_asynchronously_) {
|
|
// Doing PostTaskAndReplyWithResult by handle because it requires the result
|
|
// type have a default constructor and nqe::internal::NetworkID does not
|
|
// have that.
|
|
g_get_network_id_task_runner.Get()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(
|
|
[](scoped_refptr<base::TaskRunner> reply_task_runner,
|
|
base::OnceCallback<void(const nqe::internal::NetworkID&)>
|
|
reply_callback) {
|
|
reply_task_runner->PostTask(
|
|
FROM_HERE, base::BindOnce(std::move(reply_callback),
|
|
DoGetCurrentNetworkID(nullptr)));
|
|
},
|
|
base::SingleThreadTaskRunner::GetCurrentDefault(),
|
|
base::BindOnce(&NetworkQualityEstimator::
|
|
ContinueGatherEstimatesForNextConnectionType,
|
|
weak_ptr_factory_.GetWeakPtr())));
|
|
return;
|
|
}
|
|
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
|
|
|
ContinueGatherEstimatesForNextConnectionType(GetCurrentNetworkID());
|
|
}
|
|
|
|
void NetworkQualityEstimator::ContinueGatherEstimatesForNextConnectionType(
|
|
const nqe::internal::NetworkID& network_id) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
// Update the local state as part of preparation for the new connection.
|
|
current_network_id_ = network_id;
|
|
|
|
// Read any cached estimates for the new network. If cached estimates are
|
|
// unavailable, add the default estimates.
|
|
if (!ReadCachedNetworkQualityEstimate())
|
|
AddDefaultEstimates();
|
|
|
|
ComputeEffectiveConnectionType();
|
|
}
|
|
|
|
void NetworkQualityEstimator::ComputeEffectiveConnectionType() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
const base::TimeTicks now = tick_clock_->NowTicks();
|
|
|
|
const EffectiveConnectionType past_type = effective_connection_type_;
|
|
last_effective_connection_type_computation_ = now;
|
|
|
|
base::TimeDelta http_rtt = nqe::internal::InvalidRTT();
|
|
base::TimeDelta transport_rtt = nqe::internal::InvalidRTT();
|
|
base::TimeDelta end_to_end_rtt = nqe::internal::InvalidRTT();
|
|
int32_t downstream_throughput_kbps = nqe::internal::INVALID_RTT_THROUGHPUT;
|
|
|
|
effective_connection_type_ = GetRecentEffectiveConnectionTypeUsingMetrics(
|
|
&http_rtt, &transport_rtt, &end_to_end_rtt, &downstream_throughput_kbps,
|
|
&transport_rtt_observation_count_last_ect_computation_,
|
|
&end_to_end_rtt_observation_count_at_last_ect_computation_);
|
|
|
|
network_quality_ = nqe::internal::NetworkQuality(http_rtt, transport_rtt,
|
|
downstream_throughput_kbps);
|
|
ClampKbpsBasedOnEct();
|
|
if (network_quality_.http_rtt() != nqe::internal::InvalidRTT()) {
|
|
UMA_HISTOGRAM_TIMES("NQE.RTT.OnECTComputation",
|
|
network_quality_.http_rtt());
|
|
}
|
|
|
|
end_to_end_rtt_ = absl::nullopt;
|
|
if (end_to_end_rtt != nqe::internal::InvalidRTT()) {
|
|
end_to_end_rtt_ = end_to_end_rtt;
|
|
}
|
|
|
|
NotifyObserversOfRTTOrThroughputComputed();
|
|
|
|
if (past_type != effective_connection_type_)
|
|
NotifyObserversOfEffectiveConnectionTypeChanged();
|
|
|
|
event_creator_.MaybeAddNetworkQualityChangedEventToNetLog(
|
|
effective_connection_type_, network_quality_);
|
|
|
|
rtt_observations_size_at_last_ect_computation_ =
|
|
rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_HTTP].Size() +
|
|
rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT]
|
|
.Size();
|
|
throughput_observations_size_at_last_ect_computation_ =
|
|
http_downstream_throughput_kbps_observations_.Size();
|
|
new_rtt_observations_since_last_ect_computation_ = 0;
|
|
new_throughput_observations_since_last_ect_computation_ = 0;
|
|
}
|
|
|
|
absl::optional<net::EffectiveConnectionType>
|
|
NetworkQualityEstimator::GetOverrideECT() const {
|
|
return absl::nullopt;
|
|
}
|
|
|
|
void NetworkQualityEstimator::ClampKbpsBasedOnEct() {
|
|
// No need to clamp when ECT is unknown or if the connection speed is fast.
|
|
if (effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_UNKNOWN ||
|
|
effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_OFFLINE ||
|
|
effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_4G) {
|
|
return;
|
|
}
|
|
|
|
if (params_->upper_bound_typical_kbps_multiplier() <= 0.0)
|
|
return;
|
|
|
|
DCHECK_LT(0, params_->TypicalNetworkQuality(effective_connection_type_)
|
|
.downstream_throughput_kbps());
|
|
// For a given ECT, upper bound on Kbps can't be less than the typical Kbps
|
|
// for that ECT.
|
|
DCHECK_LE(1.0, params_->upper_bound_typical_kbps_multiplier());
|
|
|
|
DCHECK(effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_SLOW_2G ||
|
|
effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_2G ||
|
|
effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_3G);
|
|
|
|
// Put an upper bound on Kbps.
|
|
network_quality_.set_downstream_throughput_kbps(
|
|
std::min(network_quality_.downstream_throughput_kbps(),
|
|
static_cast<int>(
|
|
params_->TypicalNetworkQuality(effective_connection_type_)
|
|
.downstream_throughput_kbps() *
|
|
params_->upper_bound_typical_kbps_multiplier())));
|
|
}
|
|
|
|
void NetworkQualityEstimator::AdjustHttpRttBasedOnRTTCounts(
|
|
base::TimeDelta* http_rtt) const {
|
|
if (!params_->adjust_rtt_based_on_rtt_counts())
|
|
return;
|
|
|
|
// This is needed only when RTT from TCP sockets or
|
|
// QUIC/H2 connections is unavailable.
|
|
if (transport_rtt_observation_count_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count() ||
|
|
end_to_end_rtt_observation_count_at_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count()) {
|
|
return;
|
|
}
|
|
|
|
// We prefer to use the cached value if it's available and the network change
|
|
// happened recently.
|
|
base::TimeDelta time_since_connection_change =
|
|
tick_clock_->NowTicks() - last_connection_change_;
|
|
if (cached_estimate_applied_ &&
|
|
time_since_connection_change <= base::Minutes(1)) {
|
|
return;
|
|
}
|
|
|
|
// If there are not enough transport RTT samples, end-to-end RTT samples and
|
|
// the cached estimates are unavailble/too stale, then the computed value of
|
|
// HTTP RTT can't be trusted due to hanging GETs. In that case, return the
|
|
// typical HTTP RTT for a fast connection.
|
|
if (current_network_id_.type == net::NetworkChangeNotifier::CONNECTION_NONE) {
|
|
return;
|
|
}
|
|
|
|
base::TimeDelta upper_bound_http_rtt =
|
|
params_->TypicalNetworkQuality(net::EFFECTIVE_CONNECTION_TYPE_4G)
|
|
.http_rtt();
|
|
if (upper_bound_http_rtt > *http_rtt) {
|
|
return;
|
|
}
|
|
|
|
DCHECK_LE(upper_bound_http_rtt, *http_rtt);
|
|
*http_rtt = upper_bound_http_rtt;
|
|
}
|
|
|
|
EffectiveConnectionType NetworkQualityEstimator::GetEffectiveConnectionType()
|
|
const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
absl::optional<net::EffectiveConnectionType> override_ect = GetOverrideECT();
|
|
if (override_ect) {
|
|
return override_ect.value();
|
|
}
|
|
return effective_connection_type_;
|
|
}
|
|
|
|
void NetworkQualityEstimator::UpdateHttpRttUsingAllRttValues(
|
|
base::TimeDelta* http_rtt,
|
|
const base::TimeDelta transport_rtt,
|
|
const base::TimeDelta end_to_end_rtt) const {
|
|
DCHECK(http_rtt);
|
|
|
|
// Use transport RTT to clamp the lower bound on HTTP RTT.
|
|
// To improve accuracy, the transport RTT estimate is used only when the
|
|
// transport RTT estimate was computed using at least
|
|
// |params_->http_rtt_transport_rtt_min_count()| observations.
|
|
if (*http_rtt != nqe::internal::InvalidRTT() &&
|
|
transport_rtt != nqe::internal::InvalidRTT() &&
|
|
transport_rtt_observation_count_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count() &&
|
|
params_->lower_bound_http_rtt_transport_rtt_multiplier() > 0) {
|
|
*http_rtt =
|
|
std::max(*http_rtt,
|
|
transport_rtt *
|
|
params_->lower_bound_http_rtt_transport_rtt_multiplier());
|
|
}
|
|
|
|
// Put lower bound on |http_rtt| using |end_to_end_rtt|.
|
|
if (*http_rtt != nqe::internal::InvalidRTT() &&
|
|
params_->use_end_to_end_rtt() &&
|
|
end_to_end_rtt != nqe::internal::InvalidRTT() &&
|
|
end_to_end_rtt_observation_count_at_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count() &&
|
|
params_->lower_bound_http_rtt_transport_rtt_multiplier() > 0) {
|
|
*http_rtt =
|
|
std::max(*http_rtt,
|
|
end_to_end_rtt *
|
|
params_->lower_bound_http_rtt_transport_rtt_multiplier());
|
|
}
|
|
|
|
// Put upper bound on |http_rtt| using |end_to_end_rtt|.
|
|
if (*http_rtt != nqe::internal::InvalidRTT() &&
|
|
params_->use_end_to_end_rtt() &&
|
|
end_to_end_rtt != nqe::internal::InvalidRTT() &&
|
|
end_to_end_rtt_observation_count_at_last_ect_computation_ >=
|
|
params_->http_rtt_transport_rtt_min_count() &&
|
|
params_->upper_bound_http_rtt_endtoend_rtt_multiplier() > 0) {
|
|
*http_rtt = std::min(
|
|
*http_rtt, end_to_end_rtt *
|
|
params_->upper_bound_http_rtt_endtoend_rtt_multiplier());
|
|
}
|
|
|
|
// Put upper bound on |http_rtt| if there is not enough HTTP RTT samples
|
|
// available.
|
|
AdjustHttpRttBasedOnRTTCounts(http_rtt);
|
|
}
|
|
|
|
EffectiveConnectionType
|
|
NetworkQualityEstimator::GetRecentEffectiveConnectionTypeUsingMetrics(
|
|
base::TimeDelta* http_rtt,
|
|
base::TimeDelta* transport_rtt,
|
|
base::TimeDelta* end_to_end_rtt,
|
|
int32_t* downstream_throughput_kbps,
|
|
size_t* transport_rtt_observation_count,
|
|
size_t* end_to_end_rtt_observation_count) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
*http_rtt = nqe::internal::InvalidRTT();
|
|
*transport_rtt = nqe::internal::InvalidRTT();
|
|
*end_to_end_rtt = nqe::internal::InvalidRTT();
|
|
*downstream_throughput_kbps = nqe::internal::INVALID_RTT_THROUGHPUT;
|
|
|
|
auto forced_ect =
|
|
params_->GetForcedEffectiveConnectionType(current_network_id_.type);
|
|
if (forced_ect) {
|
|
*http_rtt = params_->TypicalNetworkQuality(forced_ect.value()).http_rtt();
|
|
*transport_rtt =
|
|
params_->TypicalNetworkQuality(forced_ect.value()).transport_rtt();
|
|
*downstream_throughput_kbps =
|
|
params_->TypicalNetworkQuality(forced_ect.value())
|
|
.downstream_throughput_kbps();
|
|
return forced_ect.value();
|
|
}
|
|
|
|
// If the device is currently offline, then return
|
|
// EFFECTIVE_CONNECTION_TYPE_OFFLINE.
|
|
if (current_network_id_.type == NetworkChangeNotifier::CONNECTION_NONE &&
|
|
!disable_offline_check_) {
|
|
return EFFECTIVE_CONNECTION_TYPE_OFFLINE;
|
|
}
|
|
|
|
if (force_report_wifi_as_slow_2g_for_testing_ &&
|
|
current_network_id_.type == NetworkChangeNotifier::CONNECTION_WIFI) {
|
|
return EFFECTIVE_CONNECTION_TYPE_SLOW_2G;
|
|
}
|
|
|
|
if (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_HTTP, base::TimeTicks(),
|
|
http_rtt, nullptr)) {
|
|
*http_rtt = nqe::internal::InvalidRTT();
|
|
}
|
|
|
|
if (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_TRANSPORT,
|
|
base::TimeTicks(), transport_rtt,
|
|
transport_rtt_observation_count)) {
|
|
*transport_rtt = nqe::internal::InvalidRTT();
|
|
}
|
|
|
|
if (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_END_TO_END,
|
|
base::TimeTicks(), end_to_end_rtt,
|
|
end_to_end_rtt_observation_count)) {
|
|
*end_to_end_rtt = nqe::internal::InvalidRTT();
|
|
}
|
|
|
|
UpdateHttpRttUsingAllRttValues(http_rtt, *transport_rtt, *end_to_end_rtt);
|
|
|
|
if (!GetRecentDownlinkThroughputKbps(base::TimeTicks(),
|
|
downstream_throughput_kbps)) {
|
|
*downstream_throughput_kbps = nqe::internal::INVALID_RTT_THROUGHPUT;
|
|
}
|
|
|
|
if (*http_rtt == nqe::internal::InvalidRTT()) {
|
|
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
|
|
}
|
|
|
|
if (*http_rtt == nqe::internal::InvalidRTT() &&
|
|
*transport_rtt == nqe::internal::InvalidRTT() &&
|
|
*downstream_throughput_kbps == nqe::internal::INVALID_RTT_THROUGHPUT) {
|
|
// None of the metrics are available.
|
|
return EFFECTIVE_CONNECTION_TYPE_UNKNOWN;
|
|
}
|
|
|
|
// Search from the slowest connection type to the fastest to find the
|
|
// EffectiveConnectionType that best matches the current connection's
|
|
// performance. The match is done by comparing RTT and throughput.
|
|
for (size_t i = 0; i < EFFECTIVE_CONNECTION_TYPE_LAST; ++i) {
|
|
EffectiveConnectionType type = static_cast<EffectiveConnectionType>(i);
|
|
if (i == EFFECTIVE_CONNECTION_TYPE_UNKNOWN)
|
|
continue;
|
|
|
|
const bool estimated_http_rtt_is_higher_than_threshold =
|
|
*http_rtt != nqe::internal::InvalidRTT() &&
|
|
params_->ConnectionThreshold(type).http_rtt() !=
|
|
nqe::internal::InvalidRTT() &&
|
|
*http_rtt >= params_->ConnectionThreshold(type).http_rtt();
|
|
|
|
if (estimated_http_rtt_is_higher_than_threshold)
|
|
return type;
|
|
}
|
|
// Return the fastest connection type.
|
|
return static_cast<EffectiveConnectionType>(EFFECTIVE_CONNECTION_TYPE_LAST -
|
|
1);
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddEffectiveConnectionTypeObserver(
|
|
EffectiveConnectionTypeObserver* observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK(observer);
|
|
effective_connection_type_observer_list_.AddObserver(observer);
|
|
|
|
// Notify the |observer| on the next message pump since |observer| may not
|
|
// be completely set up for receiving the callbacks.
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&NetworkQualityEstimator::
|
|
NotifyEffectiveConnectionTypeObserverIfPresent,
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
// This is safe as `handle` is checked against a map to
|
|
// verify it hasn't been removed before dereferencing.
|
|
base::UnsafeDangling(observer)));
|
|
}
|
|
|
|
void NetworkQualityEstimator::RemoveEffectiveConnectionTypeObserver(
|
|
EffectiveConnectionTypeObserver* observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
effective_connection_type_observer_list_.RemoveObserver(observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddPeerToPeerConnectionsCountObserver(
|
|
PeerToPeerConnectionsCountObserver* observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK(observer);
|
|
peer_to_peer_type_observer_list_.AddObserver(observer);
|
|
|
|
// Notify the |observer| on the next message pump since |observer| may not
|
|
// be completely set up for receiving the callbacks.
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&NetworkQualityEstimator::
|
|
NotifyPeerToPeerConnectionsCountObserverIfPresent,
|
|
weak_ptr_factory_.GetWeakPtr(),
|
|
// This is safe as `handle` is checked against a map to
|
|
// verify it hasn't been removed before dereferencing.
|
|
base::UnsafeDangling(observer)));
|
|
}
|
|
|
|
void NetworkQualityEstimator::RemovePeerToPeerConnectionsCountObserver(
|
|
PeerToPeerConnectionsCountObserver* observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
peer_to_peer_type_observer_list_.RemoveObserver(observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddRTTAndThroughputEstimatesObserver(
|
|
RTTAndThroughputEstimatesObserver* observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK(observer);
|
|
rtt_and_throughput_estimates_observer_list_.AddObserver(observer);
|
|
|
|
// Notify the |observer| on the next message pump since |observer| may not
|
|
// be completely set up for receiving the callbacks.
|
|
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
|
|
FROM_HERE,
|
|
base::BindOnce(&NetworkQualityEstimator::
|
|
NotifyRTTAndThroughputEstimatesObserverIfPresent,
|
|
weak_ptr_factory_.GetWeakPtr(), observer));
|
|
}
|
|
|
|
void NetworkQualityEstimator::RemoveRTTAndThroughputEstimatesObserver(
|
|
RTTAndThroughputEstimatesObserver* observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
rtt_and_throughput_estimates_observer_list_.RemoveObserver(observer);
|
|
}
|
|
|
|
bool NetworkQualityEstimator::GetRecentRTT(
|
|
nqe::internal::ObservationCategory observation_category,
|
|
const base::TimeTicks& start_time,
|
|
base::TimeDelta* rtt,
|
|
size_t* observations_count) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
*rtt = GetRTTEstimateInternal(start_time, observation_category, 50,
|
|
observations_count);
|
|
return (*rtt != nqe::internal::InvalidRTT());
|
|
}
|
|
|
|
bool NetworkQualityEstimator::GetRecentDownlinkThroughputKbps(
|
|
const base::TimeTicks& start_time,
|
|
int32_t* kbps) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
*kbps = GetDownlinkThroughputKbpsEstimateInternal(start_time, 50);
|
|
return (*kbps != nqe::internal::INVALID_RTT_THROUGHPUT);
|
|
}
|
|
|
|
base::TimeDelta NetworkQualityEstimator::GetRTTEstimateInternal(
|
|
base::TimeTicks start_time,
|
|
nqe::internal::ObservationCategory observation_category,
|
|
int percentile,
|
|
size_t* observations_count) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_EQ(nqe::internal::OBSERVATION_CATEGORY_COUNT,
|
|
std::size(rtt_ms_observations_));
|
|
|
|
// RTT observations are sorted by duration from shortest to longest, thus
|
|
// a higher percentile RTT will have a longer RTT than a lower percentile.
|
|
switch (observation_category) {
|
|
case nqe::internal::OBSERVATION_CATEGORY_HTTP:
|
|
case nqe::internal::OBSERVATION_CATEGORY_TRANSPORT:
|
|
case nqe::internal::OBSERVATION_CATEGORY_END_TO_END:
|
|
return base::Milliseconds(
|
|
rtt_ms_observations_[observation_category]
|
|
.GetPercentile(start_time, current_network_id_.signal_strength,
|
|
percentile, observations_count)
|
|
.value_or(nqe::internal::INVALID_RTT_THROUGHPUT));
|
|
case nqe::internal::OBSERVATION_CATEGORY_COUNT:
|
|
NOTREACHED();
|
|
return base::TimeDelta();
|
|
}
|
|
}
|
|
|
|
int32_t NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimateInternal(
|
|
const base::TimeTicks& start_time,
|
|
int percentile) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// Throughput observations are sorted by kbps from slowest to fastest,
|
|
// thus a higher percentile throughput will be faster than a lower one.
|
|
return http_downstream_throughput_kbps_observations_
|
|
.GetPercentile(start_time, current_network_id_.signal_strength,
|
|
100 - percentile, nullptr)
|
|
.value_or(nqe::internal::INVALID_RTT_THROUGHPUT);
|
|
}
|
|
|
|
nqe::internal::NetworkID NetworkQualityEstimator::GetCurrentNetworkID() const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// TODO(tbansal): crbug.com/498068 Add NetworkQualityEstimatorAndroid class
|
|
// that overrides this method on the Android platform.
|
|
|
|
return DoGetCurrentNetworkID(params_.get());
|
|
}
|
|
|
|
bool NetworkQualityEstimator::ReadCachedNetworkQualityEstimate() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!params_->persistent_cache_reading_enabled())
|
|
return false;
|
|
|
|
nqe::internal::CachedNetworkQuality cached_network_quality;
|
|
|
|
const bool cached_estimate_available = network_quality_store_->GetById(
|
|
current_network_id_, &cached_network_quality);
|
|
|
|
if (!cached_estimate_available) {
|
|
return false;
|
|
}
|
|
|
|
EffectiveConnectionType effective_connection_type =
|
|
cached_network_quality.effective_connection_type();
|
|
|
|
if (effective_connection_type == EFFECTIVE_CONNECTION_TYPE_UNKNOWN ||
|
|
effective_connection_type == EFFECTIVE_CONNECTION_TYPE_OFFLINE ||
|
|
effective_connection_type == EFFECTIVE_CONNECTION_TYPE_LAST) {
|
|
return false;
|
|
}
|
|
|
|
nqe::internal::NetworkQuality network_quality =
|
|
cached_network_quality.network_quality();
|
|
|
|
bool update_network_quality_store = false;
|
|
|
|
// Populate |network_quality| with synthetic RTT and throughput observations
|
|
// if they are missing.
|
|
if (network_quality.http_rtt().InMilliseconds() ==
|
|
nqe::internal::INVALID_RTT_THROUGHPUT) {
|
|
network_quality.set_http_rtt(
|
|
params_->TypicalNetworkQuality(effective_connection_type).http_rtt());
|
|
update_network_quality_store = true;
|
|
}
|
|
|
|
if (network_quality.transport_rtt().InMilliseconds() ==
|
|
nqe::internal::INVALID_RTT_THROUGHPUT) {
|
|
network_quality.set_transport_rtt(
|
|
params_->TypicalNetworkQuality(effective_connection_type)
|
|
.transport_rtt());
|
|
update_network_quality_store = true;
|
|
}
|
|
|
|
if (network_quality.downstream_throughput_kbps() ==
|
|
nqe::internal::INVALID_RTT_THROUGHPUT) {
|
|
network_quality.set_downstream_throughput_kbps(
|
|
params_->TypicalNetworkQuality(effective_connection_type)
|
|
.downstream_throughput_kbps());
|
|
update_network_quality_store = true;
|
|
}
|
|
|
|
if (update_network_quality_store) {
|
|
network_quality_store_->Add(current_network_id_,
|
|
nqe::internal::CachedNetworkQuality(
|
|
tick_clock_->NowTicks(), network_quality,
|
|
effective_connection_type));
|
|
}
|
|
|
|
Observation http_rtt_observation(
|
|
network_quality.http_rtt().InMilliseconds(), tick_clock_->NowTicks(),
|
|
INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE);
|
|
AddAndNotifyObserversOfRTT(http_rtt_observation);
|
|
|
|
Observation transport_rtt_observation(
|
|
network_quality.transport_rtt().InMilliseconds(), tick_clock_->NowTicks(),
|
|
INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE);
|
|
AddAndNotifyObserversOfRTT(transport_rtt_observation);
|
|
|
|
Observation througphput_observation(
|
|
network_quality.downstream_throughput_kbps(), tick_clock_->NowTicks(),
|
|
INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE);
|
|
AddAndNotifyObserversOfThroughput(througphput_observation);
|
|
|
|
ComputeEffectiveConnectionType();
|
|
return true;
|
|
}
|
|
|
|
void NetworkQualityEstimator::SetTickClockForTesting(
|
|
const base::TickClock* tick_clock) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
tick_clock_ = tick_clock;
|
|
for (auto& rtt_ms_observation : rtt_ms_observations_)
|
|
rtt_ms_observation.SetTickClockForTesting(tick_clock_); // IN-TEST
|
|
http_downstream_throughput_kbps_observations_.SetTickClockForTesting(
|
|
tick_clock_);
|
|
throughput_analyzer_->SetTickClockForTesting(tick_clock_);
|
|
watcher_factory_->SetTickClockForTesting(tick_clock_);
|
|
}
|
|
|
|
void NetworkQualityEstimator::OnUpdatedTransportRTTAvailable(
|
|
SocketPerformanceWatcherFactory::Protocol protocol,
|
|
const base::TimeDelta& rtt,
|
|
const absl::optional<nqe::internal::IPHash>& host) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_LT(nqe::internal::INVALID_RTT_THROUGHPUT, rtt.InMilliseconds());
|
|
Observation observation(rtt.InMilliseconds(), tick_clock_->NowTicks(),
|
|
current_network_id_.signal_strength,
|
|
ProtocolSourceToObservationSource(protocol), host);
|
|
AddAndNotifyObserversOfRTT(observation);
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddAndNotifyObserversOfRTT(
|
|
const Observation& observation) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_NE(nqe::internal::InvalidRTT(),
|
|
base::Milliseconds(observation.value()));
|
|
DCHECK_GT(NETWORK_QUALITY_OBSERVATION_SOURCE_MAX, observation.source());
|
|
|
|
if (!ShouldAddObservation(observation))
|
|
return;
|
|
|
|
MaybeUpdateCachedEstimateApplied(
|
|
observation,
|
|
&rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_HTTP]);
|
|
MaybeUpdateCachedEstimateApplied(
|
|
observation,
|
|
&rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT]);
|
|
++new_rtt_observations_since_last_ect_computation_;
|
|
|
|
std::vector<nqe::internal::ObservationCategory> observation_categories =
|
|
observation.GetObservationCategories();
|
|
for (nqe::internal::ObservationCategory observation_category :
|
|
observation_categories) {
|
|
rtt_ms_observations_[observation_category].AddObservation(observation);
|
|
}
|
|
|
|
if (observation.source() == NETWORK_QUALITY_OBSERVATION_SOURCE_TCP ||
|
|
observation.source() == NETWORK_QUALITY_OBSERVATION_SOURCE_QUIC) {
|
|
last_socket_watcher_rtt_notification_ = tick_clock_->NowTicks();
|
|
}
|
|
|
|
UMA_HISTOGRAM_ENUMERATION("NQE.RTT.ObservationSource", observation.source(),
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_MAX);
|
|
|
|
// Maybe recompute the effective connection type since a new RTT observation
|
|
// is available.
|
|
if (observation.source() !=
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE &&
|
|
observation.source() !=
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE) {
|
|
MaybeComputeEffectiveConnectionType();
|
|
}
|
|
for (auto& observer : rtt_observer_list_) {
|
|
observer.OnRTTObservation(observation.value(), observation.timestamp(),
|
|
observation.source());
|
|
}
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddAndNotifyObserversOfThroughput(
|
|
const Observation& observation) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_NE(nqe::internal::INVALID_RTT_THROUGHPUT, observation.value());
|
|
DCHECK_GT(NETWORK_QUALITY_OBSERVATION_SOURCE_MAX, observation.source());
|
|
DCHECK_EQ(1u, observation.GetObservationCategories().size());
|
|
DCHECK_EQ(nqe::internal::OBSERVATION_CATEGORY_HTTP,
|
|
observation.GetObservationCategories().front());
|
|
|
|
if (!ShouldAddObservation(observation))
|
|
return;
|
|
|
|
MaybeUpdateCachedEstimateApplied(
|
|
observation, &http_downstream_throughput_kbps_observations_);
|
|
++new_throughput_observations_since_last_ect_computation_;
|
|
http_downstream_throughput_kbps_observations_.AddObservation(observation);
|
|
|
|
// Maybe recompute the effective connection type since a new throughput
|
|
// observation is available.
|
|
if (observation.source() !=
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE &&
|
|
observation.source() !=
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE) {
|
|
MaybeComputeEffectiveConnectionType();
|
|
}
|
|
for (auto& observer : throughput_observer_list_) {
|
|
observer.OnThroughputObservation(
|
|
observation.value(), observation.timestamp(), observation.source());
|
|
}
|
|
}
|
|
|
|
void NetworkQualityEstimator::OnNewThroughputObservationAvailable(
|
|
int32_t downstream_kbps) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (downstream_kbps <= 0)
|
|
return;
|
|
|
|
DCHECK_NE(nqe::internal::INVALID_RTT_THROUGHPUT, downstream_kbps);
|
|
|
|
Observation throughput_observation(downstream_kbps, tick_clock_->NowTicks(),
|
|
current_network_id_.signal_strength,
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP);
|
|
AddAndNotifyObserversOfThroughput(throughput_observation);
|
|
}
|
|
|
|
bool NetworkQualityEstimator::ShouldComputeEffectiveConnectionType() const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_EQ(nqe::internal::OBSERVATION_CATEGORY_COUNT,
|
|
std::size(rtt_ms_observations_));
|
|
|
|
const base::TimeTicks now = tick_clock_->NowTicks();
|
|
// Recompute effective connection type only if
|
|
// |effective_connection_type_recomputation_interval_| has passed since it was
|
|
// last computed or a connection change event was observed since the last
|
|
// computation. Strict inequalities are used to ensure that effective
|
|
// connection type is recomputed on connection change events even if the clock
|
|
// has not updated.
|
|
if (now - last_effective_connection_type_computation_ >=
|
|
effective_connection_type_recomputation_interval_) {
|
|
return true;
|
|
}
|
|
|
|
if (last_connection_change_ >= last_effective_connection_type_computation_) {
|
|
return true;
|
|
}
|
|
|
|
// Recompute the effective connection type if the previously computed
|
|
// effective connection type was unknown.
|
|
if (effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_UNKNOWN) {
|
|
return true;
|
|
}
|
|
|
|
// Recompute the effective connection type if the number of samples
|
|
// available now are 50% more than the number of samples that were
|
|
// available when the effective connection type was last computed.
|
|
if (rtt_observations_size_at_last_ect_computation_ * 1.5 <
|
|
(rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_HTTP].Size() +
|
|
rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT]
|
|
.Size())) {
|
|
return true;
|
|
}
|
|
|
|
if (throughput_observations_size_at_last_ect_computation_ * 1.5 <
|
|
http_downstream_throughput_kbps_observations_.Size()) {
|
|
return true;
|
|
}
|
|
|
|
if ((new_rtt_observations_since_last_ect_computation_ +
|
|
new_throughput_observations_since_last_ect_computation_) >=
|
|
params_->count_new_observations_received_compute_ect()) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void NetworkQualityEstimator::MaybeComputeEffectiveConnectionType() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!ShouldComputeEffectiveConnectionType())
|
|
return;
|
|
ComputeEffectiveConnectionType();
|
|
}
|
|
|
|
void NetworkQualityEstimator::
|
|
NotifyObserversOfEffectiveConnectionTypeChanged() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_NE(EFFECTIVE_CONNECTION_TYPE_LAST, effective_connection_type_);
|
|
|
|
absl::optional<net::EffectiveConnectionType> override_ect = GetOverrideECT();
|
|
|
|
// TODO(tbansal): Add hysteresis in the notification.
|
|
for (auto& observer : effective_connection_type_observer_list_)
|
|
observer.OnEffectiveConnectionTypeChanged(
|
|
override_ect ? override_ect.value() : effective_connection_type_);
|
|
// Add the estimates of the current network to the cache store.
|
|
network_quality_store_->Add(current_network_id_,
|
|
nqe::internal::CachedNetworkQuality(
|
|
tick_clock_->NowTicks(), network_quality_,
|
|
effective_connection_type_));
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyObserversOfRTTOrThroughputComputed() const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
// TODO(tbansal): Add hysteresis in the notification.
|
|
for (auto& observer : rtt_and_throughput_estimates_observer_list_) {
|
|
observer.OnRTTOrThroughputEstimatesComputed(
|
|
network_quality_.http_rtt(), network_quality_.transport_rtt(),
|
|
network_quality_.downstream_throughput_kbps());
|
|
}
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyEffectiveConnectionTypeObserverIfPresent(
|
|
MayBeDangling<EffectiveConnectionTypeObserver> observer) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!effective_connection_type_observer_list_.HasObserver(observer))
|
|
return;
|
|
|
|
absl::optional<net::EffectiveConnectionType> override_ect = GetOverrideECT();
|
|
if (override_ect) {
|
|
observer->OnEffectiveConnectionTypeChanged(override_ect.value());
|
|
return;
|
|
}
|
|
if (effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_UNKNOWN)
|
|
return;
|
|
observer->OnEffectiveConnectionTypeChanged(effective_connection_type_);
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyPeerToPeerConnectionsCountObserverIfPresent(
|
|
MayBeDangling<PeerToPeerConnectionsCountObserver> observer) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!peer_to_peer_type_observer_list_.HasObserver(observer))
|
|
return;
|
|
observer->OnPeerToPeerConnectionsCountChange(p2p_connections_count_);
|
|
}
|
|
|
|
void NetworkQualityEstimator::NotifyRTTAndThroughputEstimatesObserverIfPresent(
|
|
RTTAndThroughputEstimatesObserver* observer) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (!rtt_and_throughput_estimates_observer_list_.HasObserver(observer))
|
|
return;
|
|
observer->OnRTTOrThroughputEstimatesComputed(
|
|
network_quality_.http_rtt(), network_quality_.transport_rtt(),
|
|
network_quality_.downstream_throughput_kbps());
|
|
}
|
|
|
|
void NetworkQualityEstimator::AddNetworkQualitiesCacheObserver(
|
|
nqe::internal::NetworkQualityStore::NetworkQualitiesCacheObserver*
|
|
observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
network_quality_store_->AddNetworkQualitiesCacheObserver(observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::RemoveNetworkQualitiesCacheObserver(
|
|
nqe::internal::NetworkQualityStore::NetworkQualitiesCacheObserver*
|
|
observer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
network_quality_store_->RemoveNetworkQualitiesCacheObserver(observer);
|
|
}
|
|
|
|
void NetworkQualityEstimator::OnPrefsRead(
|
|
const std::map<nqe::internal::NetworkID,
|
|
nqe::internal::CachedNetworkQuality> read_prefs) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
UMA_HISTOGRAM_COUNTS_1M("NQE.Prefs.ReadSize", read_prefs.size());
|
|
for (auto& it : read_prefs) {
|
|
EffectiveConnectionType effective_connection_type =
|
|
it.second.effective_connection_type();
|
|
if (effective_connection_type == EFFECTIVE_CONNECTION_TYPE_UNKNOWN ||
|
|
effective_connection_type == EFFECTIVE_CONNECTION_TYPE_OFFLINE) {
|
|
continue;
|
|
}
|
|
|
|
// RTT and throughput values are not set in the prefs.
|
|
DCHECK_EQ(nqe::internal::InvalidRTT(),
|
|
it.second.network_quality().http_rtt());
|
|
DCHECK_EQ(nqe::internal::InvalidRTT(),
|
|
it.second.network_quality().transport_rtt());
|
|
DCHECK_EQ(nqe::internal::INVALID_RTT_THROUGHPUT,
|
|
it.second.network_quality().downstream_throughput_kbps());
|
|
|
|
nqe::internal::CachedNetworkQuality cached_network_quality(
|
|
tick_clock_->NowTicks(),
|
|
params_->TypicalNetworkQuality(effective_connection_type),
|
|
effective_connection_type);
|
|
|
|
network_quality_store_->Add(it.first, cached_network_quality);
|
|
}
|
|
ReadCachedNetworkQualityEstimate();
|
|
}
|
|
|
|
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
|
void NetworkQualityEstimator::EnableGetNetworkIdAsynchronously() {
|
|
get_network_id_asynchronously_ = true;
|
|
}
|
|
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
|
|
|
|
absl::optional<base::TimeDelta> NetworkQualityEstimator::GetHttpRTT() const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (network_quality_.http_rtt() == nqe::internal::InvalidRTT())
|
|
return absl::optional<base::TimeDelta>();
|
|
return network_quality_.http_rtt();
|
|
}
|
|
|
|
absl::optional<base::TimeDelta> NetworkQualityEstimator::GetTransportRTT()
|
|
const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (network_quality_.transport_rtt() == nqe::internal::InvalidRTT())
|
|
return absl::optional<base::TimeDelta>();
|
|
return network_quality_.transport_rtt();
|
|
}
|
|
|
|
absl::optional<int32_t> NetworkQualityEstimator::GetDownstreamThroughputKbps()
|
|
const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (network_quality_.downstream_throughput_kbps() ==
|
|
nqe::internal::INVALID_RTT_THROUGHPUT) {
|
|
return absl::optional<int32_t>();
|
|
}
|
|
return network_quality_.downstream_throughput_kbps();
|
|
}
|
|
|
|
void NetworkQualityEstimator::MaybeUpdateCachedEstimateApplied(
|
|
const Observation& observation,
|
|
ObservationBuffer* buffer) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
if (observation.source() !=
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE &&
|
|
observation.source() !=
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE) {
|
|
return;
|
|
}
|
|
|
|
cached_estimate_applied_ = true;
|
|
bool deleted_observation_sources[NETWORK_QUALITY_OBSERVATION_SOURCE_MAX] = {
|
|
false};
|
|
deleted_observation_sources
|
|
[NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_HTTP_FROM_PLATFORM] = true;
|
|
deleted_observation_sources
|
|
[NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_TRANSPORT_FROM_PLATFORM] =
|
|
true;
|
|
|
|
buffer->RemoveObservationsWithSource(deleted_observation_sources);
|
|
}
|
|
|
|
bool NetworkQualityEstimator::ShouldAddObservation(
|
|
const Observation& observation) const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (cached_estimate_applied_ &&
|
|
(observation.source() ==
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_HTTP_FROM_PLATFORM ||
|
|
observation.source() ==
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_TRANSPORT_FROM_PLATFORM)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool NetworkQualityEstimator::ShouldSocketWatcherNotifyRTT(
|
|
base::TimeTicks now) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
return (now - last_socket_watcher_rtt_notification_ >=
|
|
params_->socket_watchers_min_notification_interval());
|
|
}
|
|
|
|
void NetworkQualityEstimator::SimulateNetworkQualityChangeForTesting(
|
|
net::EffectiveConnectionType type) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
params_->SetForcedEffectiveConnectionTypeForTesting(type);
|
|
ComputeEffectiveConnectionType();
|
|
}
|
|
|
|
void NetworkQualityEstimator::ForceReportWifiAsSlow2GForTesting() {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
force_report_wifi_as_slow_2g_for_testing_ = true;
|
|
}
|
|
|
|
void NetworkQualityEstimator::RecordSpdyPingLatency(
|
|
const HostPortPair& host_port_pair,
|
|
base::TimeDelta rtt) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
DCHECK_LT(nqe::internal::INVALID_RTT_THROUGHPUT, rtt.InMilliseconds());
|
|
|
|
Observation observation(rtt.InMilliseconds(), tick_clock_->NowTicks(),
|
|
current_network_id_.signal_strength,
|
|
NETWORK_QUALITY_OBSERVATION_SOURCE_H2_PINGS);
|
|
AddAndNotifyObserversOfRTT(observation);
|
|
}
|
|
|
|
void NetworkQualityEstimator::OnPeerToPeerConnectionsCountChange(
|
|
uint32_t count) {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
|
|
if (p2p_connections_count_ == count)
|
|
return;
|
|
|
|
p2p_connections_count_ = count;
|
|
|
|
for (auto& observer : peer_to_peer_type_observer_list_) {
|
|
observer.OnPeerToPeerConnectionsCountChange(p2p_connections_count_);
|
|
}
|
|
}
|
|
|
|
uint32_t NetworkQualityEstimator::GetPeerToPeerConnectionsCountChange() const {
|
|
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
|
return p2p_connections_count_;
|
|
}
|
|
|
|
} // namespace net
|