| // 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/base/network_quality_estimator.h" |
| |
| #include <float.h> |
| #include <algorithm> |
| #include <cmath> |
| #include <limits> |
| #include <vector> |
| |
| #include "base/logging.h" |
| #include "base/metrics/histogram.h" |
| #include "base/metrics/histogram_base.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "build/build_config.h" |
| #include "net/base/load_flags.h" |
| #include "net/base/load_timing_info.h" |
| #include "net/base/net_util.h" |
| #include "net/base/network_interfaces.h" |
| #include "net/url_request/url_request.h" |
| #include "url/gurl.h" |
| |
| #if defined(OS_ANDROID) |
| #include "net/android/network_library.h" |
| #endif // OS_ANDROID |
| |
| namespace { |
| |
| // Default value of the half life (in seconds) for computing time weighted |
| // percentiles. Every half life, the weight of all observations reduces by |
| // half. Lowering the half life would reduce the weight of older values faster. |
| const int kDefaultHalfLifeSeconds = 60; |
| |
| // Name of the variation parameter that holds the value of the half life (in |
| // seconds) of the observations. |
| const char kHalfLifeSecondsParamName[] = "HalfLifeSeconds"; |
| |
| // Returns a descriptive name corresponding to |connection_type|. |
| const char* GetNameForConnectionType( |
| net::NetworkChangeNotifier::ConnectionType connection_type) { |
| switch (connection_type) { |
| case net::NetworkChangeNotifier::CONNECTION_UNKNOWN: |
| return "Unknown"; |
| case net::NetworkChangeNotifier::CONNECTION_ETHERNET: |
| return "Ethernet"; |
| case net::NetworkChangeNotifier::CONNECTION_WIFI: |
| return "WiFi"; |
| case net::NetworkChangeNotifier::CONNECTION_2G: |
| return "2G"; |
| case net::NetworkChangeNotifier::CONNECTION_3G: |
| return "3G"; |
| case net::NetworkChangeNotifier::CONNECTION_4G: |
| return "4G"; |
| case net::NetworkChangeNotifier::CONNECTION_NONE: |
| return "None"; |
| case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH: |
| return "Bluetooth"; |
| default: |
| NOTREACHED(); |
| break; |
| } |
| return ""; |
| } |
| |
| // Suffix of the name of the variation parameter that contains the default RTT |
| // observation (in milliseconds). Complete name of the variation parameter |
| // would be |ConnectionType|.|kDefaultRTTMsecObservationSuffix| where |
| // |ConnectionType| is from |kConnectionTypeNames|. For example, variation |
| // parameter for Wi-Fi would be "WiFi.DefaultMedianRTTMsec". |
| const char kDefaultRTTMsecObservationSuffix[] = ".DefaultMedianRTTMsec"; |
| |
| // Suffix of the name of the variation parameter that contains the default |
| // downstream throughput observation (in Kbps). Complete name of the variation |
| // parameter would be |ConnectionType|.|kDefaultKbpsObservationSuffix| where |
| // |ConnectionType| is from |kConnectionTypeNames|. For example, variation |
| // parameter for Wi-Fi would be "WiFi.DefaultMedianKbps". |
| const char kDefaultKbpsObservationSuffix[] = ".DefaultMedianKbps"; |
| |
| // Computes and returns the weight multiplier per second. |
| // |variation_params| is the map containing all field trial parameters |
| // related to NetworkQualityEstimator field trial. |
| double GetWeightMultiplierPerSecond( |
| const std::map<std::string, std::string>& variation_params) { |
| int half_life_seconds = kDefaultHalfLifeSeconds; |
| int32_t variations_value = 0; |
| auto it = variation_params.find(kHalfLifeSecondsParamName); |
| if (it != variation_params.end() && |
| base::StringToInt(it->second, &variations_value) && |
| variations_value >= 1) { |
| half_life_seconds = variations_value; |
| } |
| DCHECK_GT(half_life_seconds, 0); |
| return exp(log(0.5) / half_life_seconds); |
| } |
| |
| // 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, |
| net::NetworkChangeNotifier::ConnectionType type, |
| int32_t max_limit) { |
| const base::LinearHistogram::Sample kLowerLimit = 1; |
| DCHECK_GT(max_limit, kLowerLimit); |
| const size_t kBucketCount = 50; |
| |
| // Prefix of network quality estimator histograms. |
| const char prefix[] = "NQE."; |
| return base::Histogram::FactoryGet( |
| prefix + statistic_name + GetNameForConnectionType(type), kLowerLimit, |
| max_limit, kBucketCount, base::HistogramBase::kUmaTargetedHistogramFlag); |
| } |
| |
| } // namespace |
| |
| namespace net { |
| |
| const int32_t NetworkQualityEstimator::kInvalidThroughput = 0; |
| |
| NetworkQualityEstimator::NetworkQualityEstimator( |
| scoped_ptr<ExternalEstimateProvider> external_estimates_provider, |
| const std::map<std::string, std::string>& variation_params) |
| : NetworkQualityEstimator(external_estimates_provider.Pass(), |
| variation_params, |
| false, |
| false) {} |
| |
| NetworkQualityEstimator::NetworkQualityEstimator( |
| scoped_ptr<ExternalEstimateProvider> external_estimates_provider, |
| const std::map<std::string, std::string>& variation_params, |
| bool allow_local_host_requests_for_tests, |
| bool allow_smaller_responses_for_tests) |
| : allow_localhost_requests_(allow_local_host_requests_for_tests), |
| allow_small_responses_(allow_smaller_responses_for_tests), |
| last_connection_change_(base::TimeTicks::Now()), |
| current_network_id_( |
| NetworkID(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN, |
| std::string())), |
| downstream_throughput_kbps_observations_( |
| GetWeightMultiplierPerSecond(variation_params)), |
| rtt_msec_observations_(GetWeightMultiplierPerSecond(variation_params)), |
| external_estimate_provider_(external_estimates_provider.Pass()) { |
| static_assert(kMinRequestDurationMicroseconds > 0, |
| "Minimum request duration must be > 0"); |
| static_assert(kDefaultHalfLifeSeconds > 0, |
| "Default half life duration must be > 0"); |
| static_assert(kMaximumNetworkQualityCacheSize > 0, |
| "Size of the network quality cache must be > 0"); |
| // This limit should not be increased unless the logic for removing the |
| // oldest cache entry is rewritten to use a doubly-linked-list LRU queue. |
| static_assert(kMaximumNetworkQualityCacheSize <= 10, |
| "Size of the network quality cache must <= 10"); |
| |
| ObtainOperatingParams(variation_params); |
| NetworkChangeNotifier::AddConnectionTypeObserver(this); |
| if (external_estimate_provider_) { |
| RecordExternalEstimateProviderMetrics( |
| EXTERNAL_ESTIMATE_PROVIDER_STATUS_AVAILABLE); |
| external_estimate_provider_->SetUpdatedEstimateDelegate(this); |
| QueryExternalEstimateProvider(); |
| } else { |
| RecordExternalEstimateProviderMetrics( |
| EXTERNAL_ESTIMATE_PROVIDER_STATUS_NOT_AVAILABLE); |
| } |
| current_network_id_ = GetCurrentNetworkID(); |
| AddDefaultEstimates(); |
| } |
| |
| // static |
| const base::TimeDelta NetworkQualityEstimator::InvalidRTT() { |
| return base::TimeDelta::Max(); |
| } |
| |
| void NetworkQualityEstimator::ObtainOperatingParams( |
| const std::map<std::string, std::string>& variation_params) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| for (size_t i = 0; i <= NetworkChangeNotifier::CONNECTION_LAST; ++i) { |
| NetworkChangeNotifier::ConnectionType type = |
| static_cast<NetworkChangeNotifier::ConnectionType>(i); |
| DCHECK_EQ(InvalidRTT(), default_observations_[i].rtt()); |
| DCHECK_EQ(kInvalidThroughput, |
| default_observations_[i].downstream_throughput_kbps()); |
| int32_t variations_value = kMinimumRTTVariationParameterMsec - 1; |
| // Name of the parameter that holds the RTT value for this connection type. |
| std::string rtt_parameter_name = |
| std::string(GetNameForConnectionType(type)) |
| .append(kDefaultRTTMsecObservationSuffix); |
| auto it = variation_params.find(rtt_parameter_name); |
| if (it != variation_params.end() && |
| base::StringToInt(it->second, &variations_value) && |
| variations_value >= kMinimumRTTVariationParameterMsec) { |
| default_observations_[i] = |
| NetworkQuality(base::TimeDelta::FromMilliseconds(variations_value), |
| default_observations_[i].downstream_throughput_kbps()); |
| } |
| |
| variations_value = kMinimumThroughputVariationParameterKbps - 1; |
| // Name of the parameter that holds the Kbps value for this connection |
| // type. |
| std::string kbps_parameter_name = |
| std::string(GetNameForConnectionType(type)) |
| .append(kDefaultKbpsObservationSuffix); |
| it = variation_params.find(kbps_parameter_name); |
| if (it != variation_params.end() && |
| base::StringToInt(it->second, &variations_value) && |
| variations_value >= kMinimumThroughputVariationParameterKbps) { |
| default_observations_[i] = |
| NetworkQuality(default_observations_[i].rtt(), variations_value); |
| } |
| } |
| } |
| |
| void NetworkQualityEstimator::AddDefaultEstimates() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (default_observations_[current_network_id_.type].rtt() != InvalidRTT()) { |
| Observation rtt_observation( |
| default_observations_[current_network_id_.type].rtt().InMilliseconds(), |
| base::TimeTicks::Now(), DEFAULT_FROM_PLATFORM); |
| rtt_msec_observations_.AddObservation(rtt_observation); |
| NotifyObserversOfRTT(rtt_observation); |
| } |
| if (default_observations_[current_network_id_.type] |
| .downstream_throughput_kbps() != kInvalidThroughput) { |
| Observation throughput_observation( |
| default_observations_[current_network_id_.type] |
| .downstream_throughput_kbps(), |
| base::TimeTicks::Now(), DEFAULT_FROM_PLATFORM); |
| downstream_throughput_kbps_observations_.AddObservation( |
| throughput_observation); |
| NotifyObserversOfThroughput(throughput_observation); |
| } |
| } |
| |
| NetworkQualityEstimator::~NetworkQualityEstimator() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| NetworkChangeNotifier::RemoveConnectionTypeObserver(this); |
| } |
| |
| void NetworkQualityEstimator::NotifyHeadersReceived(const URLRequest& request) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!RequestProvidesUsefulObservations(request)) |
| return; |
| |
| // Update |estimated_median_network_quality_| if this is a main frame request. |
| if (request.load_flags() & LOAD_MAIN_FRAME) { |
| estimated_median_network_quality_ = NetworkQuality( |
| GetRTTEstimateInternal(base::TimeTicks(), 50), |
| GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 50)); |
| } |
| |
| base::TimeTicks now = base::TimeTicks::Now(); |
| 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; |
| } |
| |
| // Time when the resource was requested. |
| base::TimeTicks request_start_time = load_timing_info.send_start; |
| |
| // Time when the headers were received. |
| base::TimeTicks headers_received_time = load_timing_info.receive_headers_end; |
| |
| // Duration between when the resource was requested and when response |
| // headers were received. |
| base::TimeDelta observed_rtt = headers_received_time - request_start_time; |
| DCHECK_GE(observed_rtt, base::TimeDelta()); |
| if (observed_rtt < peak_network_quality_.rtt()) { |
| peak_network_quality_ = NetworkQuality( |
| observed_rtt, peak_network_quality_.downstream_throughput_kbps()); |
| } |
| |
| Observation rtt_observation(observed_rtt.InMilliseconds(), now, |
| URL_REQUEST); |
| rtt_msec_observations_.AddObservation(rtt_observation); |
| NotifyObserversOfRTT(rtt_observation); |
| |
| // Compare the RTT observation with the estimated value and record it. |
| if (estimated_median_network_quality_.rtt() != InvalidRTT()) { |
| RecordRTTUMA(estimated_median_network_quality_.rtt().InMilliseconds(), |
| observed_rtt.InMilliseconds()); |
| } |
| } |
| |
| void NetworkQualityEstimator::NotifyRequestCompleted( |
| const URLRequest& request) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!RequestProvidesUsefulObservations(request)) |
| return; |
| |
| base::TimeTicks now = base::TimeTicks::Now(); |
| 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; |
| } |
| |
| // Time since the resource was requested. |
| // TODO(tbansal): Change the start time to receive_headers_end, once we use |
| // NetworkActivityMonitor. |
| base::TimeDelta request_start_to_completed = |
| now - load_timing_info.send_start; |
| DCHECK_GE(request_start_to_completed, base::TimeDelta()); |
| |
| // Ignore tiny transfers which will not produce accurate rates. |
| // Ignore short duration transfers. |
| // Skip the checks if |allow_small_responses_| is true. |
| if (!allow_small_responses_ && |
| (request.GetTotalReceivedBytes() < kMinTransferSizeInBytes || |
| request_start_to_completed < base::TimeDelta::FromMicroseconds( |
| kMinRequestDurationMicroseconds))) { |
| return; |
| } |
| |
| double downstream_kbps = request.GetTotalReceivedBytes() * 8.0 / 1000.0 / |
| request_start_to_completed.InSecondsF(); |
| DCHECK_GE(downstream_kbps, 0.0); |
| |
| // Check overflow errors. This may happen if the downstream_kbps is more than |
| // 2 * 10^9 (= 2000 Gbps). |
| if (downstream_kbps >= std::numeric_limits<int32_t>::max()) |
| downstream_kbps = std::numeric_limits<int32_t>::max(); |
| |
| int32_t downstream_kbps_as_integer = static_cast<int32_t>(downstream_kbps); |
| |
| // Round up |downstream_kbps_as_integer|. If the |downstream_kbps_as_integer| |
| // is less than 1, it is set to 1 to differentiate from case when there is no |
| // connection. |
| if (downstream_kbps - downstream_kbps_as_integer > 0) |
| downstream_kbps_as_integer++; |
| |
| DCHECK_GT(downstream_kbps_as_integer, 0.0); |
| if (downstream_kbps_as_integer > |
| peak_network_quality_.downstream_throughput_kbps()) |
| peak_network_quality_ = |
| NetworkQuality(peak_network_quality_.rtt(), downstream_kbps_as_integer); |
| |
| Observation throughput_observation(downstream_kbps_as_integer, now, |
| URL_REQUEST); |
| downstream_throughput_kbps_observations_.AddObservation( |
| throughput_observation); |
| NotifyObserversOfThroughput(throughput_observation); |
| } |
| |
| 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); |
| } |
| |
| void NetworkQualityEstimator::RecordRTTUMA(int32_t estimated_value_msec, |
| int32_t actual_value_msec) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| // Record the difference between the actual and the estimated value. |
| if (estimated_value_msec >= actual_value_msec) { |
| base::HistogramBase* difference_rtt = |
| GetHistogram("DifferenceRTTEstimatedAndActual.", |
| current_network_id_.type, 10 * 1000); // 10 seconds |
| difference_rtt->Add(estimated_value_msec - actual_value_msec); |
| } else { |
| base::HistogramBase* difference_rtt = |
| GetHistogram("DifferenceRTTActualAndEstimated.", |
| current_network_id_.type, 10 * 1000); // 10 seconds |
| difference_rtt->Add(actual_value_msec - estimated_value_msec); |
| } |
| |
| // Record all the RTT observations. |
| base::HistogramBase* rtt_observations = |
| GetHistogram("RTTObservations.", current_network_id_.type, |
| 10 * 1000); // 10 seconds upper bound |
| rtt_observations->Add(actual_value_msec); |
| |
| if (actual_value_msec == 0) |
| return; |
| |
| int32 ratio = (estimated_value_msec * 100) / actual_value_msec; |
| |
| // Record the accuracy of estimation by recording the ratio of estimated |
| // value to the actual value. |
| base::HistogramBase* ratio_median_rtt = GetHistogram( |
| "RatioEstimatedToActualRTT.", current_network_id_.type, 1000); |
| ratio_median_rtt->Add(ratio); |
| } |
| |
| bool NetworkQualityEstimator::RequestProvidesUsefulObservations( |
| const URLRequest& request) const { |
| return request.url().is_valid() && |
| (allow_localhost_requests_ || !IsLocalhost(request.url().host())) && |
| request.url().SchemeIsHTTPOrHTTPS() && |
| // 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()); |
| if (peak_network_quality_.rtt() != InvalidRTT()) { |
| switch (current_network_id_.type) { |
| case NetworkChangeNotifier::CONNECTION_UNKNOWN: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Unknown", |
| peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_ETHERNET: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Ethernet", |
| peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_WIFI: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Wifi", peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_2G: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.2G", peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_3G: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.3G", peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_4G: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.4G", peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_NONE: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.None", peak_network_quality_.rtt()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_BLUETOOTH: |
| UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Bluetooth", |
| peak_network_quality_.rtt()); |
| break; |
| default: |
| NOTREACHED() << "Unexpected connection type = " |
| << current_network_id_.type; |
| break; |
| } |
| } |
| |
| if (peak_network_quality_.downstream_throughput_kbps() != |
| kInvalidThroughput) { |
| switch (current_network_id_.type) { |
| case NetworkChangeNotifier::CONNECTION_UNKNOWN: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.Unknown", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_ETHERNET: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.Ethernet", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_WIFI: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.Wifi", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_2G: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.2G", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_3G: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.3G", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_4G: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.4G", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_NONE: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.None", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| case NetworkChangeNotifier::CONNECTION_BLUETOOTH: |
| UMA_HISTOGRAM_COUNTS( |
| "NQE.PeakKbps.Bluetooth", |
| peak_network_quality_.downstream_throughput_kbps()); |
| break; |
| default: |
| NOTREACHED() << "Unexpected connection type = " |
| << current_network_id_.type; |
| break; |
| } |
| } |
| |
| base::TimeDelta rtt = GetRTTEstimateInternal(base::TimeTicks(), 50); |
| if (rtt != InvalidRTT()) { |
| // Add the 50th percentile value. |
| base::HistogramBase* rtt_percentile = |
| GetHistogram("RTT.Percentile50.", current_network_id_.type, |
| 10 * 1000); // 10 seconds |
| 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(base::TimeTicks(), kPercentiles[i]); |
| |
| rtt_percentile = GetHistogram( |
| "RTT.Percentile" + base::IntToString(kPercentiles[i]) + ".", |
| current_network_id_.type, 10 * 1000); // 10 seconds |
| rtt_percentile->Add(rtt.InMilliseconds()); |
| } |
| } |
| |
| // Write the estimates of the previous network to the cache. |
| CacheNetworkQualityEstimate(); |
| |
| // Clear the local state. |
| last_connection_change_ = base::TimeTicks::Now(); |
| peak_network_quality_ = NetworkQuality(); |
| downstream_throughput_kbps_observations_.Clear(); |
| rtt_msec_observations_.Clear(); |
| current_network_id_ = GetCurrentNetworkID(); |
| |
| QueryExternalEstimateProvider(); |
| |
| // Read any cached estimates for the new network. If cached estimates are |
| // unavailable, add the default estimates. |
| if (!ReadCachedNetworkQualityEstimate()) |
| AddDefaultEstimates(); |
| estimated_median_network_quality_ = NetworkQuality(); |
| } |
| |
| bool NetworkQualityEstimator::GetRTTEstimate(base::TimeDelta* rtt) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(rtt); |
| if (rtt_msec_observations_.Size() == 0) { |
| *rtt = InvalidRTT(); |
| return false; |
| } |
| *rtt = GetRTTEstimateInternal(base::TimeTicks(), 50); |
| return (*rtt != InvalidRTT()); |
| } |
| |
| bool NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimate( |
| int32_t* kbps) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(kbps); |
| if (downstream_throughput_kbps_observations_.Size() == 0) { |
| *kbps = kInvalidThroughput; |
| return false; |
| } |
| *kbps = GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 50); |
| return (*kbps != kInvalidThroughput); |
| } |
| |
| bool NetworkQualityEstimator::GetRecentMedianRTT( |
| const base::TimeTicks& begin_timestamp, |
| base::TimeDelta* rtt) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(rtt); |
| *rtt = GetRTTEstimateInternal(begin_timestamp, 50); |
| return (*rtt != InvalidRTT()); |
| } |
| |
| bool NetworkQualityEstimator::GetRecentMedianDownlinkThroughputKbps( |
| const base::TimeTicks& begin_timestamp, |
| int32_t* kbps) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(kbps); |
| *kbps = GetDownlinkThroughputKbpsEstimateInternal(begin_timestamp, 50); |
| return (*kbps != kInvalidThroughput); |
| } |
| |
| NetworkQualityEstimator::Observation::Observation(int32_t value, |
| base::TimeTicks timestamp, |
| ObservationSource source) |
| : value(value), timestamp(timestamp), source(source) { |
| DCHECK_GE(value, 0); |
| DCHECK(!timestamp.is_null()); |
| } |
| |
| NetworkQualityEstimator::Observation::~Observation() { |
| } |
| |
| NetworkQualityEstimator::ObservationBuffer::ObservationBuffer( |
| double weight_multiplier_per_second) |
| : weight_multiplier_per_second_(weight_multiplier_per_second) { |
| static_assert(kMaximumObservationsBufferSize > 0U, |
| "Minimum size of observation buffer must be > 0"); |
| DCHECK_GE(weight_multiplier_per_second_, 0.0); |
| DCHECK_LE(weight_multiplier_per_second_, 1.0); |
| } |
| |
| NetworkQualityEstimator::ObservationBuffer::~ObservationBuffer() { |
| } |
| |
| void NetworkQualityEstimator::ObservationBuffer::AddObservation( |
| const Observation& observation) { |
| DCHECK_LE(observations_.size(), |
| static_cast<size_t>(kMaximumObservationsBufferSize)); |
| // Evict the oldest element if the buffer is already full. |
| if (observations_.size() == kMaximumObservationsBufferSize) |
| observations_.pop_front(); |
| |
| observations_.push_back(observation); |
| DCHECK_LE(observations_.size(), |
| static_cast<size_t>(kMaximumObservationsBufferSize)); |
| } |
| |
| size_t NetworkQualityEstimator::ObservationBuffer::Size() const { |
| return observations_.size(); |
| } |
| |
| void NetworkQualityEstimator::ObservationBuffer::Clear() { |
| observations_.clear(); |
| DCHECK(observations_.empty()); |
| } |
| |
| base::TimeDelta NetworkQualityEstimator::GetRTTEstimateInternal( |
| const base::TimeTicks& begin_timestamp, |
| int percentile) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_GE(percentile, 0); |
| DCHECK_LE(percentile, 100); |
| if (rtt_msec_observations_.Size() == 0) |
| return InvalidRTT(); |
| |
| // RTT observations are sorted by duration from shortest to longest, thus |
| // a higher percentile RTT will have a longer RTT than a lower percentile. |
| base::TimeDelta rtt = InvalidRTT(); |
| int32_t rtt_result = -1; |
| if (rtt_msec_observations_.GetPercentile(begin_timestamp, &rtt_result, |
| percentile)) { |
| rtt = base::TimeDelta::FromMilliseconds(rtt_result); |
| } |
| return rtt; |
| } |
| |
| int32_t NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimateInternal( |
| const base::TimeTicks& begin_timestamp, |
| int percentile) const { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_GE(percentile, 0); |
| DCHECK_LE(percentile, 100); |
| if (downstream_throughput_kbps_observations_.Size() == 0) |
| return kInvalidThroughput; |
| |
| // Throughput observations are sorted by kbps from slowest to fastest, |
| // thus a higher percentile throughput will be faster than a lower one. |
| int32_t kbps = kInvalidThroughput; |
| downstream_throughput_kbps_observations_.GetPercentile(begin_timestamp, &kbps, |
| 100 - percentile); |
| return kbps; |
| } |
| |
| void NetworkQualityEstimator::ObservationBuffer::ComputeWeightedObservations( |
| const base::TimeTicks& begin_timestamp, |
| std::vector<WeightedObservation>& weighted_observations, |
| double* total_weight) const { |
| weighted_observations.clear(); |
| double total_weight_observations = 0.0; |
| base::TimeTicks now = base::TimeTicks::Now(); |
| |
| for (const auto& observation : observations_) { |
| if (observation.timestamp < begin_timestamp) |
| continue; |
| base::TimeDelta time_since_sample_taken = now - observation.timestamp; |
| double weight = |
| pow(weight_multiplier_per_second_, time_since_sample_taken.InSeconds()); |
| weight = std::max(DBL_MIN, std::min(1.0, weight)); |
| |
| weighted_observations.push_back( |
| WeightedObservation(observation.value, weight)); |
| total_weight_observations += weight; |
| } |
| |
| // Sort the samples by value in ascending order. |
| std::sort(weighted_observations.begin(), weighted_observations.end()); |
| *total_weight = total_weight_observations; |
| } |
| |
| bool NetworkQualityEstimator::ObservationBuffer::GetPercentile( |
| const base::TimeTicks& begin_timestamp, |
| int32_t* result, |
| int percentile) const { |
| DCHECK(result); |
| // Stores WeightedObservation in increasing order of value. |
| std::vector<WeightedObservation> weighted_observations; |
| |
| // Total weight of all observations in |weighted_observations|. |
| double total_weight = 0.0; |
| |
| ComputeWeightedObservations(begin_timestamp, weighted_observations, |
| &total_weight); |
| if (weighted_observations.empty()) |
| return false; |
| |
| DCHECK(!weighted_observations.empty()); |
| DCHECK_GT(total_weight, 0.0); |
| |
| // weighted_observations may have a smaller size than observations_ since the |
| // former contains only the observations later than begin_timestamp. |
| DCHECK_GE(observations_.size(), weighted_observations.size()); |
| |
| double desired_weight = percentile / 100.0 * total_weight; |
| |
| double cumulative_weight_seen_so_far = 0.0; |
| for (const auto& weighted_observation : weighted_observations) { |
| cumulative_weight_seen_so_far += weighted_observation.weight; |
| |
| // TODO(tbansal): Consider interpolating between observations. |
| if (cumulative_weight_seen_so_far >= desired_weight) { |
| *result = weighted_observation.value; |
| return true; |
| } |
| } |
| |
| // Computation may reach here due to floating point errors. This may happen |
| // if |percentile| was 100 (or close to 100), and |desired_weight| was |
| // slightly larger than |total_weight| (due to floating point errors). |
| // In this case, we return the highest |value| among all observations. |
| // This is same as value of the last observation in the sorted vector. |
| *result = weighted_observations.at(weighted_observations.size() - 1).value; |
| return true; |
| } |
| |
| NetworkQualityEstimator::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) { |
| NetworkQualityEstimator::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_CHROMEOS) || \ |
| 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 the network name is unavailable, caching should not be performed. |
| if (current_network_id_.id.empty()) |
| return false; |
| |
| CachedNetworkQualities::const_iterator it = |
| cached_network_qualities_.find(current_network_id_); |
| |
| if (it == cached_network_qualities_.end()) |
| return false; |
| |
| NetworkQuality network_quality(it->second.network_quality()); |
| |
| DCHECK_NE(InvalidRTT(), network_quality.rtt()); |
| DCHECK_NE(kInvalidThroughput, network_quality.downstream_throughput_kbps()); |
| |
| Observation througphput_observation( |
| network_quality.downstream_throughput_kbps(), base::TimeTicks::Now(), |
| CACHED_ESTIMATE); |
| downstream_throughput_kbps_observations_.AddObservation( |
| througphput_observation); |
| NotifyObserversOfThroughput(througphput_observation); |
| |
| Observation rtt_observation(network_quality.rtt().InMilliseconds(), |
| base::TimeTicks::Now(), CACHED_ESTIMATE); |
| rtt_msec_observations_.AddObservation(rtt_observation); |
| NotifyObserversOfRTT(rtt_observation); |
| |
| return true; |
| } |
| |
| void NetworkQualityEstimator::OnUpdatedEstimateAvailable() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(external_estimate_provider_); |
| |
| RecordExternalEstimateProviderMetrics( |
| EXTERNAL_ESTIMATE_PROVIDER_STATUS_CALLBACK); |
| QueryExternalEstimateProvider(); |
| } |
| |
| void NetworkQualityEstimator::QueryExternalEstimateProvider() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| if (!external_estimate_provider_) |
| return; |
| RecordExternalEstimateProviderMetrics( |
| EXTERNAL_ESTIMATE_PROVIDER_STATUS_QUERIED); |
| |
| base::TimeDelta time_since_last_update; |
| |
| // Request a new estimate if estimate is not available, or if the available |
| // estimate is not fresh. |
| if (!external_estimate_provider_->GetTimeSinceLastUpdate( |
| &time_since_last_update) || |
| time_since_last_update > |
| base::TimeDelta::FromMilliseconds( |
| kExternalEstimateProviderFreshnessDurationMsec)) { |
| // Request the external estimate provider for updated estimates. When the |
| // updates estimates are available, OnUpdatedEstimateAvailable() will be |
| // called. |
| external_estimate_provider_->Update(); |
| return; |
| } |
| |
| RecordExternalEstimateProviderMetrics( |
| EXTERNAL_ESTIMATE_PROVIDER_STATUS_QUERY_SUCCESSFUL); |
| base::TimeDelta rtt; |
| if (external_estimate_provider_->GetRTT(&rtt)) { |
| rtt_msec_observations_.AddObservation(Observation( |
| rtt.InMilliseconds(), base::TimeTicks::Now(), EXTERNAL_ESTIMATE)); |
| } |
| |
| int32_t downstream_throughput_kbps; |
| if (external_estimate_provider_->GetDownstreamThroughputKbps( |
| &downstream_throughput_kbps)) { |
| downstream_throughput_kbps_observations_.AddObservation(Observation( |
| downstream_throughput_kbps, base::TimeTicks::Now(), EXTERNAL_ESTIMATE)); |
| } |
| } |
| |
| void NetworkQualityEstimator::CacheNetworkQualityEstimate() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK_LE(cached_network_qualities_.size(), |
| static_cast<size_t>(kMaximumNetworkQualityCacheSize)); |
| |
| // If the network name is unavailable, caching should not be performed. |
| if (current_network_id_.id.empty()) |
| return; |
| |
| NetworkQuality network_quality = NetworkQuality( |
| GetRTTEstimateInternal(base::TimeTicks(), 50), |
| GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 50)); |
| if (network_quality.rtt() == InvalidRTT() || |
| network_quality.downstream_throughput_kbps() == kInvalidThroughput) { |
| return; |
| } |
| |
| if (cached_network_qualities_.size() == kMaximumNetworkQualityCacheSize) { |
| // Remove the oldest entry. |
| CachedNetworkQualities::iterator oldest_entry_iterator = |
| cached_network_qualities_.begin(); |
| |
| for (CachedNetworkQualities::iterator it = |
| cached_network_qualities_.begin(); |
| it != cached_network_qualities_.end(); ++it) { |
| if ((it->second).OlderThan(oldest_entry_iterator->second)) |
| oldest_entry_iterator = it; |
| } |
| cached_network_qualities_.erase(oldest_entry_iterator); |
| } |
| DCHECK_LT(cached_network_qualities_.size(), |
| static_cast<size_t>(kMaximumNetworkQualityCacheSize)); |
| |
| cached_network_qualities_.insert(std::make_pair( |
| current_network_id_, CachedNetworkQuality(network_quality))); |
| DCHECK_LE(cached_network_qualities_.size(), |
| static_cast<size_t>(kMaximumNetworkQualityCacheSize)); |
| } |
| |
| scoped_ptr<SocketPerformanceWatcher> |
| NetworkQualityEstimator::CreateSocketPerformanceWatcher( |
| const Protocol protocol) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| return scoped_ptr<SocketPerformanceWatcher>( |
| new SocketPerformanceWatcher(protocol, this)); |
| } |
| |
| void NetworkQualityEstimator::OnUpdatedRTTAvailable( |
| const Protocol protocol, |
| const base::TimeDelta& rtt) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| switch (protocol) { |
| case PROTOCOL_TCP: |
| NotifyObserversOfRTT( |
| Observation(rtt.InMilliseconds(), base::TimeTicks::Now(), TCP)); |
| return; |
| case PROTOCOL_QUIC: |
| NotifyObserversOfRTT( |
| Observation(rtt.InMilliseconds(), base::TimeTicks::Now(), QUIC)); |
| return; |
| default: |
| NOTREACHED(); |
| } |
| } |
| |
| void NetworkQualityEstimator::NotifyObserversOfRTT( |
| const Observation& observation) { |
| FOR_EACH_OBSERVER(RTTObserver, rtt_observer_list_, |
| OnRTTObservation(observation.value, observation.timestamp, |
| observation.source)); |
| } |
| |
| void NetworkQualityEstimator::NotifyObserversOfThroughput( |
| const Observation& observation) { |
| FOR_EACH_OBSERVER( |
| ThroughputObserver, throughput_observer_list_, |
| OnThroughputObservation(observation.value, observation.timestamp, |
| observation.source)); |
| } |
| |
| NetworkQualityEstimator::CachedNetworkQuality::CachedNetworkQuality( |
| const NetworkQuality& network_quality) |
| : last_update_time_(base::TimeTicks::Now()), |
| network_quality_(network_quality) { |
| } |
| |
| NetworkQualityEstimator::CachedNetworkQuality::CachedNetworkQuality( |
| const CachedNetworkQuality& other) |
| : last_update_time_(other.last_update_time_), |
| network_quality_(other.network_quality_) { |
| } |
| |
| NetworkQualityEstimator::CachedNetworkQuality::~CachedNetworkQuality() { |
| } |
| |
| bool NetworkQualityEstimator::CachedNetworkQuality::OlderThan( |
| const CachedNetworkQuality& cached_network_quality) const { |
| return last_update_time_ < cached_network_quality.last_update_time_; |
| } |
| |
| NetworkQualityEstimator::NetworkQuality::NetworkQuality() |
| : NetworkQuality(NetworkQualityEstimator::InvalidRTT(), |
| NetworkQualityEstimator::kInvalidThroughput) {} |
| |
| NetworkQualityEstimator::NetworkQuality::NetworkQuality( |
| const base::TimeDelta& rtt, |
| int32_t downstream_throughput_kbps) |
| : rtt_(rtt), downstream_throughput_kbps_(downstream_throughput_kbps) { |
| DCHECK_GE(rtt_, base::TimeDelta()); |
| DCHECK_GE(downstream_throughput_kbps_, 0); |
| } |
| |
| NetworkQualityEstimator::NetworkQuality::NetworkQuality( |
| const NetworkQuality& other) |
| : NetworkQuality(other.rtt_, other.downstream_throughput_kbps_) {} |
| |
| NetworkQualityEstimator::NetworkQuality::~NetworkQuality() {} |
| |
| NetworkQualityEstimator::NetworkQuality& |
| NetworkQualityEstimator::NetworkQuality:: |
| operator=(const NetworkQuality& other) { |
| rtt_ = other.rtt_; |
| downstream_throughput_kbps_ = other.downstream_throughput_kbps_; |
| return *this; |
| } |
| |
| } // namespace net |