// Copyright 2016 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_params.h" #include #include "base/strings/string_number_conversions.h" #include "base/time/time.h" namespace net { const char kForceEffectiveConnectionType[] = "force_effective_connection_type"; const char kEffectiveConnectionTypeSlow2GOnCellular[] = "Slow-2G-On-Cellular"; const base::TimeDelta kHttpRttEffectiveConnectionTypeThresholds[EFFECTIVE_CONNECTION_TYPE_LAST] = {base::TimeDelta::FromMilliseconds(0), base::TimeDelta::FromMilliseconds(0), base::TimeDelta::FromMilliseconds(2010), base::TimeDelta::FromMilliseconds(1420), base::TimeDelta::FromMilliseconds(272), base::TimeDelta::FromMilliseconds(0)}; namespace { // Minimum valid value of the variation parameter that holds RTT (in // milliseconds) values. static const int kMinimumRTTVariationParameterMsec = 1; // Minimum valid value of the variation parameter that holds throughput (in // kilobits per second) values. static const int kMinimumThroughputVariationParameterKbps = 1; // Returns the value of |parameter_name| read from |params|. If the // value is unavailable from |params|, then |default_value| is returned. int64_t GetValueForVariationParam( const std::map& params, const std::string& parameter_name, int64_t default_value) { const auto it = params.find(parameter_name); int64_t variations_value = default_value; if (it != params.end() && base::StringToInt64(it->second, &variations_value)) { return variations_value; } return default_value; } // Returns the variation value for |parameter_name|. If the value is // unavailable, |default_value| is returned. double GetDoubleValueForVariationParamWithDefaultValue( const std::map& params, const std::string& parameter_name, double default_value) { const auto it = params.find(parameter_name); if (it == params.end()) return default_value; double variations_value = default_value; if (!base::StringToDouble(it->second, &variations_value)) return default_value; return variations_value; } // Returns the variation value for |parameter_name|. If the value is // unavailable, |default_value| is returned. std::string GetStringValueForVariationParamWithDefaultValue( const std::map& params, const std::string& parameter_name, const std::string& default_value) { const auto it = params.find(parameter_name); if (it == params.end()) return default_value; return it->second; } double GetWeightMultiplierPerSecond( const std::map& params) { // 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. int half_life_seconds = 60; int32_t variations_value = 0; auto it = params.find("HalfLifeSeconds"); if (it != params.end() && base::StringToInt(it->second, &variations_value) && variations_value >= 1) { half_life_seconds = variations_value; } DCHECK_GT(half_life_seconds, 0); return pow(0.5, 1.0 / half_life_seconds); } bool GetPersistentCacheReadingEnabled( const std::map& params) { if (GetStringValueForVariationParamWithDefaultValue( params, "persistent_cache_reading_enabled", "true") != "true") { return false; } return true; } base::TimeDelta GetMinSocketWatcherNotificationInterval( const std::map& params) { // Use 1000 milliseconds as the default value. return base::TimeDelta::FromMilliseconds(GetValueForVariationParam( params, "min_socket_watcher_notification_interval_msec", 1000)); } // static const char* GetNameForConnectionTypeInternal( NetworkChangeNotifier::ConnectionType connection_type) { switch (connection_type) { case NetworkChangeNotifier::CONNECTION_UNKNOWN: return "Unknown"; case NetworkChangeNotifier::CONNECTION_ETHERNET: return "Ethernet"; case NetworkChangeNotifier::CONNECTION_WIFI: return "WiFi"; case NetworkChangeNotifier::CONNECTION_2G: return "2G"; case NetworkChangeNotifier::CONNECTION_3G: return "3G"; case NetworkChangeNotifier::CONNECTION_4G: return "4G"; case NetworkChangeNotifier::CONNECTION_NONE: return "None"; case NetworkChangeNotifier::CONNECTION_BLUETOOTH: return "Bluetooth"; } return ""; } // Sets the default observation for different connection types in // |default_observations|. The default observations are different for // different connection types (e.g., 2G, 3G, 4G, WiFi). The default // observations may be used to determine the network quality in absence of any // other information. void ObtainDefaultObservations( const std::map& params, nqe::internal::NetworkQuality default_observations[]) { for (size_t i = 0; i < NetworkChangeNotifier::CONNECTION_LAST; ++i) { DCHECK_EQ(nqe::internal::InvalidRTT(), default_observations[i].http_rtt()); DCHECK_EQ(nqe::internal::InvalidRTT(), default_observations[i].transport_rtt()); DCHECK_EQ(nqe::internal::INVALID_RTT_THROUGHPUT, default_observations[i].downstream_throughput_kbps()); } // Default observations for HTTP RTT, transport RTT, and downstream throughput // Kbps for the various connection types. These may be overridden by // variations params. The default observation for a connection type // corresponds to typical network quality for that connection type. default_observations[NetworkChangeNotifier::CONNECTION_UNKNOWN] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(115), base::TimeDelta::FromMilliseconds(55), 1961); default_observations[NetworkChangeNotifier::CONNECTION_ETHERNET] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(90), base::TimeDelta::FromMilliseconds(33), 1456); default_observations[NetworkChangeNotifier::CONNECTION_WIFI] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(116), base::TimeDelta::FromMilliseconds(66), 2658); default_observations[NetworkChangeNotifier::CONNECTION_2G] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(1726), base::TimeDelta::FromMilliseconds(1531), 74); default_observations[NetworkChangeNotifier::CONNECTION_3G] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(273), base::TimeDelta::FromMilliseconds(209), 749); default_observations[NetworkChangeNotifier::CONNECTION_4G] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(137), base::TimeDelta::FromMilliseconds(80), 1708); default_observations[NetworkChangeNotifier::CONNECTION_NONE] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(163), base::TimeDelta::FromMilliseconds(83), 575); default_observations[NetworkChangeNotifier::CONNECTION_BLUETOOTH] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(385), base::TimeDelta::FromMilliseconds(318), 476); // Override using the values provided via variation params. for (size_t i = 0; i <= NetworkChangeNotifier::CONNECTION_LAST; ++i) { NetworkChangeNotifier::ConnectionType type = static_cast(i); int32_t variations_value = kMinimumRTTVariationParameterMsec - 1; std::string parameter_name = std::string(GetNameForConnectionTypeInternal(type)) .append(".DefaultMedianRTTMsec"); auto it = params.find(parameter_name); if (it != params.end() && base::StringToInt(it->second, &variations_value) && variations_value >= kMinimumRTTVariationParameterMsec) { default_observations[i] = nqe::internal::NetworkQuality( base::TimeDelta::FromMilliseconds(variations_value), default_observations[i].transport_rtt(), default_observations[i].downstream_throughput_kbps()); } variations_value = kMinimumRTTVariationParameterMsec - 1; parameter_name = std::string(GetNameForConnectionTypeInternal(type)) .append(".DefaultMedianTransportRTTMsec"); it = params.find(parameter_name); if (it != params.end() && base::StringToInt(it->second, &variations_value) && variations_value >= kMinimumRTTVariationParameterMsec) { default_observations[i] = nqe::internal::NetworkQuality( default_observations[i].http_rtt(), base::TimeDelta::FromMilliseconds(variations_value), default_observations[i].downstream_throughput_kbps()); } variations_value = kMinimumThroughputVariationParameterKbps - 1; parameter_name = std::string(GetNameForConnectionTypeInternal(type)) .append(".DefaultMedianKbps"); it = params.find(parameter_name); if (it != params.end() && base::StringToInt(it->second, &variations_value) && variations_value >= kMinimumThroughputVariationParameterKbps) { default_observations[i] = nqe::internal::NetworkQuality( default_observations[i].http_rtt(), default_observations[i].transport_rtt(), variations_value); } } } // Sets |typical_network_quality| to typical network quality for different // effective connection types. void ObtainTypicalNetworkQualities( const std::map& params, nqe::internal::NetworkQuality typical_network_quality[]) { for (size_t i = 0; i < EFFECTIVE_CONNECTION_TYPE_LAST; ++i) { DCHECK_EQ(nqe::internal::InvalidRTT(), typical_network_quality[i].http_rtt()); DCHECK_EQ(nqe::internal::InvalidRTT(), typical_network_quality[i].transport_rtt()); DCHECK_EQ(nqe::internal::INVALID_RTT_THROUGHPUT, typical_network_quality[i].downstream_throughput_kbps()); } typical_network_quality[EFFECTIVE_CONNECTION_TYPE_SLOW_2G] = nqe::internal::NetworkQuality( // Set to the 77.5th percentile of 2G RTT observations on Android. // This corresponds to the median RTT observation when effective // connection type is Slow 2G. base::TimeDelta::FromMilliseconds(3600), base::TimeDelta::FromMilliseconds(3000), 40); typical_network_quality[EFFECTIVE_CONNECTION_TYPE_2G] = nqe::internal::NetworkQuality( // Set to the 58th percentile of 2G RTT observations on Android. This // corresponds to the median RTT observation when effective connection // type is 2G. base::TimeDelta::FromMilliseconds(1800), base::TimeDelta::FromMilliseconds(1500), 75); typical_network_quality[EFFECTIVE_CONNECTION_TYPE_3G] = nqe::internal::NetworkQuality( // Set to the 75th percentile of 3G RTT observations on Android. This // corresponds to the median RTT observation when effective connection // type is 3G. base::TimeDelta::FromMilliseconds(450), base::TimeDelta::FromMilliseconds(400), 400); // Set to the 25th percentile of 3G RTT observations on Android. typical_network_quality[EFFECTIVE_CONNECTION_TYPE_4G] = nqe::internal::NetworkQuality(base::TimeDelta::FromMilliseconds(175), base::TimeDelta::FromMilliseconds(125), 1600); static_assert( EFFECTIVE_CONNECTION_TYPE_4G + 1 == EFFECTIVE_CONNECTION_TYPE_LAST, "Missing effective connection type"); } // Sets the thresholds for different effective connection types in // |connection_thresholds|. void ObtainConnectionThresholds( const std::map& params, nqe::internal::NetworkQuality connection_thresholds[]) { // First set the default thresholds. nqe::internal::NetworkQuality default_effective_connection_type_thresholds [EffectiveConnectionType::EFFECTIVE_CONNECTION_TYPE_LAST]; DCHECK_LT(base::TimeDelta(), kHttpRttEffectiveConnectionTypeThresholds [EFFECTIVE_CONNECTION_TYPE_SLOW_2G]); default_effective_connection_type_thresholds [EFFECTIVE_CONNECTION_TYPE_SLOW_2G] = nqe::internal::NetworkQuality( // Set to the 66th percentile of 2G RTT observations on Android. kHttpRttEffectiveConnectionTypeThresholds [EFFECTIVE_CONNECTION_TYPE_SLOW_2G], nqe::internal::InvalidRTT(), nqe::internal::INVALID_RTT_THROUGHPUT); DCHECK_LT( base::TimeDelta(), kHttpRttEffectiveConnectionTypeThresholds[EFFECTIVE_CONNECTION_TYPE_2G]); default_effective_connection_type_thresholds[EFFECTIVE_CONNECTION_TYPE_2G] = nqe::internal::NetworkQuality( // Set to the 50th percentile of RTT observations on Android. kHttpRttEffectiveConnectionTypeThresholds [EFFECTIVE_CONNECTION_TYPE_2G], nqe::internal::InvalidRTT(), nqe::internal::INVALID_RTT_THROUGHPUT); DCHECK_LT( base::TimeDelta(), kHttpRttEffectiveConnectionTypeThresholds[EFFECTIVE_CONNECTION_TYPE_3G]); default_effective_connection_type_thresholds[EFFECTIVE_CONNECTION_TYPE_3G] = nqe::internal::NetworkQuality( // Set to the 50th percentile of 3G RTT observations on Android. kHttpRttEffectiveConnectionTypeThresholds [EFFECTIVE_CONNECTION_TYPE_3G], nqe::internal::InvalidRTT(), nqe::internal::INVALID_RTT_THROUGHPUT); // Connection threshold should not be set for 4G effective connection type // since it is the fastest. static_assert( EFFECTIVE_CONNECTION_TYPE_3G + 1 == EFFECTIVE_CONNECTION_TYPE_4G, "Missing effective connection type"); static_assert( EFFECTIVE_CONNECTION_TYPE_4G + 1 == EFFECTIVE_CONNECTION_TYPE_LAST, "Missing effective connection type"); for (size_t i = 0; i <= EFFECTIVE_CONNECTION_TYPE_3G; ++i) { EffectiveConnectionType effective_connection_type = static_cast(i); DCHECK_EQ(nqe::internal::InvalidRTT(), connection_thresholds[i].http_rtt()); DCHECK_EQ(nqe::internal::InvalidRTT(), connection_thresholds[i].transport_rtt()); DCHECK_EQ(nqe::internal::INVALID_RTT_THROUGHPUT, connection_thresholds[i].downstream_throughput_kbps()); if (effective_connection_type == EFFECTIVE_CONNECTION_TYPE_UNKNOWN) continue; std::string connection_type_name = std::string( DeprecatedGetNameForEffectiveConnectionType(effective_connection_type)); connection_thresholds[i].set_http_rtt( base::TimeDelta::FromMilliseconds(GetValueForVariationParam( params, connection_type_name + ".ThresholdMedianHttpRTTMsec", default_effective_connection_type_thresholds[i] .http_rtt() .InMilliseconds()))); connection_thresholds[i].set_transport_rtt( default_effective_connection_type_thresholds[i].transport_rtt()); connection_thresholds[i].set_downstream_throughput_kbps( GetValueForVariationParam( params, connection_type_name + ".ThresholdMedianKbps", default_effective_connection_type_thresholds[i] .downstream_throughput_kbps())); DCHECK(i == 0 || connection_thresholds[i].IsFaster(connection_thresholds[i - 1])); } } std::string GetForcedEffectiveConnectionTypeString( const std::map& params) { return GetStringValueForVariationParamWithDefaultValue( params, kForceEffectiveConnectionType, ""); } bool GetForcedEffectiveConnectionTypeOnCellularOnly( const std::map& params) { return GetForcedEffectiveConnectionTypeString(params) == kEffectiveConnectionTypeSlow2GOnCellular; } base::Optional GetInitForcedEffectiveConnectionType( const std::map& params) { if (GetForcedEffectiveConnectionTypeOnCellularOnly(params)) { return base::nullopt; } std::string forced_value = GetForcedEffectiveConnectionTypeString(params); base::Optional ect = GetEffectiveConnectionTypeForName(forced_value); DCHECK(forced_value.empty() || ect); return ect; } } // namespace NetworkQualityEstimatorParams::NetworkQualityEstimatorParams( const std::map& params) : params_(params), throughput_min_requests_in_flight_( GetValueForVariationParam(params_, "throughput_min_requests_in_flight", 5)), throughput_min_transfer_size_kilobytes_( GetValueForVariationParam(params_, "throughput_min_transfer_size_kilobytes", 32)), throughput_hanging_requests_cwnd_size_multiplier_( GetDoubleValueForVariationParamWithDefaultValue( params_, "throughput_hanging_requests_cwnd_size_multiplier", 1)), weight_multiplier_per_second_(GetWeightMultiplierPerSecond(params_)), weight_multiplier_per_signal_strength_level_( GetDoubleValueForVariationParamWithDefaultValue( params_, "rssi_weight_per_signal_strength_level", // Set the default value to something less than 1.0. This enables // caching of the network quality indexed by signal strength. // A value of 1.0 indicates that the signal strength is not used // when assigning weight to the observations. A value less than // 1.0, but close to 1.0 allows caching of the network quality // indexed by signal strength, while not significantly changing // the weight of the observations based on signal strength. // TODO(tbansal): https://crbug.com/513681: Update this value. 0.999)), forced_effective_connection_type_( GetInitForcedEffectiveConnectionType(params_)), forced_effective_connection_type_on_cellular_only_( GetForcedEffectiveConnectionTypeOnCellularOnly(params_)), persistent_cache_reading_enabled_( GetPersistentCacheReadingEnabled(params_)), min_socket_watcher_notification_interval_( GetMinSocketWatcherNotificationInterval(params_)), lower_bound_http_rtt_transport_rtt_multiplier_(1.0), hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_( GetValueForVariationParam( params_, "hanging_request_http_rtt_upper_bound_transport_rtt_multiplier", 8)), hanging_request_http_rtt_upper_bound_http_rtt_multiplier_( GetValueForVariationParam( params_, "hanging_request_http_rtt_upper_bound_http_rtt_multiplier", 6)), hanging_request_upper_bound_min_http_rtt_( base::TimeDelta::FromMilliseconds(GetValueForVariationParam( params_, "hanging_request_upper_bound_min_http_rtt_msec", 500))), http_rtt_transport_rtt_min_count_( GetValueForVariationParam(params_, "http_rtt_transport_rtt_min_count", 5)), increase_in_transport_rtt_logging_interval_( base::TimeDelta::FromMillisecondsD( GetDoubleValueForVariationParamWithDefaultValue( params_, "increase_in_transport_rtt_logging_interval", 10000))), recent_time_threshold_(base::TimeDelta::FromMillisecondsD( GetDoubleValueForVariationParamWithDefaultValue( params_, "recent_time_threshold", 5000))), historical_time_threshold_(base::TimeDelta::FromMillisecondsD( GetDoubleValueForVariationParamWithDefaultValue( params_, "historical_time_threshold", 60000))), hanging_request_duration_http_rtt_multiplier_(GetValueForVariationParam( params_, "hanging_request_duration_http_rtt_multiplier", 5)), hanging_request_min_duration_(base::TimeDelta::FromMilliseconds( GetValueForVariationParam(params_, "hanging_request_min_duration_msec", 3000))), add_default_platform_observations_( GetStringValueForVariationParamWithDefaultValue( params_, "add_default_platform_observations", "true") == "true"), socket_watchers_min_notification_interval_( base::TimeDelta::FromMilliseconds(GetValueForVariationParam( params_, "socket_watchers_min_notification_interval_msec", 200))), use_end_to_end_rtt_( GetStringValueForVariationParamWithDefaultValue(params_, "use_end_to_end_rtt", "false") == "true"), use_small_responses_(false) { DCHECK(hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_ == -1 || hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_ > 0); DCHECK(hanging_request_http_rtt_upper_bound_http_rtt_multiplier_ == -1 || hanging_request_http_rtt_upper_bound_http_rtt_multiplier_ > 0); DCHECK(hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_ == -1 || hanging_request_http_rtt_upper_bound_http_rtt_multiplier_ == -1 || hanging_request_http_rtt_upper_bound_transport_rtt_multiplier_ >= hanging_request_http_rtt_upper_bound_http_rtt_multiplier_); DCHECK_GE(1.0, weight_multiplier_per_signal_strength_level_); DCHECK_LE(0.0, weight_multiplier_per_signal_strength_level_); ObtainDefaultObservations(params_, default_observations_); ObtainTypicalNetworkQualities(params_, typical_network_quality_); ObtainConnectionThresholds(params_, connection_thresholds_); } NetworkQualityEstimatorParams::~NetworkQualityEstimatorParams() = default; void NetworkQualityEstimatorParams::SetUseSmallResponsesForTesting( bool use_small_responses) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); use_small_responses_ = use_small_responses; } bool NetworkQualityEstimatorParams::use_small_responses() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return use_small_responses_; }; void NetworkQualityEstimatorParams::SetForcedEffectiveConnectionTypeForTesting( EffectiveConnectionType type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); DCHECK(!forced_effective_connection_type_on_cellular_only_); forced_effective_connection_type_ = type; } base::Optional NetworkQualityEstimatorParams::GetForcedEffectiveConnectionType( NetworkChangeNotifier::ConnectionType connection_type) { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); if (forced_effective_connection_type_) { return forced_effective_connection_type_; } if (forced_effective_connection_type_on_cellular_only_ && net::NetworkChangeNotifier::IsConnectionCellular(connection_type)) { return EFFECTIVE_CONNECTION_TYPE_SLOW_2G; } return base::nullopt; } size_t NetworkQualityEstimatorParams::throughput_min_requests_in_flight() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); // If |use_small_responses_| is set to true for testing, then consider one // request as sufficient for taking throughput sample. return use_small_responses_ ? 1 : throughput_min_requests_in_flight_; } int64_t NetworkQualityEstimatorParams::GetThroughputMinTransferSizeBits() const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return static_cast(throughput_min_transfer_size_kilobytes_) * 8 * 1000; } const nqe::internal::NetworkQuality& NetworkQualityEstimatorParams::DefaultObservation( NetworkChangeNotifier::ConnectionType type) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return default_observations_[type]; } const nqe::internal::NetworkQuality& NetworkQualityEstimatorParams::TypicalNetworkQuality( EffectiveConnectionType type) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return typical_network_quality_[type]; } const nqe::internal::NetworkQuality& NetworkQualityEstimatorParams::ConnectionThreshold( EffectiveConnectionType type) const { DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); return connection_thresholds_[type]; } } // namespace net