| // Copyright 2018 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 "services/network/resource_scheduler/resource_scheduler_params_manager.h" |
| |
| #include "base/feature_list.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/metrics/field_trial_params.h" |
| #include "base/optional.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "net/nqe/network_quality_estimator.h" |
| #include "net/nqe/network_quality_estimator_params.h" |
| #include "net/traffic_annotation/network_traffic_annotation.h" |
| #include "services/network/public/cpp/features.h" |
| |
| namespace network { |
| |
| namespace { |
| |
| base::Optional<base::TimeDelta> GetMaxWaitTimeP2PConnections() { |
| if (!base::FeatureList::IsEnabled( |
| features::kPauseBrowserInitiatedHeavyTrafficForP2P)) { |
| return base::nullopt; |
| } |
| |
| int max_wait_time_p2p_connections_in_minutes = |
| base::GetFieldTrialParamByFeatureAsInt( |
| features::kPauseBrowserInitiatedHeavyTrafficForP2P, |
| "max_wait_time_p2p_connections_in_minutes", 60); |
| |
| return base::TimeDelta::FromMinutes(max_wait_time_p2p_connections_in_minutes); |
| } |
| |
| std::set<int32_t> GetThrottledHashes() { |
| std::set<int32_t> throttled_hashes; |
| |
| if (!base::FeatureList::IsEnabled( |
| features::kPauseBrowserInitiatedHeavyTrafficForP2P)) { |
| return throttled_hashes; |
| } |
| |
| std::string throttled_traffic_annotation_tags = |
| base::GetFieldTrialParamValueByFeature( |
| features::kPauseBrowserInitiatedHeavyTrafficForP2P, |
| "throttled_traffic_annotation_tags"); |
| |
| // Use default values for blocked hashes if there is none specified using |
| // field trial: The list below includes annotation tags that generate a lot of |
| // either downlink or uplink traffic and are expected to cause traffic |
| // contention with the P2P traffic on slow connections. |
| if (throttled_traffic_annotation_tags.empty()) { |
| // 6019475: safe_browsing_module_loader |
| // 82509217: safe_browsing_v4_update |
| // 727528: metrics_report_uma |
| throttled_traffic_annotation_tags = "6019475,82509217,727528"; |
| } |
| |
| const std::vector<std::string>& tokens = |
| base::SplitString(throttled_traffic_annotation_tags, ",", |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| |
| for (const std::string& token : tokens) { |
| int int_token; |
| bool successful = base::StringToInt(token, &int_token); |
| if (!successful) |
| continue; |
| throttled_hashes.insert(int_token); |
| } |
| return throttled_hashes; |
| } |
| |
| // The maximum number of delayable requests to allow to be in-flight at any |
| // point in time (across all hosts). |
| constexpr size_t kDefaultMaxNumDelayableRequestsPerClient = 10; |
| |
| // Value by which HTTP RTT estimate is multiplied to get the maximum queuing |
| // duration. |
| constexpr int kHttpRttMultiplierForQueuingDuration = 30; |
| |
| // Reads experiment parameters and returns them. |
| ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer |
| GetParamsForNetworkQualityContainer() { |
| // Look for configuration parameters with sequential numeric suffixes, and |
| // stop looking after the first failure to find an experimetal parameter. |
| // A sample configuration is given below: |
| // "EffectiveConnectionType1": "Slow-2G", |
| // "MaxDelayableRequests1": "6", |
| // "NonDelayableWeight1": "2.0", |
| // "EffectiveConnectionType2": "3G", |
| // "MaxDelayableRequests2": "12", |
| // "NonDelayableWeight2": "3.0", |
| // This config implies that when Effective Connection Type (ECT) is Slow-2G, |
| // then the maximum number of non-delayable requests should be |
| // limited to 6, and the non-delayable request weight should be set to 2. |
| // When ECT is 3G, it should be limited to 12. For all other values of ECT, |
| // the default values are used. |
| static const char kMaxDelayableRequestsBase[] = "MaxDelayableRequests"; |
| static const char kEffectiveConnectionTypeBase[] = "EffectiveConnectionType"; |
| static const char kNonDelayableWeightBase[] = "NonDelayableWeight"; |
| static constexpr base::TimeDelta kUpperBoundQueuingDuration = |
| base::TimeDelta::FromSeconds(120); |
| static constexpr base::TimeDelta kLowerBoundQueuingDuration = |
| base::TimeDelta::FromSeconds(15); |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer result; |
| // Set the default params for networks with ECT Slow2G and 2G. These params |
| // can still be overridden using the field trial. |
| result.emplace(std::make_pair( |
| net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G, |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| 8, 3.0, false /* delay_requests_on_multiplexed_connections */, |
| base::nullopt))); |
| result.emplace(std::make_pair( |
| net::EFFECTIVE_CONNECTION_TYPE_2G, |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| 8, 3.0, false /* delay_requests_on_multiplexed_connections */, |
| base::nullopt))); |
| result.emplace(std::make_pair( |
| net::EFFECTIVE_CONNECTION_TYPE_3G, |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| 8, 3.0, false /* delay_requests_on_multiplexed_connections */, |
| base::nullopt))); |
| |
| for (int config_param_index = 1; config_param_index <= 20; |
| ++config_param_index) { |
| size_t max_delayable_requests; |
| |
| if (!base::StringToSizeT(base::GetFieldTrialParamValueByFeature( |
| features::kThrottleDelayable, |
| kMaxDelayableRequestsBase + |
| base::NumberToString(config_param_index)), |
| &max_delayable_requests)) { |
| break; |
| } |
| |
| base::Optional<net::EffectiveConnectionType> effective_connection_type = |
| net::GetEffectiveConnectionTypeForName( |
| base::GetFieldTrialParamValueByFeature( |
| features::kThrottleDelayable, |
| kEffectiveConnectionTypeBase + |
| base::NumberToString(config_param_index))); |
| DCHECK(effective_connection_type.has_value()); |
| |
| double non_delayable_weight = base::GetFieldTrialParamByFeatureAsDouble( |
| features::kThrottleDelayable, |
| kNonDelayableWeightBase + base::NumberToString(config_param_index), |
| 0.0); |
| |
| // Check if the entry is already present. This will happen if the default |
| // params are being overridden by the field trial. |
| ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer::iterator |
| iter = result.find(effective_connection_type.value()); |
| if (iter != result.end()) { |
| iter->second.max_delayable_requests = max_delayable_requests; |
| iter->second.non_delayable_weight = non_delayable_weight; |
| } else { |
| result.emplace(std::make_pair( |
| effective_connection_type.value(), |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| max_delayable_requests, non_delayable_weight, false, |
| base::nullopt))); |
| } |
| } |
| |
| // Next, read the experiments params for |
| // DelayRequestsOnMultiplexedConnections finch experiment, and modify |result| |
| // based on the experiment params. |
| if (base::FeatureList::IsEnabled( |
| features::kDelayRequestsOnMultiplexedConnections)) { |
| base::Optional<net::EffectiveConnectionType> max_effective_connection_type = |
| net::GetEffectiveConnectionTypeForName( |
| base::GetFieldTrialParamValueByFeature( |
| features::kDelayRequestsOnMultiplexedConnections, |
| "MaxEffectiveConnectionType")); |
| |
| if (!max_effective_connection_type) { |
| // Use a default value if one is not set using field trial params. |
| max_effective_connection_type = net::EFFECTIVE_CONNECTION_TYPE_3G; |
| } |
| |
| for (int ect = net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; |
| ect <= max_effective_connection_type.value(); ++ect) { |
| net::EffectiveConnectionType effective_connection_type = |
| static_cast<net::EffectiveConnectionType>(ect); |
| ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer::iterator |
| iter = result.find(effective_connection_type); |
| if (iter != result.end()) { |
| iter->second.delay_requests_on_multiplexed_connections = true; |
| } else { |
| result.emplace(std::make_pair( |
| effective_connection_type, |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| kDefaultMaxNumDelayableRequestsPerClient, 0.0, true, |
| base::nullopt))); |
| } |
| } |
| } |
| |
| for (int ect = net::EFFECTIVE_CONNECTION_TYPE_UNKNOWN; |
| ect <= net::EFFECTIVE_CONNECTION_TYPE_4G; ++ect) { |
| net::EffectiveConnectionType effective_connection_type = |
| static_cast<net::EffectiveConnectionType>(ect); |
| base::TimeDelta http_rtt = |
| net::NetworkQualityEstimatorParams::GetDefaultTypicalHttpRtt( |
| effective_connection_type); |
| base::TimeDelta max_queuing_time = |
| http_rtt * kHttpRttMultiplierForQueuingDuration; |
| |
| // If GetDefaultTypicalHttpRtt returns a null value, set |
| // |max_queuing_time| to kUpperBoundQueuingDuration, This may happen |
| // when |ect| is UNKNOWN or OFFLINE. Both these cases are very rare, but |
| // it's important to handle them to ensure that |max_queuing_time| |
| // is set to some non-zero value in all cases. This ensures that the |
| // requests that are queued for too long are always unthrottled. |
| if (http_rtt.is_zero()) |
| max_queuing_time = kUpperBoundQueuingDuration; |
| if (max_queuing_time < kLowerBoundQueuingDuration) |
| max_queuing_time = kLowerBoundQueuingDuration; |
| if (max_queuing_time > kUpperBoundQueuingDuration) |
| max_queuing_time = kUpperBoundQueuingDuration; |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer::iterator |
| iter = result.find(effective_connection_type); |
| if (iter != result.end()) { |
| iter->second.max_queuing_time = max_queuing_time; |
| } else { |
| result.emplace(std::make_pair( |
| effective_connection_type, |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| kDefaultMaxNumDelayableRequestsPerClient, 0.0, false, |
| max_queuing_time))); |
| } |
| } |
| |
| if (base::FeatureList::IsEnabled( |
| features::kProactivelyThrottleLowPriorityRequests)) { |
| for (net::EffectiveConnectionType effective_connection_type = |
| net::EFFECTIVE_CONNECTION_TYPE_SLOW_2G; |
| effective_connection_type <= net::EFFECTIVE_CONNECTION_TYPE_4G; |
| effective_connection_type = static_cast<net::EffectiveConnectionType>( |
| effective_connection_type + 1)) { |
| std::string param_name = "http_rtt_multiplier_for_proactive_throttling_" + |
| std::string(GetNameForEffectiveConnectionType( |
| effective_connection_type)); |
| |
| double http_rtt_multiplier = base::GetFieldTrialParamByFeatureAsDouble( |
| features::kProactivelyThrottleLowPriorityRequests, param_name, -1); |
| |
| if (http_rtt_multiplier < 0) |
| continue; |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQualityContainer::iterator |
| iter = result.find(effective_connection_type); |
| |
| if (iter == result.end()) { |
| // Add a default ParamsForNetworkQuality object to |result|. |
| result.emplace(std::make_pair( |
| effective_connection_type, |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality())); |
| } |
| iter = result.find(effective_connection_type); |
| iter->second.http_rtt_multiplier_for_proactive_throttling = |
| http_rtt_multiplier; |
| } |
| } |
| |
| return result; |
| } |
| |
| } // namespace |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality:: |
| ParamsForNetworkQuality() |
| : ResourceSchedulerParamsManager::ParamsForNetworkQuality( |
| kDefaultMaxNumDelayableRequestsPerClient, |
| 0.0, |
| false, |
| base::nullopt) {} |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality:: |
| ParamsForNetworkQuality(size_t max_delayable_requests, |
| double non_delayable_weight, |
| bool delay_requests_on_multiplexed_connections, |
| base::Optional<base::TimeDelta> max_queuing_time) |
| : max_delayable_requests(max_delayable_requests), |
| non_delayable_weight(non_delayable_weight), |
| delay_requests_on_multiplexed_connections( |
| delay_requests_on_multiplexed_connections), |
| max_queuing_time(max_queuing_time) {} |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality:: |
| ParamsForNetworkQuality( |
| const ResourceSchedulerParamsManager::ParamsForNetworkQuality& other) = |
| default; |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality& |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality::operator=( |
| const ParamsForNetworkQuality& other) = default; |
| |
| ResourceSchedulerParamsManager::ResourceSchedulerParamsManager() |
| : ResourceSchedulerParamsManager(GetParamsForNetworkQualityContainer()) {} |
| |
| ResourceSchedulerParamsManager::ResourceSchedulerParamsManager( |
| const ParamsForNetworkQualityContainer& |
| params_for_network_quality_container) |
| : params_for_network_quality_container_( |
| params_for_network_quality_container), |
| max_wait_time_p2p_connections_(GetMaxWaitTimeP2PConnections()), |
| throttled_traffic_annotation_hashes_(GetThrottledHashes()) {} |
| |
| ResourceSchedulerParamsManager::ResourceSchedulerParamsManager( |
| const ResourceSchedulerParamsManager& other) |
| : params_for_network_quality_container_( |
| other.params_for_network_quality_container_), |
| max_wait_time_p2p_connections_(other.max_wait_time_p2p_connections_), |
| throttled_traffic_annotation_hashes_( |
| other.throttled_traffic_annotation_hashes_) {} |
| |
| ResourceSchedulerParamsManager::~ResourceSchedulerParamsManager() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| } |
| |
| ResourceSchedulerParamsManager::ParamsForNetworkQuality |
| ResourceSchedulerParamsManager::GetParamsForEffectiveConnectionType( |
| net::EffectiveConnectionType effective_connection_type) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| ParamsForNetworkQualityContainer::const_iterator iter = |
| params_for_network_quality_container_.find(effective_connection_type); |
| if (iter != params_for_network_quality_container_.end()) |
| return iter->second; |
| return ParamsForNetworkQuality(kDefaultMaxNumDelayableRequestsPerClient, 0.0, |
| false, base::nullopt); |
| } |
| |
| bool ResourceSchedulerParamsManager::CanThrottleNetworkTrafficAnnotationHash( |
| const int32_t unique_id_hash_code) const { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| // TODO(tbansal): Replace this check by an algorithm that builds history |
| // locally, records exponential weighted moving average of request size per |
| // tag. Next, using this database, the algorithm throttles requests whose tag |
| // cause the most traffic. |
| return throttled_traffic_annotation_hashes_.find(unique_id_hash_code) != |
| throttled_traffic_annotation_hashes_.end(); |
| } |
| |
| } // namespace network |