| // Copyright 2015 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 "net/nqe/network_quality_estimator.h" |
| |
| #include <algorithm> |
| #include <cmath> |
| #include <limits> |
| #include <utility> |
| |
| #include "base/bind_helpers.h" |
| #include "base/location.h" |
| #include "base/logging.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/single_thread_task_runner.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task/lazy_task_runner.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "base/time/default_tick_clock.h" |
| #include "base/trace_event/trace_event.h" |
| #include "build/build_config.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/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 "net/url_request/url_request_status.h" |
| #include "url/gurl.h" |
| |
| #if defined(OS_ANDROID) |
| #include "net/android/cellular_signal_strength.h" |
| #include "net/android/network_library.h" |
| #endif // OS_ANDROID |
| |
| namespace net { |
| |
| class HostResolver; |
| |
| namespace { |
| |
| #if defined(OS_CHROMEOS) |
| // 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::LazySequencedTaskRunner g_get_network_id_task_runner = |
| LAZY_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(); |
| } |
| |
| // Returns the suffix of the histogram that should be used for recording the |
| // accuracy when the observed RTT is |observed_rtt|. The width of the intervals |
| // are in exponentially increasing order. |
| const char* GetHistogramSuffixObservedRTT(const base::TimeDelta& observed_rtt) { |
| const int32_t rtt_milliseconds = observed_rtt.InMilliseconds(); |
| DCHECK_GE(rtt_milliseconds, 0); |
| |
| // The values here should remain synchronized with the suffixes specified in |
| // histograms.xml. |
| static const char* const kSuffixes[] = { |
| "0_20", "20_60", "60_140", "140_300", "300_620", |
| "620_1260", "1260_2540", "2540_5100", "5100_Infinity"}; |
| for (size_t i = 0; i < arraysize(kSuffixes) - 1; ++i) { |
| if (rtt_milliseconds <= (20 * (2 << i) - 20)) |
| return kSuffixes[i]; |
| } |
| return kSuffixes[arraysize(kSuffixes) - 1]; |
| } |
| |
| // Returns the suffix of the histogram that should be used for recording the |
| // accuracy when the observed throughput in kilobits per second is |
| // |observed_throughput_kbps|. The width of the intervals are in exponentially |
| // increasing order. |
| const char* GetHistogramSuffixObservedThroughput( |
| const int32_t& observed_throughput_kbps) { |
| DCHECK_GE(observed_throughput_kbps, 0); |
| |
| // The values here should remain synchronized with the suffixes specified in |
| // histograms.xml. |
| static const char* const kSuffixes[] = { |
| "0_20", "20_60", "60_140", "140_300", "300_620", |
| "620_1260", "1260_2540", "2540_5100", "5100_Infinity"}; |
| for (size_t i = 0; i < arraysize(kSuffixes) - 1; ++i) { |
| if (observed_throughput_kbps <= (20 * (2 << i) - 20)) |
| return kSuffixes[i]; |
| } |
| return kSuffixes[arraysize(kSuffixes) - 1]; |
| } |
| |
| void RecordRTTAccuracy(base::StringPiece prefix, |
| int32_t metric, |
| base::TimeDelta measuring_duration, |
| base::TimeDelta observed_rtt) { |
| const std::string histogram_name = |
| base::StringPrintf("%s.EstimatedObservedDiff.%s.%d.%s", prefix.data(), |
| metric >= 0 ? "Positive" : "Negative", |
| static_cast<int32_t>(measuring_duration.InSeconds()), |
| GetHistogramSuffixObservedRTT(observed_rtt)); |
| |
| base::HistogramBase* histogram = base::Histogram::FactoryGet( |
| histogram_name, 1, 10 * 1000 /* 10 seconds */, 50 /* Number of buckets */, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| histogram->Add(std::abs(metric)); |
| } |
| |
| void RecordThroughputAccuracy(const char* prefix, |
| int32_t metric, |
| base::TimeDelta measuring_duration, |
| int32_t observed_throughput_kbps) { |
| const std::string histogram_name = base::StringPrintf( |
| "%s.EstimatedObservedDiff.%s.%d.%s", prefix, |
| metric >= 0 ? "Positive" : "Negative", |
| static_cast<int32_t>(measuring_duration.InSeconds()), |
| GetHistogramSuffixObservedThroughput(observed_throughput_kbps)); |
| |
| base::HistogramBase* histogram = base::Histogram::FactoryGet( |
| histogram_name, 1, 1000 * 1000 /* 1 Gbps */, 50 /* Number of buckets */, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| histogram->Add(std::abs(metric)); |
| } |
| |
| void RecordEffectiveConnectionTypeAccuracy( |
| const char* prefix, |
| int32_t metric, |
| base::TimeDelta measuring_duration, |
| EffectiveConnectionType observed_effective_connection_type) { |
| const std::string histogram_name = |
| base::StringPrintf("%s.EstimatedObservedDiff.%s.%d.%s", prefix, |
| metric >= 0 ? "Positive" : "Negative", |
| static_cast<int32_t>(measuring_duration.InSeconds()), |
| DeprecatedGetNameForEffectiveConnectionType( |
| observed_effective_connection_type)); |
| |
| base::HistogramBase* histogram = base::Histogram::FactoryGet( |
| histogram_name, 0, EFFECTIVE_CONNECTION_TYPE_LAST, |
| EFFECTIVE_CONNECTION_TYPE_LAST /* Number of buckets */, |
| base::HistogramBase::kUmaTargetedHistogramFlag); |
| histogram->Add(std::abs(metric)); |
| } |
| |
| nqe::internal::NetworkID DoGetCurrentNetworkID() { |
| // It is possible that the connection type changed between when |
| // GetConnectionType() was called and when the API to determine the |
| // network name was called. Check if that happened and retry until the |
| // connection type stabilizes. This is an imperfect solution but should |
| // capture majority of cases, and should not significantly affect estimates |
| // (that are approximate to begin with). |
| while (true) { |
| nqe::internal::NetworkID network_id( |
| NetworkChangeNotifier::GetConnectionType(), std::string(), INT32_MIN); |
| |
| switch (network_id.type) { |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN: |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_NONE: |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_BLUETOOTH: |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET: |
| break; |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI: |
| #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_WIN) |
| network_id.id = GetWifiSSID(); |
| #endif |
| break; |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_2G: |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_3G: |
| case NetworkChangeNotifier::ConnectionType::CONNECTION_4G: |
| #if defined(OS_ANDROID) |
| network_id.id = android::GetTelephonyNetworkOperator(); |
| #endif |
| break; |
| default: |
| NOTREACHED() << "Unexpected connection type = " << network_id.type; |
| break; |
| } |
| |
| if (network_id.type == NetworkChangeNotifier::GetConnectionType()) |
| return network_id; |
| } |
| NOTREACHED(); |
| } |
| |
| } // namespace |
| |
| NetworkQualityEstimator::NetworkQualityEstimator( |
| std::unique_ptr<NetworkQualityEstimatorParams> params, |
| NetLog* net_log) |
| : params_(std::move(params)), |
| end_to_end_rtt_observation_count_at_last_ect_computation_(0), |
| use_localhost_requests_(false), |
| disable_offline_check_(false), |
| 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(), |
| params_->weight_multiplier_per_signal_strength_level()), |
| effective_connection_type_at_last_main_frame_( |
| EFFECTIVE_CONNECTION_TYPE_UNKNOWN), |
| effective_connection_type_recomputation_interval_( |
| base::TimeDelta::FromSeconds(10)), |
| rtt_observations_size_at_last_ect_computation_(0), |
| throughput_observations_size_at_last_ect_computation_(0), |
| transport_rtt_observation_count_last_ect_computation_(0), |
| new_rtt_observations_since_last_ect_computation_(0), |
| new_throughput_observations_since_last_ect_computation_(0), |
| increase_in_transport_rtt_updater_posted_(false), |
| effective_connection_type_(EFFECTIVE_CONNECTION_TYPE_UNKNOWN), |
| cached_estimate_applied_(false), |
| net_log_(NetLogWithSource::Make( |
| net_log, |
| net::NetLogSourceType::NETWORK_QUALITY_ESTIMATOR)), |
| event_creator_(net_log_), |
| weak_ptr_factory_(this) { |
| rtt_ms_observations_.reserve(nqe::internal::OBSERVATION_CATEGORY_COUNT); |
| for (int i = 0; i < nqe::internal::OBSERVATION_CATEGORY_COUNT; ++i) { |
| rtt_ms_observations_.push_back(ObservationBuffer( |
| params_.get(), tick_clock_, params_->weight_multiplier_per_second(), |
| params_->weight_multiplier_per_signal_strength_level())); |
| } |
| |
| network_quality_store_.reset(new nqe::internal::NetworkQualityStore()); |
| NetworkChangeNotifier::AddConnectionTypeObserver(this); |
| throughput_analyzer_.reset(new nqe::internal::ThroughputAnalyzer( |
| this, params_.get(), base::ThreadTaskRunnerHandle::Get(), |
| base::Bind(&NetworkQualityEstimator::OnNewThroughputObservationAvailable, |
| // It is safe to use base::Unretained here since |
| // |throughput_analyzer_| is owned by |this|. This ensures that |
| // |throughput_analyzer_| will be destroyed before |this|. |
| base::Unretained(this)), |
| tick_clock_, net_log_)); |
| |
| watcher_factory_.reset(new nqe::internal::SocketWatcherFactory( |
| base::ThreadTaskRunnerHandle::Get(), |
| 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::ThreadTaskRunnerHandle::Get()). |
| // Use WeakPtr() to avoid crashes where the socket watcher is destroyed |
| // after |this| is destroyed. |
| base::Bind(&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::ThreadTaskRunnerHandle::Get()). 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::Bind(&NetworkQualityEstimator::ShouldSocketWatcherNotifyRTT, |
| base::Unretained(this)), |
| tick_clock_)); |
| |
| // Record accuracy after a 15 second interval. The values used here must |
| // remain in sync with the suffixes specified in |
| // tools/metrics/histograms/histograms.xml. |
| accuracy_recording_intervals_.push_back(base::TimeDelta::FromSeconds(15)); |
| |
| GatherEstimatesForNextConnectionType(); |
| } |
| |
| void NetworkQualityEstimator::AddDefaultEstimates() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_checker_); |
| NetworkChangeNotifier::RemoveConnectionTypeObserver(this); |
| } |
| |
| const std::vector<base::TimeDelta>& |
| NetworkQualityEstimator::GetAccuracyRecordingIntervals() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return accuracy_recording_intervals_; |
| } |
| |
| void NetworkQualityEstimator::NotifyStartTransaction( |
| const URLRequest& request) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!RequestSchemeIsHTTPOrHTTPS(request)) |
| return; |
| |
| // Update |estimated_quality_at_last_main_frame_| if this is a main frame |
| // request. |
| // TODO(tbansal): Refactor this to a separate method. |
| if (request.load_flags() & LOAD_MAIN_FRAME_DEPRECATED) { |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| last_main_frame_request_ = now; |
| |
| ComputeEffectiveConnectionType(); |
| effective_connection_type_at_last_main_frame_ = effective_connection_type_; |
| estimated_quality_at_last_main_frame_ = network_quality_; |
| |
| // Post the tasks which will run in the future and record the estimation |
| // accuracy based on the observations received between now and the time of |
| // task execution. Posting the task at different intervals makes it |
| // possible to measure the accuracy by comparing the estimate with the |
| // observations received over intervals of varying durations. |
| for (const base::TimeDelta& measuring_delay : |
| GetAccuracyRecordingIntervals()) { |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&NetworkQualityEstimator::RecordAccuracyAfterMainFrame, |
| weak_ptr_factory_.GetWeakPtr(), measuring_delay), |
| measuring_delay); |
| } |
| } else { |
| MaybeComputeEffectiveConnectionType(); |
| } |
| throughput_analyzer_->NotifyStartTransaction(request); |
| } |
| |
| bool NetworkQualityEstimator::IsHangingRequest( |
| base::TimeDelta observed_http_rtt) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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()) { |
| UMA_HISTOGRAM_TIMES("NQE.RTT.NotAHangingRequest.EndToEndRTT", |
| observed_http_rtt); |
| 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::TimeDelta::FromSeconds(10)))) { |
| // If there are sufficient number of transport RTT samples available, use |
| // the transport RTT estimate to determine if the request is hanging. |
| UMA_HISTOGRAM_TIMES("NQE.RTT.NotAHangingRequest.TransportRTT", |
| observed_http_rtt); |
| 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::TimeDelta::FromSeconds(10))) { |
| // Use the HTTP RTT estimate to determine if the request is hanging. |
| UMA_HISTOGRAM_TIMES("NQE.RTT.NotAHangingRequest.HttpRTT", |
| observed_http_rtt); |
| return false; |
| } |
| |
| if (observed_http_rtt <= |
| params_->hanging_request_upper_bound_min_http_rtt()) { |
| UMA_HISTOGRAM_TIMES("NQE.RTT.NotAHangingRequest.MinHttpBound", |
| observed_http_rtt); |
| return false; |
| } |
| UMA_HISTOGRAM_TIMES("NQE.RTT.HangingRequest", observed_http_rtt); |
| return true; |
| } |
| |
| void NetworkQualityEstimator::NotifyHeadersReceived(const URLRequest& request) { |
| TRACE_EVENT0(kNetTracingCategory, |
| "NetworkQualityEstimator::NotifyHeadersReceived"); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!RequestSchemeIsHTTPOrHTTPS(request) || |
| !RequestProvidesRTTObservation(request)) { |
| return; |
| } |
| |
| if (request.load_flags() & LOAD_MAIN_FRAME_DEPRECATED) { |
| ComputeEffectiveConnectionType(); |
| RecordMetricsOnMainFrameRequest(); |
| } |
| |
| 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); |
| } |
| |
| void NetworkQualityEstimator::NotifyBytesRead(const URLRequest& request) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| throughput_analyzer_->NotifyBytesRead(request); |
| } |
| |
| void NetworkQualityEstimator::RecordAccuracyAfterMainFrame( |
| base::TimeDelta measuring_duration) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK_EQ(0, measuring_duration.InMilliseconds() % 1000); |
| DCHECK(ContainsValue(GetAccuracyRecordingIntervals(), measuring_duration)); |
| |
| const base::TimeTicks now = tick_clock_->NowTicks(); |
| |
| // Return if the time since |last_main_frame_request_| is less than |
| // |measuring_duration|. This may happen if another main frame request started |
| // during last |measuring_duration|. Returning here ensures that we do not |
| // take inaccurate readings. |
| if (now - last_main_frame_request_ < measuring_duration) |
| return; |
| |
| // Return if the time since |last_main_frame_request_| is off by a factor of |
| // 2. This can happen if the task is executed much later than its scheduled |
| // time. Returning here ensures that we do not take inaccurate readings. |
| if (now - last_main_frame_request_ > 2 * measuring_duration) |
| return; |
| |
| // Do not record accuracy if there was a connection change since the last main |
| // frame request. |
| if (last_main_frame_request_ <= last_connection_change_) |
| return; |
| |
| base::TimeDelta recent_http_rtt; |
| if (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_HTTP, |
| last_main_frame_request_, &recent_http_rtt, nullptr)) { |
| recent_http_rtt = nqe::internal::InvalidRTT(); |
| } |
| |
| if (estimated_quality_at_last_main_frame_.http_rtt() != |
| nqe::internal::InvalidRTT() && |
| recent_http_rtt != nqe::internal::InvalidRTT()) { |
| const int estimated_observed_diff_milliseconds = |
| estimated_quality_at_last_main_frame_.http_rtt().InMilliseconds() - |
| recent_http_rtt.InMilliseconds(); |
| |
| RecordRTTAccuracy("NQE.Accuracy.HttpRTT", |
| estimated_observed_diff_milliseconds, measuring_duration, |
| recent_http_rtt); |
| } |
| |
| base::TimeDelta recent_transport_rtt; |
| if (estimated_quality_at_last_main_frame_.transport_rtt() != |
| nqe::internal::InvalidRTT() && |
| GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_TRANSPORT, |
| last_main_frame_request_, &recent_transport_rtt, nullptr)) { |
| const int estimated_observed_diff_milliseconds = |
| estimated_quality_at_last_main_frame_.transport_rtt().InMilliseconds() - |
| recent_transport_rtt.InMilliseconds(); |
| |
| RecordRTTAccuracy("NQE.Accuracy.TransportRTT", |
| estimated_observed_diff_milliseconds, measuring_duration, |
| recent_transport_rtt); |
| } |
| |
| int32_t recent_downstream_throughput_kbps; |
| if (estimated_quality_at_last_main_frame_.downstream_throughput_kbps() != |
| nqe::internal::INVALID_RTT_THROUGHPUT && |
| GetRecentDownlinkThroughputKbps(last_main_frame_request_, |
| &recent_downstream_throughput_kbps)) { |
| const int estimated_observed_diff = |
| estimated_quality_at_last_main_frame_.downstream_throughput_kbps() - |
| recent_downstream_throughput_kbps; |
| |
| RecordThroughputAccuracy("NQE.Accuracy.DownstreamThroughputKbps", |
| estimated_observed_diff, measuring_duration, |
| recent_downstream_throughput_kbps); |
| } |
| |
| EffectiveConnectionType recent_effective_connection_type = |
| GetRecentEffectiveConnectionType(last_main_frame_request_); |
| if (effective_connection_type_at_last_main_frame_ != |
| EFFECTIVE_CONNECTION_TYPE_UNKNOWN && |
| recent_effective_connection_type != EFFECTIVE_CONNECTION_TYPE_UNKNOWN) { |
| const int estimated_observed_diff = |
| static_cast<int>(effective_connection_type_at_last_main_frame_) - |
| static_cast<int>(recent_effective_connection_type); |
| |
| RecordEffectiveConnectionTypeAccuracy( |
| "NQE.Accuracy.EffectiveConnectionType", estimated_observed_diff, |
| measuring_duration, recent_effective_connection_type); |
| } |
| } |
| |
| void NetworkQualityEstimator::NotifyRequestCompleted(const URLRequest& request, |
| int net_error) { |
| TRACE_EVENT0(kNetTracingCategory, |
| "NetworkQualityEstimator::NotifyRequestCompleted"); |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!RequestSchemeIsHTTPOrHTTPS(request)) |
| return; |
| |
| throughput_analyzer_->NotifyRequestCompleted(request); |
| } |
| |
| void NetworkQualityEstimator::NotifyURLRequestDestroyed( |
| const URLRequest& request) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!RequestSchemeIsHTTPOrHTTPS(request)) |
| return; |
| |
| throughput_analyzer_->NotifyRequestCompleted(request); |
| } |
| |
| void NetworkQualityEstimator::AddRTTObserver(RTTObserver* rtt_observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| rtt_observer_list_.AddObserver(rtt_observer); |
| } |
| |
| void NetworkQualityEstimator::RemoveRTTObserver(RTTObserver* rtt_observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| rtt_observer_list_.RemoveObserver(rtt_observer); |
| } |
| |
| void NetworkQualityEstimator::AddThroughputObserver( |
| ThroughputObserver* throughput_observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| throughput_observer_list_.AddObserver(throughput_observer); |
| } |
| |
| void NetworkQualityEstimator::RemoveThroughputObserver( |
| ThroughputObserver* throughput_observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| throughput_observer_list_.RemoveObserver(throughput_observer); |
| } |
| |
| SocketPerformanceWatcherFactory* |
| NetworkQualityEstimator::GetSocketPerformanceWatcherFactory() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| return watcher_factory_.get(); |
| } |
| |
| void NetworkQualityEstimator::SetUseLocalHostRequestsForTesting( |
| bool use_localhost_requests) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_checker_); |
| params_->SetUseSmallResponsesForTesting(use_small_responses); |
| } |
| |
| void NetworkQualityEstimator::DisableOfflineCheckForTesting( |
| bool disable_offline_check) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| disable_offline_check_ = disable_offline_check; |
| } |
| |
| void NetworkQualityEstimator::ReportEffectiveConnectionTypeForTesting( |
| EffectiveConnectionType effective_connection_type) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_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_THREAD(thread_checker_); |
| |
| bool private_network_request = nqe::internal::IsPrivateHost( |
| request.context()->host_resolver(), HostPortPair::FromURL(request.url())); |
| |
| 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_THREAD(thread_checker_); |
| |
| // 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 (int i = 0; i < nqe::internal::OBSERVATION_CATEGORY_COUNT; ++i) |
| rtt_ms_observations_[i].Clear(); |
| |
| #if defined(OS_ANDROID) |
| if (params_->weight_multiplier_per_signal_strength_level() < 1.0 && |
| NetworkChangeNotifier::IsConnectionCellular(current_network_id_.type)) { |
| bool signal_strength_available = |
| min_signal_strength_since_connection_change_ && |
| max_signal_strength_since_connection_change_; |
| UMA_HISTOGRAM_BOOLEAN("NQE.CellularSignalStrength.LevelAvailable", |
| signal_strength_available); |
| |
| if (signal_strength_available) { |
| UMA_HISTOGRAM_COUNTS_100( |
| "NQE.CellularSignalStrength.LevelDifference", |
| max_signal_strength_since_connection_change_.value() - |
| min_signal_strength_since_connection_change_.value()); |
| } |
| } |
| #endif // OS_ANDROID |
| current_network_id_.signal_strength = INT32_MIN; |
| min_signal_strength_since_connection_change_.reset(); |
| max_signal_strength_since_connection_change_.reset(); |
| network_quality_ = nqe::internal::NetworkQuality(); |
| end_to_end_rtt_ = base::nullopt; |
| effective_connection_type_ = EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| effective_connection_type_at_last_main_frame_ = |
| 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(); |
| estimated_quality_at_last_main_frame_ = nqe::internal::NetworkQuality(); |
| cached_estimate_applied_ = false; |
| |
| GatherEstimatesForNextConnectionType(); |
| throughput_analyzer_->OnConnectionTypeChanged(); |
| } |
| |
| void NetworkQualityEstimator::GatherEstimatesForNextConnectionType() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| #if defined(OS_CHROMEOS) |
| 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())); |
| }, |
| base::ThreadTaskRunnerHandle::Get(), |
| base::BindOnce(&NetworkQualityEstimator:: |
| ContinueGatherEstimatesForNextConnectionType, |
| weak_ptr_factory_.GetWeakPtr()))); |
| return; |
| } |
| #endif // defined(OS_CHROMEOS) |
| |
| ContinueGatherEstimatesForNextConnectionType(GetCurrentNetworkID()); |
| } |
| |
| void NetworkQualityEstimator::ContinueGatherEstimatesForNextConnectionType( |
| const nqe::internal::NetworkID& network_id) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| // Update the local state as part of preparation for the new connection. |
| current_network_id_ = network_id; |
| RecordNetworkIDAvailability(); |
| |
| // Read any cached estimates for the new network. If cached estimates are |
| // unavailable, add the default estimates. |
| if (!ReadCachedNetworkQualityEstimate()) |
| AddDefaultEstimates(); |
| |
| ComputeEffectiveConnectionType(); |
| } |
| |
| int32_t NetworkQualityEstimator::GetCurrentSignalStrength() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| #if defined(OS_ANDROID) |
| if (params_->weight_multiplier_per_signal_strength_level() >= 1.0) |
| return INT32_MIN; |
| if (!NetworkChangeNotifier::IsConnectionCellular(current_network_id_.type)) |
| return INT32_MIN; |
| return android::cellular_signal_strength::GetSignalStrengthLevel().value_or( |
| INT32_MIN); |
| #endif // OS_ANDROID |
| |
| return INT32_MIN; |
| } |
| |
| void NetworkQualityEstimator::UpdateSignalStrength() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| int32_t past_signal_strength = current_network_id_.signal_strength; |
| int32_t new_signal_strength = GetCurrentSignalStrength(); |
| |
| // Check if there is no change in the signal strength. |
| if (past_signal_strength == new_signal_strength) |
| return; |
| |
| // Check if the signal strength is unavailable. |
| if (new_signal_strength == INT32_MIN) |
| return; |
| |
| DCHECK(new_signal_strength >= 0 && new_signal_strength <= 4); |
| |
| // Record the network quality we experienced for the previous signal strength |
| // (for when we return to that signal strength). |
| network_quality_store_->Add(current_network_id_, |
| nqe::internal::CachedNetworkQuality( |
| tick_clock_->NowTicks(), network_quality_, |
| effective_connection_type_)); |
| |
| current_network_id_.signal_strength = new_signal_strength; |
| // Update network quality from cached value for new signal strength. |
| ReadCachedNetworkQualityEstimate(); |
| |
| min_signal_strength_since_connection_change_ = |
| std::min(min_signal_strength_since_connection_change_.value_or(INT32_MAX), |
| current_network_id_.signal_strength); |
| max_signal_strength_since_connection_change_ = |
| std::max(max_signal_strength_since_connection_change_.value_or(INT32_MIN), |
| current_network_id_.signal_strength); |
| } |
| |
| void NetworkQualityEstimator::RecordNetworkIDAvailability() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| if (current_network_id_.type == |
| NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI || |
| NetworkChangeNotifier::IsConnectionCellular(current_network_id_.type)) { |
| UMA_HISTOGRAM_BOOLEAN("NQE.NetworkIdAvailable", |
| !current_network_id_.id.empty()); |
| } |
| } |
| |
| void NetworkQualityEstimator::RecordMetricsOnMainFrameRequest() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (estimated_quality_at_last_main_frame_.http_rtt() != |
| nqe::internal::InvalidRTT()) { |
| // Add the 50th percentile value. |
| UMA_HISTOGRAM_TIMES("NQE.MainFrame.RTT.Percentile50", |
| estimated_quality_at_last_main_frame_.http_rtt()); |
| } |
| UMA_HISTOGRAM_BOOLEAN("NQE.EstimateAvailable.MainFrame.RTT", |
| estimated_quality_at_last_main_frame_.http_rtt() != |
| nqe::internal::InvalidRTT()); |
| |
| if (estimated_quality_at_last_main_frame_.transport_rtt() != |
| nqe::internal::InvalidRTT()) { |
| // Add the 50th percentile value. |
| UMA_HISTOGRAM_TIMES("NQE.MainFrame.TransportRTT.Percentile50", |
| estimated_quality_at_last_main_frame_.transport_rtt()); |
| } |
| UMA_HISTOGRAM_BOOLEAN("NQE.EstimateAvailable.MainFrame.TransportRTT", |
| estimated_quality_at_last_main_frame_.transport_rtt() != |
| nqe::internal::InvalidRTT()); |
| |
| if (estimated_quality_at_last_main_frame_.downstream_throughput_kbps() != |
| nqe::internal::INVALID_RTT_THROUGHPUT) { |
| // Add the 50th percentile value. |
| UMA_HISTOGRAM_COUNTS_1M( |
| "NQE.MainFrame.Kbps.Percentile50", |
| estimated_quality_at_last_main_frame_.downstream_throughput_kbps()); |
| } |
| UMA_HISTOGRAM_BOOLEAN( |
| "NQE.EstimateAvailable.MainFrame.Kbps", |
| estimated_quality_at_last_main_frame_.downstream_throughput_kbps() != |
| nqe::internal::INVALID_RTT_THROUGHPUT); |
| |
| UMA_HISTOGRAM_ENUMERATION("NQE.MainFrame.EffectiveConnectionType", |
| effective_connection_type_at_last_main_frame_, |
| EFFECTIVE_CONNECTION_TYPE_LAST); |
| } |
| |
| void NetworkQualityEstimator::ComputeBandwidthDelayProduct() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| // Reset the bandwidth delay product to prevent stale values being returned. |
| bandwidth_delay_product_kbits_.reset(); |
| |
| // Record the bandwidth delay product (BDP) from the 80 percentile throughput |
| // and the 20 percentile transport RTT. Percentiles are reversed for |
| // throughput. The reason for using the 20 percentile transport RTT is to get |
| // an estimate of the true RTT sans the queueing delay. The minimum value of |
| // transport RTT was not used because it is likely to be noisy. For |
| // throughput, the 80 percentile value is considered to get an estimate of the |
| // maximum bandwidth when there is no congestion. The maximum value of |
| // observed throughput was not used because it is likely to be noisy. |
| base::TimeDelta transport_rtt = GetRTTEstimateInternal( |
| base::TimeTicks(), nqe::internal::OBSERVATION_CATEGORY_TRANSPORT, 20, |
| nullptr); |
| if (transport_rtt == nqe::internal::InvalidRTT()) |
| return; |
| |
| int32_t downlink_throughput_kbps = |
| GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 20); |
| if (downlink_throughput_kbps == nqe::internal::INVALID_RTT_THROUGHPUT) |
| return; |
| |
| bandwidth_delay_product_kbits_ = |
| (downlink_throughput_kbps * transport_rtt.InMilliseconds()) / 1000; |
| |
| // Record UMA histograms. |
| UMA_HISTOGRAM_TIMES("NQE.BDPComputationTransportRTT.OnECTComputation", |
| transport_rtt); |
| UMA_HISTOGRAM_COUNTS_1M("NQE.BDPComputationKbps.OnECTComputation", |
| downlink_throughput_kbps); |
| UMA_HISTOGRAM_COUNTS_1M("NQE.BDPKbits.OnECTComputation", |
| bandwidth_delay_product_kbits_.value()); |
| } |
| |
| void NetworkQualityEstimator::IncreaseInTransportRTTUpdater() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| increase_in_transport_rtt_ = ComputeIncreaseInTransportRTT(); |
| |
| // Stop the timer if there was no recent data and |increase_in_transport_rtt_| |
| // could not be computed. This is fine because |increase_in_transport_rtt| can |
| // only be computed if there is recent transport RTT data, and the timer is |
| // restarted when there is a new observation. |
| if (!increase_in_transport_rtt_) { |
| increase_in_transport_rtt_updater_posted_ = false; |
| return; |
| } |
| |
| increase_in_transport_rtt_updater_posted_ = true; |
| base::ThreadTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::Bind(&NetworkQualityEstimator::IncreaseInTransportRTTUpdater, |
| weak_ptr_factory_.GetWeakPtr()), |
| params_->increase_in_transport_rtt_logging_interval()); |
| } |
| |
| base::Optional<int32_t> NetworkQualityEstimator::ComputeIncreaseInTransportRTT() |
| const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| base::TimeTicks now = tick_clock_->NowTicks(); |
| |
| // The time after which the observations are considered to be recent enough to |
| // be a good proxy for the current level of congestion. |
| base::TimeTicks recent_start_time = now - params_->recent_time_threshold(); |
| |
| // Get the median transport RTT observed over the last 5 seconds for each |
| // remote host. This is an estimate of the current RTT which will be compared |
| // to the baseline obtained from historical data to detect an increase in RTT. |
| std::map<nqe::internal::IPHash, int32_t> recent_median_rtts; |
| std::map<nqe::internal::IPHash, size_t> recent_observation_counts; |
| rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT] |
| .GetPercentileForEachHostWithCounts(recent_start_time, 50, base::nullopt, |
| &recent_median_rtts, |
| &recent_observation_counts); |
| |
| if (recent_median_rtts.empty()) |
| return base::nullopt; |
| |
| // The time after which the observations are used to calculate the baseline. |
| // This is needed because the general network characteristics could have |
| // changed over time. |
| base::TimeTicks history_start_time = |
| now - params_->historical_time_threshold(); |
| |
| // Create a set of the remote hosts seen in the recent observations so that |
| // the data can be filtered while calculating the percentiles. |
| std::set<nqe::internal::IPHash> recent_hosts_set; |
| for (const auto& recent_median_rtts_for_host : recent_median_rtts) |
| recent_hosts_set.insert(recent_median_rtts_for_host.first); |
| |
| // Get the minimum transport RTT observed over 1 minute for each remote host. |
| // This is an estimate of the true RTT which will be used as a baseline value |
| // to detect an increase in RTT. The minimum value is used here because the |
| // observed values cannot be lower than the true RTT. The median is used for |
| // the recent data to reduce noise in the calculation. |
| std::map<nqe::internal::IPHash, int32_t> historical_min_rtts; |
| std::map<nqe::internal::IPHash, size_t> historical_observation_counts; |
| rtt_ms_observations_[nqe::internal::OBSERVATION_CATEGORY_TRANSPORT] |
| .GetPercentileForEachHostWithCounts( |
| history_start_time, 0, recent_hosts_set, &historical_min_rtts, |
| &historical_observation_counts); |
| |
| // Calculate the total observation counts for the hosts common to the recent |
| // data and the historical data. |
| size_t total_historical_count = 0; |
| size_t total_recent_count = 0; |
| for (const auto& recent_median_rtts_for_host : recent_median_rtts) { |
| nqe::internal::IPHash host = recent_median_rtts_for_host.first; |
| total_historical_count += historical_observation_counts[host]; |
| total_recent_count += recent_observation_counts[host]; |
| } |
| |
| // Compute the increases in transport RTT for each remote host. Also compute |
| // the weight for each remote host based on the number of observations. |
| double total_weight = 0.0; |
| std::vector<nqe::internal::WeightedObservation> weighted_rtts; |
| for (auto& host : recent_hosts_set) { |
| // The relative weight signifies the amount of confidence in the data. The |
| // weight is higher if there were more observations. A regularization term |
| // of |1 / recent_hosts_set.size()| is added so that if one particular |
| // remote host has a lot of observations, the results do not get skewed. |
| double weight = |
| 1.0 / recent_hosts_set.size() + |
| std::min(static_cast<double>(recent_observation_counts[host]) / |
| total_recent_count, |
| static_cast<double>(historical_observation_counts[host]) / |
| total_historical_count); |
| weighted_rtts.push_back(nqe::internal::WeightedObservation( |
| recent_median_rtts[host] - historical_min_rtts[host], weight)); |
| total_weight += weight; |
| } |
| |
| // Sort the increases in RTT for percentile computation. |
| std::sort(weighted_rtts.begin(), weighted_rtts.end()); |
| |
| // Calculate the weighted 50th percentile increase in transport RTT. |
| double desired_weight = 0.5 * total_weight; |
| for (nqe::internal::WeightedObservation wo : weighted_rtts) { |
| desired_weight -= wo.weight; |
| if (desired_weight <= 0) |
| return wo.value; |
| } |
| |
| // Calculation will reach here when the 50th percentile is the last value. |
| return weighted_rtts.back().value; |
| } |
| |
| void NetworkQualityEstimator::ComputeEffectiveConnectionType() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| UpdateSignalStrength(); |
| |
| 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_ = |
| GetRecentEffectiveConnectionTypeAndNetworkQuality( |
| base::TimeTicks(), &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); |
| ComputeBandwidthDelayProduct(); |
| |
| UMA_HISTOGRAM_ENUMERATION("NQE.EffectiveConnectionType.OnECTComputation", |
| effective_connection_type_, |
| EFFECTIVE_CONNECTION_TYPE_LAST); |
| if (network_quality_.http_rtt() != nqe::internal::InvalidRTT()) { |
| UMA_HISTOGRAM_TIMES("NQE.RTT.OnECTComputation", |
| network_quality_.http_rtt()); |
| } |
| |
| if (network_quality_.transport_rtt() != nqe::internal::InvalidRTT()) { |
| UMA_HISTOGRAM_TIMES("NQE.TransportRTT.OnECTComputation", |
| network_quality_.transport_rtt()); |
| } |
| |
| if (end_to_end_rtt != nqe::internal::InvalidRTT()) { |
| UMA_HISTOGRAM_TIMES("NQE.EndToEndRTT.OnECTComputation", end_to_end_rtt); |
| } |
| end_to_end_rtt_ = base::nullopt; |
| if (end_to_end_rtt != nqe::internal::InvalidRTT()) |
| end_to_end_rtt_ = end_to_end_rtt; |
| |
| if (network_quality_.downstream_throughput_kbps() != |
| nqe::internal::INVALID_RTT_THROUGHPUT) { |
| UMA_HISTOGRAM_COUNTS_1M("NQE.Kbps.OnECTComputation", |
| network_quality_.downstream_throughput_kbps()); |
| } |
| |
| 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; |
| } |
| |
| EffectiveConnectionType NetworkQualityEstimator::GetEffectiveConnectionType() |
| const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return effective_connection_type_; |
| } |
| |
| EffectiveConnectionType |
| NetworkQualityEstimator::GetRecentEffectiveConnectionType( |
| const base::TimeTicks& start_time) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| 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; |
| |
| return GetRecentEffectiveConnectionTypeAndNetworkQuality( |
| start_time, &http_rtt, &transport_rtt, &end_to_end_rtt, |
| &downstream_throughput_kbps, nullptr, nullptr); |
| } |
| |
| EffectiveConnectionType |
| NetworkQualityEstimator::GetRecentEffectiveConnectionTypeAndNetworkQuality( |
| const base::TimeTicks& start_time, |
| 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_THREAD(thread_checker_); |
| |
| return GetRecentEffectiveConnectionTypeUsingMetrics( |
| start_time, |
| NetworkQualityEstimator::MetricUsage::MUST_BE_USED /* http_rtt_metric */, |
| NetworkQualityEstimator::MetricUsage:: |
| DO_NOT_USE /* transport_rtt_metric */, |
| NetworkQualityEstimator::MetricUsage:: |
| USE_IF_AVAILABLE /* downstream_throughput_kbps_metric */, |
| http_rtt, transport_rtt, end_to_end_rtt, downstream_throughput_kbps, |
| transport_rtt_observation_count, end_to_end_rtt_observation_count); |
| } |
| |
| EffectiveConnectionType |
| NetworkQualityEstimator::GetRecentEffectiveConnectionTypeUsingMetrics( |
| const base::TimeTicks& start_time, |
| NetworkQualityEstimator::MetricUsage http_rtt_metric, |
| NetworkQualityEstimator::MetricUsage transport_rtt_metric, |
| NetworkQualityEstimator::MetricUsage downstream_throughput_kbps_metric, |
| 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_THREAD(thread_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 (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_HTTP, start_time, |
| http_rtt, nullptr)) { |
| *http_rtt = nqe::internal::InvalidRTT(); |
| } |
| |
| if (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_TRANSPORT, start_time, |
| transport_rtt, transport_rtt_observation_count)) { |
| *transport_rtt = nqe::internal::InvalidRTT(); |
| } |
| |
| if (!GetRecentRTT(nqe::internal::OBSERVATION_CATEGORY_END_TO_END, start_time, |
| end_to_end_rtt, end_to_end_rtt_observation_count)) { |
| *end_to_end_rtt = nqe::internal::InvalidRTT(); |
| } |
| |
| if (*http_rtt != nqe::internal::InvalidRTT() && |
| *transport_rtt != nqe::internal::InvalidRTT()) { |
| // Use transport RTT to clamp the HTTP RTT between lower and upper bounds. |
| // 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 (transport_rtt_observation_count_last_ect_computation_ >= |
| params_->http_rtt_transport_rtt_min_count()) { |
| if (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 (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()); |
| } |
| |
| if (!GetRecentDownlinkThroughputKbps(start_time, downstream_throughput_kbps)) |
| *downstream_throughput_kbps = nqe::internal::INVALID_RTT_THROUGHPUT; |
| |
| if (*http_rtt == nqe::internal::InvalidRTT() && |
| http_rtt_metric == NetworkQualityEstimator::MetricUsage::MUST_BE_USED) { |
| return EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| } |
| |
| if (*transport_rtt == nqe::internal::InvalidRTT() && |
| transport_rtt_metric == |
| NetworkQualityEstimator::MetricUsage::MUST_BE_USED) { |
| return EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| } |
| |
| if (*downstream_throughput_kbps == nqe::internal::INVALID_RTT_THROUGHPUT && |
| downstream_throughput_kbps_metric == |
| NetworkQualityEstimator::MetricUsage::MUST_BE_USED) { |
| 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_metric != NetworkQualityEstimator::MetricUsage::DO_NOT_USE && |
| *http_rtt != nqe::internal::InvalidRTT() && |
| params_->ConnectionThreshold(type).http_rtt() != |
| nqe::internal::InvalidRTT() && |
| *http_rtt >= params_->ConnectionThreshold(type).http_rtt(); |
| |
| const bool estimated_transport_rtt_is_higher_than_threshold = |
| transport_rtt_metric != |
| NetworkQualityEstimator::MetricUsage::DO_NOT_USE && |
| *transport_rtt != nqe::internal::InvalidRTT() && |
| params_->ConnectionThreshold(type).transport_rtt() != |
| nqe::internal::InvalidRTT() && |
| *transport_rtt >= params_->ConnectionThreshold(type).transport_rtt(); |
| |
| const bool estimated_throughput_is_lower_than_threshold = |
| downstream_throughput_kbps_metric != |
| NetworkQualityEstimator::MetricUsage::DO_NOT_USE && |
| *downstream_throughput_kbps != nqe::internal::INVALID_RTT_THROUGHPUT && |
| params_->ConnectionThreshold(type).downstream_throughput_kbps() != |
| nqe::internal::INVALID_RTT_THROUGHPUT && |
| *downstream_throughput_kbps <= |
| params_->ConnectionThreshold(type).downstream_throughput_kbps(); |
| |
| if (estimated_http_rtt_is_higher_than_threshold || |
| estimated_transport_rtt_is_higher_than_threshold || |
| estimated_throughput_is_lower_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_THREAD(thread_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::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, base::Bind(&NetworkQualityEstimator:: |
| NotifyEffectiveConnectionTypeObserverIfPresent, |
| weak_ptr_factory_.GetWeakPtr(), observer)); |
| } |
| |
| void NetworkQualityEstimator::RemoveEffectiveConnectionTypeObserver( |
| EffectiveConnectionTypeObserver* observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| effective_connection_type_observer_list_.RemoveObserver(observer); |
| } |
| |
| void NetworkQualityEstimator::AddRTTAndThroughputEstimatesObserver( |
| RTTAndThroughputEstimatesObserver* observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&NetworkQualityEstimator:: |
| NotifyRTTAndThroughputEstimatesObserverIfPresent, |
| weak_ptr_factory_.GetWeakPtr(), observer)); |
| } |
| |
| void NetworkQualityEstimator::RemoveRTTAndThroughputEstimatesObserver( |
| RTTAndThroughputEstimatesObserver* observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_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_THREAD(thread_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_THREAD(thread_checker_); |
| |
| // 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::TimeDelta::FromMilliseconds( |
| 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(); |
| } |
| #if defined(STARBOARD) |
| return base::TimeDelta(); |
| #endif |
| } |
| |
| int32_t NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimateInternal( |
| const base::TimeTicks& start_time, |
| int percentile) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_checker_); |
| |
| // TODO(tbansal): crbug.com/498068 Add NetworkQualityEstimatorAndroid class |
| // that overrides this method on the Android platform. |
| |
| return DoGetCurrentNetworkID(); |
| } |
| |
| bool NetworkQualityEstimator::ReadCachedNetworkQualityEstimate() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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); |
| UMA_HISTOGRAM_BOOLEAN("NQE.CachedNetworkQualityAvailable", |
| cached_estimate_available); |
| |
| 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_THREAD(thread_checker_); |
| tick_clock_ = tick_clock; |
| for (int i = 0; i < nqe::internal::OBSERVATION_CATEGORY_COUNT; ++i) |
| rtt_ms_observations_[i].SetTickClockForTesting(tick_clock_); |
| 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 base::Optional<nqe::internal::IPHash>& host) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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); |
| |
| // Post a task to compute and update the increase in RTT if not already |
| // posted. |
| if (!increase_in_transport_rtt_updater_posted_) |
| IncreaseInTransportRTTUpdater(); |
| } |
| |
| void NetworkQualityEstimator::AddAndNotifyObserversOfRTT( |
| const Observation& observation) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK_NE(nqe::internal::InvalidRTT(), |
| base::TimeDelta::FromMilliseconds(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); |
| |
| base::HistogramBase* raw_observation_histogram = base::Histogram::FactoryGet( |
| std::string("NQE.RTT.RawObservation.") + |
| nqe::internal::GetNameForObservationSource(observation.source()), |
| 1, 10 * 1000, 50, base::HistogramBase::kUmaTargetedHistogramFlag); |
| if (raw_observation_histogram) |
| raw_observation_histogram->Add(observation.value()); |
| |
| // Maybe recompute the effective connection type since a new RTT observation |
| // is available. |
| 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_THREAD(thread_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); |
| |
| UMA_HISTOGRAM_ENUMERATION("NQE.Kbps.ObservationSource", observation.source(), |
| NETWORK_QUALITY_OBSERVATION_SOURCE_MAX); |
| |
| base::HistogramBase* raw_observation_histogram = base::Histogram::FactoryGet( |
| std::string("NQE.Kbps.RawObservation.") + |
| nqe::internal::GetNameForObservationSource(observation.source()), |
| 1, 10 * 1000, 50, base::HistogramBase::kUmaTargetedHistogramFlag); |
| if (raw_observation_histogram) |
| raw_observation_histogram->Add(observation.value()); |
| |
| // Maybe recompute the effective connection type since a new throughput |
| // observation is available. |
| 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_THREAD(thread_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); |
| } |
| |
| void NetworkQualityEstimator::MaybeComputeEffectiveConnectionType() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| 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_ && |
| last_connection_change_ < last_effective_connection_type_computation_ && |
| // Recompute the effective connection type if the previously computed |
| // effective connection type was unknown. |
| effective_connection_type_ != EFFECTIVE_CONNECTION_TYPE_UNKNOWN && |
| // 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. |
| 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()) && |
| throughput_observations_size_at_last_ect_computation_ * 1.5 >= |
| http_downstream_throughput_kbps_observations_.Size() && |
| (new_rtt_observations_since_last_ect_computation_ + |
| new_throughput_observations_since_last_ect_computation_) < |
| params_->count_new_observations_received_compute_ect()) { |
| return; |
| } |
| ComputeEffectiveConnectionType(); |
| } |
| |
| void NetworkQualityEstimator:: |
| NotifyObserversOfEffectiveConnectionTypeChanged() { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| DCHECK_NE(EFFECTIVE_CONNECTION_TYPE_LAST, effective_connection_type_); |
| |
| // TODO(tbansal): Add hysteresis in the notification. |
| for (auto& observer : effective_connection_type_observer_list_) |
| observer.OnEffectiveConnectionTypeChanged(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_THREAD(thread_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( |
| EffectiveConnectionTypeObserver* observer) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (!effective_connection_type_observer_list_.HasObserver(observer)) |
| return; |
| if (effective_connection_type_ == EFFECTIVE_CONNECTION_TYPE_UNKNOWN) |
| return; |
| observer->OnEffectiveConnectionTypeChanged(effective_connection_type_); |
| } |
| |
| void NetworkQualityEstimator::NotifyRTTAndThroughputEstimatesObserverIfPresent( |
| RTTAndThroughputEstimatesObserver* observer) const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_checker_); |
| network_quality_store_->AddNetworkQualitiesCacheObserver(observer); |
| } |
| |
| void NetworkQualityEstimator::RemoveNetworkQualitiesCacheObserver( |
| nqe::internal::NetworkQualityStore::NetworkQualitiesCacheObserver* |
| observer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| network_quality_store_->RemoveNetworkQualitiesCacheObserver(observer); |
| } |
| |
| void NetworkQualityEstimator::OnPrefsRead( |
| const std::map<nqe::internal::NetworkID, |
| nqe::internal::CachedNetworkQuality> read_prefs) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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 defined(OS_CHROMEOS) |
| void NetworkQualityEstimator::EnableGetNetworkIdAsynchronously() { |
| get_network_id_asynchronously_ = true; |
| } |
| #endif // defined(OS_CHROMEOS) |
| |
| base::Optional<base::TimeDelta> NetworkQualityEstimator::GetHttpRTT() const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (network_quality_.http_rtt() == nqe::internal::InvalidRTT()) |
| return base::Optional<base::TimeDelta>(); |
| return network_quality_.http_rtt(); |
| } |
| |
| base::Optional<base::TimeDelta> NetworkQualityEstimator::GetTransportRTT() |
| const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (network_quality_.transport_rtt() == nqe::internal::InvalidRTT()) |
| return base::Optional<base::TimeDelta>(); |
| return network_quality_.transport_rtt(); |
| } |
| |
| base::Optional<int32_t> NetworkQualityEstimator::GetDownstreamThroughputKbps() |
| const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| |
| if (network_quality_.downstream_throughput_kbps() == |
| nqe::internal::INVALID_RTT_THROUGHPUT) { |
| return base::Optional<int32_t>(); |
| } |
| return network_quality_.downstream_throughput_kbps(); |
| } |
| |
| base::Optional<int32_t> NetworkQualityEstimator::GetBandwidthDelayProductKbits() |
| const { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| return bandwidth_delay_product_kbits_; |
| } |
| |
| void NetworkQualityEstimator::MaybeUpdateCachedEstimateApplied( |
| const Observation& observation, |
| ObservationBuffer* buffer) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_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_THREAD(thread_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_THREAD(thread_checker_); |
| return (now - last_socket_watcher_rtt_notification_ >= |
| params_->socket_watchers_min_notification_interval()); |
| } |
| |
| void NetworkQualityEstimator::SimulateNetworkQualityChangeForTesting( |
| net::EffectiveConnectionType type) { |
| DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); |
| params_->SetForcedEffectiveConnectionTypeForTesting(type); |
| ComputeEffectiveConnectionType(); |
| } |
| |
| } // namespace net |