// 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 #include #include #include #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_macros.h" #include "base/metrics/sparse_histogram.h" #include "base/rand_util.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/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 { // Returns the histogram that should be used to record the given statistic. // |max_limit| is the maximum value that can be stored in the histogram. base::HistogramBase* GetHistogram(const std::string& statistic_name, NetworkChangeNotifier::ConnectionType type, int32_t max_limit) { const base::LinearHistogram::Sample kLowerLimit = 1; DCHECK_GT(max_limit, kLowerLimit); const size_t kBucketCount = 50; return base::Histogram::FactoryGet( "NQE." + statistic_name + NetworkQualityEstimatorParams::GetNameForConnectionType(type), kLowerLimit, max_limit, kBucketCount, base::HistogramBase::kUmaTargetedHistogramFlag); } 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]; } // The least significant kTrimBits of the metric will be discarded. If the // trimmed metric value is greater than what can be fit in kBitsPerMetric bits, // then the largest value that can be represented in kBitsPerMetric bits is // returned. const int32_t kTrimBits = 5; // Maximum number of bits in which one metric should fit. Restricting the amount // of space allocated to a single metric makes it possile to fit multiple // metrics in a single histogram sample, and ensures that all those metrics // are recorded together as a single tuple. const int32_t kBitsPerMetric = 7; static_assert(32 >= kBitsPerMetric * 4, "Four metrics would not fit in a 32-bit int"); // Trims the |metric| by removing the last kTrimBits, and then rounding down // the |metric| such that the |metric| fits in kBitsPerMetric. int32_t FitInKBitsPerMetricBits(int32_t metric) { // Remove the last kTrimBits. This will allow the metric to fit within // kBitsPerMetric while losing only the least significant bits. DCHECK_LE(0, metric); metric = metric >> kTrimBits; // kLargestValuePossible is the largest value that can be recorded using // kBitsPerMetric. static const int32_t kLargestValuePossible = (1 << kBitsPerMetric) - 1; if (metric > kLargestValuePossible) { // Fit |metric| in kBitsPerMetric by clamping it down. metric = kLargestValuePossible; } DCHECK_EQ(0, metric >> kBitsPerMetric) << metric; return metric; } 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(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(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(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)); } } // namespace NetworkQualityEstimator::NetworkQualityEstimator( std::unique_ptr external_estimates_provider, std::unique_ptr params, NetLog* net_log) : params_(std::move(params)), use_localhost_requests_(false), disable_offline_check_(false), add_default_platform_observations_(true), tick_clock_(new base::DefaultTickClock()), last_connection_change_(tick_clock_->NowTicks()), current_network_id_(nqe::internal::NetworkID( NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, std::string())), downstream_throughput_kbps_observations_( params_->weight_multiplier_per_second(), params_->weight_multiplier_per_signal_strength_level()), rtt_ms_observations_( params_->weight_multiplier_per_second(), params_->weight_multiplier_per_signal_strength_level()), effective_connection_type_at_last_main_frame_( EFFECTIVE_CONNECTION_TYPE_UNKNOWN), external_estimate_provider_(std::move(external_estimates_provider)), 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), increase_in_transport_rtt_updater_posted_(false), effective_connection_type_(EFFECTIVE_CONNECTION_TYPE_UNKNOWN), net_log_(NetLogWithSource::Make( net_log, net::NetLogSourceType::NETWORK_QUALITY_ESTIMATOR)), event_creator_(net_log_), disallowed_observation_sources_for_http_( {NETWORK_QUALITY_OBSERVATION_SOURCE_TCP, NETWORK_QUALITY_OBSERVATION_SOURCE_QUIC, NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE, NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_TRANSPORT_FROM_PLATFORM}), disallowed_observation_sources_for_transport_( {NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_EXTERNAL_ESTIMATE, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE, NETWORK_QUALITY_OBSERVATION_SOURCE_DEFAULT_HTTP_FROM_PLATFORM}), weak_ptr_factory_(this) { network_quality_store_.reset(new nqe::internal::NetworkQualityStore()); NetworkChangeNotifier::AddConnectionTypeObserver(this); if (external_estimate_provider_) { RecordExternalEstimateProviderMetrics( EXTERNAL_ESTIMATE_PROVIDER_STATUS_AVAILABLE); external_estimate_provider_->SetUpdatedEstimateDelegate(this); } else { RecordExternalEstimateProviderMetrics( EXTERNAL_ESTIMATE_PROVIDER_STATUS_NOT_AVAILABLE); } current_network_id_ = GetCurrentNetworkID(); throughput_analyzer_.reset(new nqe::internal::ThroughputAnalyzer( this, params_.get(), base::ThreadTaskRunnerHandle::Get(), base::Bind(&NetworkQualityEstimator::OnNewThroughputObservationAvailable, base::Unretained(this)), tick_clock_.get(), net_log_)); watcher_factory_.reset(new nqe::internal::SocketWatcherFactory( base::ThreadTaskRunnerHandle::Get(), params_->min_socket_watcher_notification_interval(), base::Bind(&NetworkQualityEstimator::OnUpdatedRTTAvailable, base::Unretained(this)), tick_clock_.get())); // 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)); ComputeEffectiveConnectionType(); } void NetworkQualityEstimator::AddDefaultEstimates() { DCHECK(thread_checker_.CalledOnValidThread()); if (!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::kInvalidThroughput) { 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(thread_checker_.CalledOnValidThread()); NetworkChangeNotifier::RemoveConnectionTypeObserver(this); } const std::vector& NetworkQualityEstimator::GetAccuracyRecordingIntervals() const { DCHECK(thread_checker_.CalledOnValidThread()); return accuracy_recording_intervals_; } void NetworkQualityEstimator::NotifyStartTransaction( const URLRequest& request) { DCHECK(thread_checker_.CalledOnValidThread()); 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); } void NetworkQualityEstimator::NotifyHeadersReceived(const URLRequest& request) { TRACE_EVENT0(kNetTracingCategory, "NetworkQualityEstimator::NotifyHeadersReceived"); DCHECK(thread_checker_.CalledOnValidThread()); if (!RequestSchemeIsHTTPOrHTTPS(request) || !RequestProvidesRTTObservation(request)) { return; } if (request.load_flags() & LOAD_MAIN_FRAME_DEPRECATED) { ComputeEffectiveConnectionType(); RecordMetricsOnMainFrameRequest(); MaybeQueryExternalEstimateProvider(); } 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()); Observation http_rtt_observation(observed_http_rtt.InMilliseconds(), tick_clock_->NowTicks(), signal_strength_, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP); AddAndNotifyObserversOfRTT(http_rtt_observation); throughput_analyzer_->NotifyBytesRead(request); } void NetworkQualityEstimator::NotifyBytesRead(const URLRequest& request) { DCHECK(thread_checker_.CalledOnValidThread()); throughput_analyzer_->NotifyBytesRead(request); } void NetworkQualityEstimator::RecordAccuracyAfterMainFrame( base::TimeDelta measuring_duration) const { DCHECK(thread_checker_.CalledOnValidThread()); 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 (!GetRecentHttpRTT(last_main_frame_request_, &recent_http_rtt)) 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() && GetRecentTransportRTT(last_main_frame_request_, &recent_transport_rtt)) { 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::kInvalidThroughput && 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(effective_connection_type_at_last_main_frame_) - static_cast(recent_effective_connection_type); RecordEffectiveConnectionTypeAccuracy( "NQE.Accuracy.EffectiveConnectionType", estimated_observed_diff, measuring_duration, recent_effective_connection_type); } // Add histogram to evaluate the accuracy of the external estimate provider. if (external_estimate_provider_quality_.http_rtt() != nqe::internal::InvalidRTT() && recent_http_rtt != nqe::internal::InvalidRTT()) { const int estimated_observed_diff_milliseconds = external_estimate_provider_quality_.http_rtt().InMilliseconds() - recent_http_rtt.InMilliseconds(); RecordRTTAccuracy("NQE.ExternalEstimateProvider.RTT.Accuracy", estimated_observed_diff_milliseconds, measuring_duration, recent_http_rtt); } } void NetworkQualityEstimator::NotifyRequestCompleted(const URLRequest& request, int net_error) { TRACE_EVENT0(kNetTracingCategory, "NetworkQualityEstimator::NotifyRequestCompleted"); DCHECK(thread_checker_.CalledOnValidThread()); if (!RequestSchemeIsHTTPOrHTTPS(request)) return; throughput_analyzer_->NotifyRequestCompleted(request); RecordCorrelationMetric(request, net_error); } void NetworkQualityEstimator::RecordCorrelationMetric(const URLRequest& request, int net_error) const { DCHECK(thread_checker_.CalledOnValidThread()); // The histogram is recorded randomly to reduce overhead involved with sparse // histograms. Furthermore, recording the correlation on each request is // unnecessary. if (RandDouble() >= params_->correlation_uma_logging_probability()) return; if (request.response_info().was_cached || !request.response_info().network_accessed) { return; } 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; } // Record UMA only for successful requests that have completed. if (net_error != OK) return; if (!request.response_info().headers.get() || request.response_info().headers->response_code() != HTTP_OK) { return; } if (load_timing_info.receive_headers_end < last_main_frame_request_) return; // Use the system clock instead of |tick_clock_| to compare the current // timestamp with the |load_timing_info| timestamp since the latter is set by // the system clock, and may be different from |tick_clock_| in tests. const base::TimeTicks now = base::TimeTicks::Now(); // Record UMA only for requests that started recently. if (now - last_main_frame_request_ > base::TimeDelta::FromSeconds(15)) return; if (last_connection_change_ >= last_main_frame_request_) return; DCHECK_GE(now, load_timing_info.send_start); int32_t rtt = 0; if (estimated_quality_at_last_main_frame_.downstream_throughput_kbps() == nqe::internal::kInvalidThroughput) { return; } if (UseTransportRTT()) { if (estimated_quality_at_last_main_frame_.transport_rtt() == nqe::internal::InvalidRTT()) { return; } rtt = FitInKBitsPerMetricBits( estimated_quality_at_last_main_frame_.transport_rtt().InMilliseconds()); } else { if (estimated_quality_at_last_main_frame_.http_rtt() == nqe::internal::InvalidRTT()) { return; } rtt = FitInKBitsPerMetricBits( estimated_quality_at_last_main_frame_.http_rtt().InMilliseconds()); } const int32_t downstream_throughput = FitInKBitsPerMetricBits( estimated_quality_at_last_main_frame_.downstream_throughput_kbps()); const int32_t resource_load_time = FitInKBitsPerMetricBits( (now - load_timing_info.send_start).InMilliseconds()); int64_t resource_size = (request.GetTotalReceivedBytes() * 8) / 1024; if (resource_size >= (1 << kBitsPerMetric)) { // Too large resource size (at least 128 Kb). return; } DCHECK_EQ( 0, (rtt | downstream_throughput | resource_load_time | resource_size) >> kBitsPerMetric); // First 32 - (4* kBitsPerMetric) of the sample are unset. Next // kBitsPerMetric of the sample contain |rtt|. Next // kBitsPerMetric contain |downstream_throughput|. Next kBitsPerMetric // contain |resource_load_time|. And, the last kBitsPerMetric // contain |resource_size|. int32_t sample = rtt; sample = (sample << kBitsPerMetric) | downstream_throughput; sample = (sample << kBitsPerMetric) | resource_load_time; sample = (sample << kBitsPerMetric) | resource_size; UMA_HISTOGRAM_SPARSE_SLOWLY("NQE.Correlation.ResourceLoadTime.0Kb_128Kb", sample); } void NetworkQualityEstimator::NotifyURLRequestDestroyed( const URLRequest& request) { DCHECK(thread_checker_.CalledOnValidThread()); if (!RequestSchemeIsHTTPOrHTTPS(request)) return; throughput_analyzer_->NotifyRequestCompleted(request); } void NetworkQualityEstimator::AddRTTObserver(RTTObserver* rtt_observer) { DCHECK(thread_checker_.CalledOnValidThread()); rtt_observer_list_.AddObserver(rtt_observer); } void NetworkQualityEstimator::RemoveRTTObserver(RTTObserver* rtt_observer) { DCHECK(thread_checker_.CalledOnValidThread()); rtt_observer_list_.RemoveObserver(rtt_observer); } void NetworkQualityEstimator::AddThroughputObserver( ThroughputObserver* throughput_observer) { DCHECK(thread_checker_.CalledOnValidThread()); throughput_observer_list_.AddObserver(throughput_observer); } void NetworkQualityEstimator::RemoveThroughputObserver( ThroughputObserver* throughput_observer) { DCHECK(thread_checker_.CalledOnValidThread()); throughput_observer_list_.RemoveObserver(throughput_observer); } SocketPerformanceWatcherFactory* NetworkQualityEstimator::GetSocketPerformanceWatcherFactory() { DCHECK(thread_checker_.CalledOnValidThread()); return watcher_factory_.get(); } void NetworkQualityEstimator::SetUseLocalHostRequestsForTesting( bool use_localhost_requests) { DCHECK(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); params_->SetUseSmallResponsesForTesting(use_small_responses); } void NetworkQualityEstimator::DisableOfflineCheckForTesting( bool disable_offline_check) { DCHECK(thread_checker_.CalledOnValidThread()); disable_offline_check_ = disable_offline_check; network_quality_store_->DisableOfflineCheckForTesting(disable_offline_check_); } void NetworkQualityEstimator::SetAddDefaultPlatformObservationsForTesting( bool add_default_platform_observations) { DCHECK(thread_checker_.CalledOnValidThread()); add_default_platform_observations_ = add_default_platform_observations; } void NetworkQualityEstimator::ReportEffectiveConnectionTypeForTesting( EffectiveConnectionType effective_connection_type) { DCHECK(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); bool private_network_request = nqe::internal::IsPrivateHost( request.context()->host_resolver(), HostPortPair(request.url().host(), request.url().EffectiveIntPort())); 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_; } void NetworkQualityEstimator::RecordExternalEstimateProviderMetrics( NQEExternalEstimateProviderStatus status) const { UMA_HISTOGRAM_ENUMERATION("NQE.ExternalEstimateProviderStatus", status, EXTERNAL_ESTIMATE_PROVIDER_STATUS_BOUNDARY); } void NetworkQualityEstimator::OnConnectionTypeChanged( NetworkChangeNotifier::ConnectionType type) { DCHECK(thread_checker_.CalledOnValidThread()); RecordMetricsOnConnectionTypeChanged(); // 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(); downstream_throughput_kbps_observations_.Clear(); rtt_ms_observations_.Clear(); if (external_estimate_provider_) external_estimate_provider_->ClearCachedEstimate(); #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 signal_strength_.reset(); min_signal_strength_since_connection_change_.reset(); max_signal_strength_since_connection_change_.reset(); network_quality_ = nqe::internal::NetworkQuality(); 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; // Update the local state as part of preparation for the new connection. current_network_id_ = GetCurrentNetworkID(); RecordNetworkIDAvailability(); MaybeQueryExternalEstimateProvider(); // Read any cached estimates for the new network. If cached estimates are // unavailable, add the default estimates. if (!ReadCachedNetworkQualityEstimate()) AddDefaultEstimates(); estimated_quality_at_last_main_frame_ = nqe::internal::NetworkQuality(); throughput_analyzer_->OnConnectionTypeChanged(); MaybeComputeEffectiveConnectionType(); } void NetworkQualityEstimator::MaybeQueryExternalEstimateProvider() const { // Query the external estimate provider on certain connection types. Once the // updated estimates are available, OnUpdatedEstimateAvailable will be called // by |external_estimate_provider_| with updated estimates. if (external_estimate_provider_ && current_network_id_.type != NetworkChangeNotifier::CONNECTION_NONE && current_network_id_.type != NetworkChangeNotifier::CONNECTION_UNKNOWN && current_network_id_.type != NetworkChangeNotifier::CONNECTION_ETHERNET && current_network_id_.type != NetworkChangeNotifier::CONNECTION_BLUETOOTH) { RecordExternalEstimateProviderMetrics( EXTERNAL_ESTIMATE_PROVIDER_STATUS_QUERIED); external_estimate_provider_->Update(); } } void NetworkQualityEstimator::UpdateSignalStrength() { DCHECK(thread_checker_.CalledOnValidThread()); signal_strength_.reset(); #if defined(OS_ANDROID) if (params_->weight_multiplier_per_signal_strength_level() >= 1.0) return; if (!NetworkChangeNotifier::IsConnectionCellular(current_network_id_.type)) return; signal_strength_ = android::cellular_signal_strength::GetSignalStrengthLevel(); if (!signal_strength_) return; min_signal_strength_since_connection_change_ = std::min(min_signal_strength_since_connection_change_.value_or(INT32_MAX), signal_strength_.value()); max_signal_strength_since_connection_change_ = std::max(max_signal_strength_since_connection_change_.value_or(INT32_MIN), signal_strength_.value()); #endif // OS_ANDROID } void NetworkQualityEstimator::RecordMetricsOnConnectionTypeChanged() const { DCHECK(thread_checker_.CalledOnValidThread()); base::TimeDelta rtt; if (GetRecentHttpRTT(base::TimeTicks(), &rtt)) { // Add the 50th percentile value. base::HistogramBase* rtt_percentile = GetHistogram("RTT.Percentile50.", current_network_id_.type, 10 * 1000); rtt_percentile->Add(rtt.InMilliseconds()); // Add the remaining percentile values. static const int kPercentiles[] = {0, 10, 90, 100}; for (size_t i = 0; i < arraysize(kPercentiles); ++i) { rtt = GetRTTEstimateInternal( disallowed_observation_sources_for_http_, base::TimeTicks(), base::Optional(), kPercentiles[i]); rtt_percentile = GetHistogram( "RTT.Percentile" + base::IntToString(kPercentiles[i]) + ".", current_network_id_.type, 10 * 1000); // 10 seconds rtt_percentile->Add(rtt.InMilliseconds()); } } if (GetRecentTransportRTT(base::TimeTicks(), &rtt)) { // Add the 50th percentile value. base::HistogramBase* transport_rtt_percentile = GetHistogram( "TransportRTT.Percentile50.", current_network_id_.type, 10 * 1000); transport_rtt_percentile->Add(rtt.InMilliseconds()); // Add the remaining percentile values. static const int kPercentiles[] = {0, 10, 90, 100}; for (size_t i = 0; i < arraysize(kPercentiles); ++i) { rtt = GetRTTEstimateInternal( disallowed_observation_sources_for_transport_, base::TimeTicks(), base::Optional(), kPercentiles[i]); transport_rtt_percentile = GetHistogram( "TransportRTT.Percentile" + base::IntToString(kPercentiles[i]) + ".", current_network_id_.type, 10 * 1000); // 10 seconds transport_rtt_percentile->Add(rtt.InMilliseconds()); } } } void NetworkQualityEstimator::RecordNetworkIDAvailability() const { DCHECK(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); 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::kInvalidThroughput) { // 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::kInvalidThroughput); UMA_HISTOGRAM_ENUMERATION("NQE.MainFrame.EffectiveConnectionType", effective_connection_type_at_last_main_frame_, EFFECTIVE_CONNECTION_TYPE_LAST); } void NetworkQualityEstimator::ComputeBandwidthDelayProduct() { DCHECK(thread_checker_.CalledOnValidThread()); // 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( disallowed_observation_sources_for_transport_, base::TimeTicks(), base::Optional(), 20); if (transport_rtt == nqe::internal::InvalidRTT()) return; int32_t downlink_throughput_kbps = GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 20); if (downlink_throughput_kbps == nqe::internal::kInvalidThroughput) 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(thread_checker_.CalledOnValidThread()); 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 NetworkQualityEstimator::ComputeIncreaseInTransportRTT() const { DCHECK(thread_checker_.CalledOnValidThread()); base::TimeTicks now = base::TimeTicks::Now(); // 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 recent_median_rtts; std::map recent_observation_counts; rtt_ms_observations_.GetPercentileForEachHostWithCounts( recent_start_time, 50, disallowed_observation_sources_for_transport_, 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 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 historical_min_rtts; std::map historical_observation_counts; rtt_ms_observations_.GetPercentileForEachHostWithCounts( history_start_time, 0, disallowed_observation_sources_for_transport_, 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 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(recent_observation_counts[host]) / total_recent_count, static_cast(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(thread_checker_.CalledOnValidThread()); 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(); int32_t downstream_throughput_kbps = nqe::internal::kInvalidThroughput; effective_connection_type_ = GetRecentEffectiveConnectionTypeAndNetworkQuality( base::TimeTicks(), &http_rtt, &transport_rtt, &downstream_throughput_kbps); 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 (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_.Size(); throughput_observations_size_at_last_ect_computation_ = downstream_throughput_kbps_observations_.Size(); } EffectiveConnectionType NetworkQualityEstimator::GetEffectiveConnectionType() const { DCHECK(thread_checker_.CalledOnValidThread()); return effective_connection_type_; } EffectiveConnectionType NetworkQualityEstimator::GetRecentEffectiveConnectionType( const base::TimeTicks& start_time) const { DCHECK(thread_checker_.CalledOnValidThread()); base::TimeDelta http_rtt = nqe::internal::InvalidRTT(); base::TimeDelta transport_rtt = nqe::internal::InvalidRTT(); int32_t downstream_throughput_kbps = nqe::internal::kInvalidThroughput; return GetRecentEffectiveConnectionTypeAndNetworkQuality( start_time, &http_rtt, &transport_rtt, &downstream_throughput_kbps); } EffectiveConnectionType NetworkQualityEstimator::GetRecentEffectiveConnectionTypeAndNetworkQuality( const base::TimeTicks& start_time, base::TimeDelta* http_rtt, base::TimeDelta* transport_rtt, int32_t* downstream_throughput_kbps) const { DCHECK(thread_checker_.CalledOnValidThread()); if (params_->GetEffectiveConnectionTypeAlgorithm() == NetworkQualityEstimatorParams::EffectiveConnectionTypeAlgorithm:: HTTP_RTT_AND_DOWNSTREAM_THROUGHOUT) { 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, downstream_throughput_kbps); } if (params_->GetEffectiveConnectionTypeAlgorithm() == NetworkQualityEstimatorParams::EffectiveConnectionTypeAlgorithm:: TRANSPORT_RTT_OR_DOWNSTREAM_THROUGHOUT) { return GetRecentEffectiveConnectionTypeUsingMetrics( start_time, NetworkQualityEstimator::MetricUsage::DO_NOT_USE /* http_rtt_metric */, NetworkQualityEstimator::MetricUsage:: USE_IF_AVAILABLE /* transport_rtt_metric */, NetworkQualityEstimator::MetricUsage:: USE_IF_AVAILABLE /* downstream_throughput_kbps_metric */, http_rtt, transport_rtt, downstream_throughput_kbps); } // Add additional algorithms here. NOTREACHED(); return EFFECTIVE_CONNECTION_TYPE_UNKNOWN; } bool NetworkQualityEstimator::UseTransportRTT() const { DCHECK(thread_checker_.CalledOnValidThread()); if (params_->GetEffectiveConnectionTypeAlgorithm() == NetworkQualityEstimatorParams::EffectiveConnectionTypeAlgorithm:: HTTP_RTT_AND_DOWNSTREAM_THROUGHOUT) { return false; } if (params_->GetEffectiveConnectionTypeAlgorithm() == NetworkQualityEstimatorParams::EffectiveConnectionTypeAlgorithm:: TRANSPORT_RTT_OR_DOWNSTREAM_THROUGHOUT) { return true; } // Add additional algorithms here. NOTREACHED(); return false; } 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, int32_t* downstream_throughput_kbps) const { DCHECK(thread_checker_.CalledOnValidThread()); *http_rtt = nqe::internal::InvalidRTT(); *transport_rtt = nqe::internal::InvalidRTT(); *downstream_throughput_kbps = nqe::internal::kInvalidThroughput; if (params_->forced_effective_connection_type()) { *http_rtt = params_ ->TypicalNetworkQuality( params_->forced_effective_connection_type().value()) .http_rtt(); *transport_rtt = params_ ->TypicalNetworkQuality( params_->forced_effective_connection_type().value()) .transport_rtt(); *downstream_throughput_kbps = params_ ->TypicalNetworkQuality( params_->forced_effective_connection_type().value()) .downstream_throughput_kbps(); return params_->forced_effective_connection_type().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 (!GetRecentHttpRTT(start_time, http_rtt)) *http_rtt = nqe::internal::InvalidRTT(); if (!GetRecentTransportRTT(start_time, transport_rtt)) *transport_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. 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()); } if (params_->upper_bound_http_rtt_transport_rtt_multiplier() > 0) { *http_rtt = std::min( *http_rtt, *transport_rtt * params_->upper_bound_http_rtt_transport_rtt_multiplier()); } } if (!GetRecentDownlinkThroughputKbps(start_time, downstream_throughput_kbps)) *downstream_throughput_kbps = nqe::internal::kInvalidThroughput; 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::kInvalidThroughput && 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::kInvalidThroughput) { // 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(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::kInvalidThroughput && params_->ConnectionThreshold(type).downstream_throughput_kbps() != nqe::internal::kInvalidThroughput && *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(EFFECTIVE_CONNECTION_TYPE_LAST - 1); } void NetworkQualityEstimator::AddEffectiveConnectionTypeObserver( EffectiveConnectionTypeObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); effective_connection_type_observer_list_.RemoveObserver(observer); } void NetworkQualityEstimator::AddRTTAndThroughputEstimatesObserver( RTTAndThroughputEstimatesObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); rtt_and_throughput_estimates_observer_list_.RemoveObserver(observer); } bool NetworkQualityEstimator::GetRecentHttpRTT( const base::TimeTicks& start_time, base::TimeDelta* rtt) const { DCHECK(thread_checker_.CalledOnValidThread()); *rtt = GetRTTEstimateInternal(disallowed_observation_sources_for_http_, start_time, base::Optional(), 50); return (*rtt != nqe::internal::InvalidRTT()); } bool NetworkQualityEstimator::GetRecentTransportRTT( const base::TimeTicks& start_time, base::TimeDelta* rtt) const { DCHECK(thread_checker_.CalledOnValidThread()); *rtt = GetRTTEstimateInternal(disallowed_observation_sources_for_transport_, start_time, base::Optional(), 50); return (*rtt != nqe::internal::InvalidRTT()); } bool NetworkQualityEstimator::GetRecentDownlinkThroughputKbps( const base::TimeTicks& start_time, int32_t* kbps) const { DCHECK(thread_checker_.CalledOnValidThread()); *kbps = GetDownlinkThroughputKbpsEstimateInternal(start_time, 50); return (*kbps != nqe::internal::kInvalidThroughput); } base::TimeDelta NetworkQualityEstimator::GetRTTEstimateInternal( const std::vector& disallowed_observation_sources, base::TimeTicks start_time, const base::Optional& statistic, int percentile) const { DCHECK(thread_checker_.CalledOnValidThread()); // RTT observations are sorted by duration from shortest to longest, thus // a higher percentile RTT will have a longer RTT than a lower percentile. if (!statistic) { // Use default statistic algorithm. return base::TimeDelta::FromMilliseconds( rtt_ms_observations_ .GetPercentile(start_time, signal_strength_, percentile, disallowed_observation_sources) .value_or(nqe::internal::INVALID_RTT_THROUGHPUT)); } base::Optional rtt_ms; switch (statistic.value()) { case STATISTIC_LAST: NOTREACHED(); return nqe::internal::InvalidRTT(); } return base::TimeDelta::FromMilliseconds( rtt_ms.value_or(nqe::internal::INVALID_RTT_THROUGHPUT)); } int32_t NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimateInternal( const base::TimeTicks& start_time, int percentile) const { DCHECK(thread_checker_.CalledOnValidThread()); // Throughput observations are sorted by kbps from slowest to fastest, // thus a higher percentile throughput will be faster than a lower one. return downstream_throughput_kbps_observations_ .GetPercentile(start_time, signal_strength_, 100 - percentile, std::vector()) .value_or(nqe::internal::INVALID_RTT_THROUGHPUT); } nqe::internal::NetworkID NetworkQualityEstimator::GetCurrentNetworkID() const { DCHECK(thread_checker_.CalledOnValidThread()); // TODO(tbansal): crbug.com/498068 Add NetworkQualityEstimatorAndroid class // that overrides this method on the Android platform. // 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()); 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(); } bool NetworkQualityEstimator::ReadCachedNetworkQualityEstimate() { DCHECK(thread_checker_.CalledOnValidThread()); if (!params_->persistent_cache_reading_enabled()) return false; if (current_network_id_.type != NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI && current_network_id_.type != NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET && !disable_offline_check_) { return false; } nqe::internal::CachedNetworkQuality cached_network_quality; const bool cached_estimate_available = network_quality_store_->GetById( current_network_id_, &cached_network_quality); if (network_quality_store_->EligibleForCaching(current_network_id_)) { UMA_HISTOGRAM_BOOLEAN("NQE.CachedNetworkQualityAvailable", cached_estimate_available); } if (!cached_estimate_available) return false; const base::TimeTicks now = tick_clock_->NowTicks(); if (cached_network_quality.network_quality().downstream_throughput_kbps() != nqe::internal::kInvalidThroughput) { Observation througphput_observation( cached_network_quality.network_quality().downstream_throughput_kbps(), now, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE); AddAndNotifyObserversOfThroughput(througphput_observation); } if (cached_network_quality.network_quality().http_rtt() != nqe::internal::InvalidRTT()) { Observation rtt_observation( cached_network_quality.network_quality().http_rtt().InMilliseconds(), now, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE); AddAndNotifyObserversOfRTT(rtt_observation); } if (cached_network_quality.network_quality().transport_rtt() != nqe::internal::InvalidRTT()) { Observation rtt_observation( cached_network_quality.network_quality() .transport_rtt() .InMilliseconds(), now, INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE); AddAndNotifyObserversOfRTT(rtt_observation); } ComputeEffectiveConnectionType(); return true; } void NetworkQualityEstimator::OnUpdatedEstimateAvailable( const base::TimeDelta& rtt, int32_t downstream_throughput_kbps) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(external_estimate_provider_); RecordExternalEstimateProviderMetrics( EXTERNAL_ESTIMATE_PROVIDER_STATUS_CALLBACK); external_estimate_provider_quality_ = nqe::internal::NetworkQuality(); if (rtt > base::TimeDelta()) { RecordExternalEstimateProviderMetrics( EXTERNAL_ESTIMATE_PROVIDER_STATUS_RTT_AVAILABLE); UMA_HISTOGRAM_TIMES("NQE.ExternalEstimateProvider.RTT", rtt); Observation rtt_observation( rtt.InMilliseconds(), tick_clock_->NowTicks(), signal_strength_, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_EXTERNAL_ESTIMATE); external_estimate_provider_quality_.set_http_rtt(rtt); AddAndNotifyObserversOfRTT(rtt_observation); } if (downstream_throughput_kbps > 0) { RecordExternalEstimateProviderMetrics( EXTERNAL_ESTIMATE_PROVIDER_STATUS_DOWNLINK_BANDWIDTH_AVAILABLE); UMA_HISTOGRAM_COUNTS_1M("NQE.ExternalEstimateProvider.DownlinkBandwidth", downstream_throughput_kbps); Observation throughput_observation( downstream_throughput_kbps, tick_clock_->NowTicks(), signal_strength_, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_EXTERNAL_ESTIMATE); external_estimate_provider_quality_.set_downstream_throughput_kbps( downstream_throughput_kbps); AddAndNotifyObserversOfThroughput(throughput_observation); } } void NetworkQualityEstimator::SetTickClockForTesting( std::unique_ptr tick_clock) { DCHECK(thread_checker_.CalledOnValidThread()); tick_clock_ = std::move(tick_clock); throughput_analyzer_->SetTickClockForTesting(tick_clock_.get()); } double NetworkQualityEstimator::RandDouble() const { return base::RandDouble(); } void NetworkQualityEstimator::OnUpdatedRTTAvailable( SocketPerformanceWatcherFactory::Protocol protocol, const base::TimeDelta& rtt, const base::Optional& host) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK_NE(nqe::internal::InvalidRTT(), rtt); Observation observation(rtt.InMilliseconds(), tick_clock_->NowTicks(), 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(thread_checker_.CalledOnValidThread()); DCHECK_NE(nqe::internal::InvalidRTT(), base::TimeDelta::FromMilliseconds(observation.value)); DCHECK_GT(NETWORK_QUALITY_OBSERVATION_SOURCE_MAX, observation.source); rtt_ms_observations_.AddObservation(observation); 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); 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(thread_checker_.CalledOnValidThread()); DCHECK_NE(nqe::internal::kInvalidThroughput, observation.value); DCHECK_GT(NETWORK_QUALITY_OBSERVATION_SOURCE_MAX, observation.source); 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); 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(thread_checker_.CalledOnValidThread()); if (downstream_kbps <= 0) return; DCHECK_NE(nqe::internal::kInvalidThroughput, downstream_kbps); Observation throughput_observation(downstream_kbps, tick_clock_->NowTicks(), signal_strength_, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP); AddAndNotifyObserversOfThroughput(throughput_observation); } void NetworkQualityEstimator::MaybeComputeEffectiveConnectionType() { DCHECK(thread_checker_.CalledOnValidThread()); 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_.Size() && throughput_observations_size_at_last_ect_computation_ * 1.5 >= downstream_throughput_kbps_observations_.Size()) { return; } ComputeEffectiveConnectionType(); } void NetworkQualityEstimator:: NotifyObserversOfEffectiveConnectionTypeChanged() { DCHECK(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); // 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(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); 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(thread_checker_.CalledOnValidThread()); network_quality_store_->AddNetworkQualitiesCacheObserver(observer); } void NetworkQualityEstimator::RemoveNetworkQualitiesCacheObserver( nqe::internal::NetworkQualityStore::NetworkQualitiesCacheObserver* observer) { DCHECK(thread_checker_.CalledOnValidThread()); network_quality_store_->RemoveNetworkQualitiesCacheObserver(observer); } void NetworkQualityEstimator::OnPrefsRead( const std::map read_prefs) { DCHECK(thread_checker_.CalledOnValidThread()); 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::kInvalidThroughput, it.second.network_quality().downstream_throughput_kbps()); nqe::internal::CachedNetworkQuality cached_network_quality( base::TimeTicks::Now(), params_->TypicalNetworkQuality(effective_connection_type), effective_connection_type); network_quality_store_->Add(it.first, cached_network_quality); MaybeUpdateNetworkQualityFromCache(it.first, cached_network_quality); } } base::Optional NetworkQualityEstimator::GetHttpRTT() const { DCHECK(thread_checker_.CalledOnValidThread()); if (network_quality_.http_rtt() == nqe::internal::InvalidRTT()) return base::Optional(); return network_quality_.http_rtt(); } base::Optional NetworkQualityEstimator::GetTransportRTT() const { DCHECK(thread_checker_.CalledOnValidThread()); if (network_quality_.transport_rtt() == nqe::internal::InvalidRTT()) return base::Optional(); return network_quality_.transport_rtt(); } base::Optional NetworkQualityEstimator::GetDownstreamThroughputKbps() const { DCHECK(thread_checker_.CalledOnValidThread()); if (network_quality_.downstream_throughput_kbps() == nqe::internal::kInvalidThroughput) { return base::Optional(); } return network_quality_.downstream_throughput_kbps(); } base::Optional NetworkQualityEstimator::GetBandwidthDelayProductKbits() const { DCHECK(thread_checker_.CalledOnValidThread()); return bandwidth_delay_product_kbits_; } void NetworkQualityEstimator::MaybeUpdateNetworkQualityFromCache( const nqe::internal::NetworkID& network_id, const nqe::internal::CachedNetworkQuality& cached_network_quality) { DCHECK(thread_checker_.CalledOnValidThread()); if (!params_->persistent_cache_reading_enabled()) return; if (network_id != current_network_id_) return; if (network_id.type != NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI && network_id.type != NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET && !disable_offline_check_) { return; } // Since the cached network quality is for the current network, add it to // the current observations. Observation http_rtt_observation( cached_network_quality.network_quality().http_rtt().InMilliseconds(), base::TimeTicks::Now(), INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE); AddAndNotifyObserversOfRTT(http_rtt_observation); Observation transport_rtt_observation( cached_network_quality.network_quality().transport_rtt().InMilliseconds(), base::TimeTicks::Now(), INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_TRANSPORT_CACHED_ESTIMATE); AddAndNotifyObserversOfRTT(transport_rtt_observation); // TODO(tbansal): crbug.com/673977: Remove this check. if (cached_network_quality.network_quality().downstream_throughput_kbps() != nqe::internal::kInvalidThroughput) { Observation throughput_observation( cached_network_quality.network_quality().downstream_throughput_kbps(), base::TimeTicks::Now(), INT32_MIN, NETWORK_QUALITY_OBSERVATION_SOURCE_HTTP_CACHED_ESTIMATE); AddAndNotifyObserversOfThroughput(throughput_observation); } ComputeEffectiveConnectionType(); } const char* NetworkQualityEstimator::GetNameForStatistic(int i) const { Statistic statistic = static_cast(i); switch (statistic) { case STATISTIC_LAST: NOTREACHED(); return ""; } NOTREACHED(); return ""; } } // namespace net