1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "net/base/network_quality_estimator.h"
14 #include "base/logging.h"
15 #include "base/metrics/histogram.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "net/base/load_timing_info.h"
18 #include "net/base/net_util.h"
19 #include "net/url_request/url_request.h"
24 // Maximum number of observations that can be held in the ObservationBuffer.
25 const size_t kMaximumObservationsBufferSize
= 500;
27 // Default value of the half life (in seconds) for computing time weighted
28 // percentiles. Every half life, the weight of all observations reduces by
29 // half. Lowering the half life would reduce the weight of older values faster.
30 const int kDefaultHalfLifeSeconds
= 60;
32 // Name of the variation parameter that holds the value of the half life (in
33 // seconds) of the observations.
34 const char kHalfLifeSecondsParamName
[] = "HalfLifeSeconds";
36 // Name of the different connection types. Used for matching the connection
37 // type to the variation parameters. Names must be in the same order as
38 // NetworkChangeNotifier::ConnectionType enum.
39 const char* const kConnectionTypeNames
[] =
40 {"Unknown", "Ethernet", "WiFi", "2G", "3G", "4G", "None", "Bluetooth"};
42 // Suffix of the name of the variation parameter that contains the default RTT
43 // observation (in milliseconds). Complete name of the variation parameter
44 // would be |ConnectionType|.|kDefaultRTTMsecObservationSuffix| where
45 // |ConnectionType| is from |kConnectionTypeNames|. For example, variation
46 // parameter for Wi-Fi would be "WiFi.DefaultMedianRTTMsec".
47 const char kDefaultRTTMsecObservationSuffix
[] = ".DefaultMedianRTTMsec";
49 // Suffix of the name of the variation parameter that contains the default
50 // downstream throughput observation (in Kbps). Complete name of the variation
51 // parameter would be |ConnectionType|.|kDefaultKbpsObservationSuffix| where
52 // |ConnectionType| is from |kConnectionTypeNames|. For example, variation
53 // parameter for Wi-Fi would be "WiFi.DefaultMedianKbps".
54 const char kDefaultKbpsObservationSuffix
[] = ".DefaultMedianKbps";
56 // Computes and returns the weight multiplier per second.
57 // |variation_params| is the map containing all field trial parameters
58 // related to NetworkQualityEstimator field trial.
59 double GetWeightMultiplierPerSecond(
60 const std::map
<std::string
, std::string
>& variation_params
) {
61 int half_life_seconds
= kDefaultHalfLifeSeconds
;
62 int32_t variations_value
= 0;
63 auto it
= variation_params
.find(kHalfLifeSecondsParamName
);
64 if (it
!= variation_params
.end() &&
65 base::StringToInt(it
->second
, &variations_value
) &&
66 variations_value
>= 1) {
67 half_life_seconds
= variations_value
;
69 DCHECK_GT(half_life_seconds
, 0);
70 return exp(log(0.5) / half_life_seconds
);
77 NetworkQualityEstimator::NetworkQualityEstimator(
78 const std::map
<std::string
, std::string
>& variation_params
)
79 : NetworkQualityEstimator(variation_params
, false, false) {
82 NetworkQualityEstimator::NetworkQualityEstimator(
83 const std::map
<std::string
, std::string
>& variation_params
,
84 bool allow_local_host_requests_for_tests
,
85 bool allow_smaller_responses_for_tests
)
86 : allow_localhost_requests_(allow_local_host_requests_for_tests
),
87 allow_small_responses_(allow_smaller_responses_for_tests
),
88 last_connection_change_(base::TimeTicks::Now()),
89 current_connection_type_(NetworkChangeNotifier::GetConnectionType()),
90 fastest_rtt_since_last_connection_change_(NetworkQuality::InvalidRTT()),
91 peak_kbps_since_last_connection_change_(
92 NetworkQuality::kInvalidThroughput
),
93 kbps_observations_(GetWeightMultiplierPerSecond(variation_params
)),
94 rtt_msec_observations_(GetWeightMultiplierPerSecond(variation_params
)) {
95 static_assert(kMinRequestDurationMicroseconds
> 0,
96 "Minimum request duration must be > 0");
97 static_assert(kDefaultHalfLifeSeconds
> 0,
98 "Default half life duration must be > 0");
99 static_assert(arraysize(kConnectionTypeNames
) ==
100 NetworkChangeNotifier::CONNECTION_LAST
+ 1,
101 "ConnectionType name count should match");
103 ObtainOperatingParams(variation_params
);
104 AddDefaultEstimates();
105 NetworkChangeNotifier::AddConnectionTypeObserver(this);
108 void NetworkQualityEstimator::ObtainOperatingParams(
109 const std::map
<std::string
, std::string
>& variation_params
) {
110 DCHECK(thread_checker_
.CalledOnValidThread());
112 for (size_t i
= 0; i
< arraysize(kConnectionTypeNames
); ++i
) {
113 int32_t variations_value
= kMinimumRTTVariationParameterMsec
- 1;
114 // Name of the parameter that holds the RTT value for this connection type.
115 std::string rtt_parameter_name
=
116 std::string(kConnectionTypeNames
[i
])
117 .append(kDefaultRTTMsecObservationSuffix
);
118 auto it
= variation_params
.find(rtt_parameter_name
);
119 if (it
!= variation_params
.end() &&
120 base::StringToInt(it
->second
, &variations_value
) &&
121 variations_value
>= kMinimumRTTVariationParameterMsec
) {
122 default_observations_
[i
] =
123 NetworkQuality(base::TimeDelta::FromMilliseconds(variations_value
),
124 default_observations_
[i
].downstream_throughput_kbps());
127 variations_value
= kMinimumThroughputVariationParameterKbps
- 1;
128 // Name of the parameter that holds the Kbps value for this connection
130 std::string kbps_parameter_name
=
131 std::string(kConnectionTypeNames
[i
])
132 .append(kDefaultKbpsObservationSuffix
);
133 it
= variation_params
.find(kbps_parameter_name
);
134 if (it
!= variation_params
.end() &&
135 base::StringToInt(it
->second
, &variations_value
) &&
136 variations_value
>= kMinimumThroughputVariationParameterKbps
) {
137 default_observations_
[i
] =
138 NetworkQuality(default_observations_
[i
].rtt(), variations_value
);
143 void NetworkQualityEstimator::AddDefaultEstimates() {
144 DCHECK(thread_checker_
.CalledOnValidThread());
146 if (default_observations_
[current_connection_type_
].rtt() !=
147 NetworkQuality::InvalidRTT()) {
148 rtt_msec_observations_
.AddObservation(Observation(
149 default_observations_
[current_connection_type_
].rtt().InMilliseconds(),
150 base::TimeTicks::Now()));
153 if (default_observations_
[current_connection_type_
]
154 .downstream_throughput_kbps() != NetworkQuality::kInvalidThroughput
) {
155 kbps_observations_
.AddObservation(
156 Observation(default_observations_
[current_connection_type_
]
157 .downstream_throughput_kbps(),
158 base::TimeTicks::Now()));
162 NetworkQualityEstimator::~NetworkQualityEstimator() {
163 DCHECK(thread_checker_
.CalledOnValidThread());
164 NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
167 void NetworkQualityEstimator::NotifyDataReceived(
168 const URLRequest
& request
,
169 int64_t cumulative_prefilter_bytes_read
,
170 int64_t prefiltered_bytes_read
) {
171 DCHECK(thread_checker_
.CalledOnValidThread());
172 DCHECK_GT(cumulative_prefilter_bytes_read
, 0);
173 DCHECK_GT(prefiltered_bytes_read
, 0);
175 if (!request
.url().is_valid() ||
176 (!allow_localhost_requests_
&& IsLocalhost(request
.url().host())) ||
177 !request
.url().SchemeIsHTTPOrHTTPS() ||
178 // Verify that response headers are received, so it can be ensured that
179 // response is not cached.
180 request
.response_info().response_time
.is_null() || request
.was_cached() ||
181 request
.creation_time() < last_connection_change_
) {
185 base::TimeTicks now
= base::TimeTicks::Now();
186 LoadTimingInfo load_timing_info
;
187 request
.GetLoadTimingInfo(&load_timing_info
);
189 // If the load timing info is unavailable, it probably means that the request
190 // did not go over the network.
191 if (load_timing_info
.send_start
.is_null() ||
192 load_timing_info
.receive_headers_end
.is_null()) {
196 // Time when the resource was requested.
197 base::TimeTicks request_start_time
= load_timing_info
.send_start
;
199 // Time when the headers were received.
200 base::TimeTicks headers_received_time
= load_timing_info
.receive_headers_end
;
202 // Only add RTT observation if this is the first read for this response.
203 if (cumulative_prefilter_bytes_read
== prefiltered_bytes_read
) {
204 // Duration between when the resource was requested and when response
205 // headers were received.
206 base::TimeDelta observed_rtt
= headers_received_time
- request_start_time
;
207 DCHECK_GE(observed_rtt
, base::TimeDelta());
208 if (observed_rtt
< fastest_rtt_since_last_connection_change_
)
209 fastest_rtt_since_last_connection_change_
= observed_rtt
;
211 rtt_msec_observations_
.AddObservation(
212 Observation(observed_rtt
.InMilliseconds(), now
));
215 // Time since the resource was requested.
216 base::TimeDelta since_request_start
= now
- request_start_time
;
217 DCHECK_GE(since_request_start
, base::TimeDelta());
219 // Ignore tiny transfers which will not produce accurate rates.
220 // Ignore short duration transfers.
221 // Skip the checks if |allow_small_responses_| is true.
222 if (allow_small_responses_
||
223 (cumulative_prefilter_bytes_read
>= kMinTransferSizeInBytes
&&
224 since_request_start
>= base::TimeDelta::FromMicroseconds(
225 kMinRequestDurationMicroseconds
))) {
226 double kbps_f
= cumulative_prefilter_bytes_read
* 8.0 / 1000.0 /
227 since_request_start
.InSecondsF();
228 DCHECK_GE(kbps_f
, 0.0);
230 // Check overflow errors. This may happen if the kbps_f is more than
231 // 2 * 10^9 (= 2000 Gbps).
232 if (kbps_f
>= std::numeric_limits
<int32_t>::max())
233 kbps_f
= std::numeric_limits
<int32_t>::max() - 1;
235 int32_t kbps
= static_cast<int32_t>(kbps_f
);
237 // If the |kbps| is less than 1, we set it to 1 to differentiate from case
238 // when there is no connection.
239 if (kbps_f
> 0.0 && kbps
== 0)
243 if (kbps
> peak_kbps_since_last_connection_change_
)
244 peak_kbps_since_last_connection_change_
= kbps
;
246 kbps_observations_
.AddObservation(Observation(kbps
, now
));
251 void NetworkQualityEstimator::OnConnectionTypeChanged(
252 NetworkChangeNotifier::ConnectionType type
) {
253 DCHECK(thread_checker_
.CalledOnValidThread());
254 if (fastest_rtt_since_last_connection_change_
!=
255 NetworkQuality::InvalidRTT()) {
256 switch (current_connection_type_
) {
257 case NetworkChangeNotifier::CONNECTION_UNKNOWN
:
258 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Unknown",
259 fastest_rtt_since_last_connection_change_
);
261 case NetworkChangeNotifier::CONNECTION_ETHERNET
:
262 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Ethernet",
263 fastest_rtt_since_last_connection_change_
);
265 case NetworkChangeNotifier::CONNECTION_WIFI
:
266 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Wifi",
267 fastest_rtt_since_last_connection_change_
);
269 case NetworkChangeNotifier::CONNECTION_2G
:
270 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.2G",
271 fastest_rtt_since_last_connection_change_
);
273 case NetworkChangeNotifier::CONNECTION_3G
:
274 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.3G",
275 fastest_rtt_since_last_connection_change_
);
277 case NetworkChangeNotifier::CONNECTION_4G
:
278 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.4G",
279 fastest_rtt_since_last_connection_change_
);
281 case NetworkChangeNotifier::CONNECTION_NONE
:
282 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.None",
283 fastest_rtt_since_last_connection_change_
);
285 case NetworkChangeNotifier::CONNECTION_BLUETOOTH
:
286 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Bluetooth",
287 fastest_rtt_since_last_connection_change_
);
295 if (peak_kbps_since_last_connection_change_
!=
296 NetworkQuality::kInvalidThroughput
) {
297 switch (current_connection_type_
) {
298 case NetworkChangeNotifier::CONNECTION_UNKNOWN
:
299 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.Unknown",
300 peak_kbps_since_last_connection_change_
);
302 case NetworkChangeNotifier::CONNECTION_ETHERNET
:
303 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.Ethernet",
304 peak_kbps_since_last_connection_change_
);
306 case NetworkChangeNotifier::CONNECTION_WIFI
:
307 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.Wifi",
308 peak_kbps_since_last_connection_change_
);
310 case NetworkChangeNotifier::CONNECTION_2G
:
311 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.2G",
312 peak_kbps_since_last_connection_change_
);
314 case NetworkChangeNotifier::CONNECTION_3G
:
315 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.3G",
316 peak_kbps_since_last_connection_change_
);
318 case NetworkChangeNotifier::CONNECTION_4G
:
319 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.4G",
320 peak_kbps_since_last_connection_change_
);
322 case NetworkChangeNotifier::CONNECTION_NONE
:
323 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.None",
324 peak_kbps_since_last_connection_change_
);
326 case NetworkChangeNotifier::CONNECTION_BLUETOOTH
:
327 UMA_HISTOGRAM_COUNTS("NQE.PeakKbps.Bluetooth",
328 peak_kbps_since_last_connection_change_
);
336 last_connection_change_
= base::TimeTicks::Now();
337 peak_kbps_since_last_connection_change_
= NetworkQuality::kInvalidThroughput
;
338 fastest_rtt_since_last_connection_change_
= NetworkQuality::InvalidRTT();
339 kbps_observations_
.Clear();
340 rtt_msec_observations_
.Clear();
341 current_connection_type_
= type
;
343 AddDefaultEstimates();
346 NetworkQuality
NetworkQualityEstimator::GetPeakEstimate() const {
347 DCHECK(thread_checker_
.CalledOnValidThread());
349 return NetworkQuality(fastest_rtt_since_last_connection_change_
,
350 peak_kbps_since_last_connection_change_
);
353 bool NetworkQualityEstimator::GetEstimate(NetworkQuality
* median
) const {
354 if (kbps_observations_
.Size() == 0 || rtt_msec_observations_
.Size() == 0) {
355 *median
= NetworkQuality();
358 *median
= GetEstimate(50);
362 size_t NetworkQualityEstimator::GetMaximumObservationBufferSizeForTests()
364 return kMaximumObservationsBufferSize
;
367 bool NetworkQualityEstimator::VerifyBufferSizeForTests(
368 size_t expected_size
) const {
369 return kbps_observations_
.Size() == expected_size
&&
370 rtt_msec_observations_
.Size() == expected_size
;
373 NetworkQualityEstimator::Observation::Observation(int32_t value
,
374 base::TimeTicks timestamp
)
375 : value(value
), timestamp(timestamp
) {
377 DCHECK(!timestamp
.is_null());
380 NetworkQualityEstimator::Observation::~Observation() {
383 NetworkQualityEstimator::ObservationBuffer::ObservationBuffer(
384 double weight_multiplier_per_second
)
385 : weight_multiplier_per_second_(weight_multiplier_per_second
) {
386 static_assert(kMaximumObservationsBufferSize
> 0U,
387 "Minimum size of observation buffer must be > 0");
388 DCHECK_GE(weight_multiplier_per_second_
, 0.0);
389 DCHECK_LE(weight_multiplier_per_second_
, 1.0);
392 NetworkQualityEstimator::ObservationBuffer::~ObservationBuffer() {
395 void NetworkQualityEstimator::ObservationBuffer::AddObservation(
396 const Observation
& observation
) {
397 DCHECK_LE(observations_
.size(), kMaximumObservationsBufferSize
);
398 // Evict the oldest element if the buffer is already full.
399 if (observations_
.size() == kMaximumObservationsBufferSize
)
400 observations_
.pop_front();
402 observations_
.push_back(observation
);
403 DCHECK_LE(observations_
.size(), kMaximumObservationsBufferSize
);
406 size_t NetworkQualityEstimator::ObservationBuffer::Size() const {
407 return observations_
.size();
410 void NetworkQualityEstimator::ObservationBuffer::Clear() {
411 observations_
.clear();
412 DCHECK(observations_
.empty());
415 NetworkQuality
NetworkQualityEstimator::GetEstimate(int percentile
) const {
416 DCHECK(thread_checker_
.CalledOnValidThread());
417 DCHECK_GE(percentile
, 0);
418 DCHECK_LE(percentile
, 100);
419 DCHECK_GT(kbps_observations_
.Size(), 0U);
420 DCHECK_GT(rtt_msec_observations_
.Size(), 0U);
422 // RTT observations are sorted by duration from shortest to longest, thus
423 // a higher percentile RTT will have a longer RTT than a lower percentile.
424 // Throughput observations are sorted by kbps from slowest to fastest,
425 // thus a higher percentile throughput will be faster than a lower one.
426 return NetworkQuality(base::TimeDelta::FromMilliseconds(
427 rtt_msec_observations_
.GetPercentile(percentile
)),
428 kbps_observations_
.GetPercentile(100 - percentile
));
431 void NetworkQualityEstimator::ObservationBuffer::ComputeWeightedObservations(
432 std::vector
<WeightedObservation
>& weighted_observations
,
433 double* total_weight
) const {
434 weighted_observations
.clear();
435 double total_weight_observations
= 0.0;
436 base::TimeTicks now
= base::TimeTicks::Now();
438 for (const auto& observation
: observations_
) {
439 base::TimeDelta time_since_sample_taken
= now
- observation
.timestamp
;
441 pow(weight_multiplier_per_second_
, time_since_sample_taken
.InSeconds());
442 weight
= std::max(DBL_MIN
, std::min(1.0, weight
));
444 weighted_observations
.push_back(
445 WeightedObservation(observation
.value
, weight
));
446 total_weight_observations
+= weight
;
449 // Sort the samples by value in ascending order.
450 std::sort(weighted_observations
.begin(), weighted_observations
.end());
451 *total_weight
= total_weight_observations
;
454 int32_t NetworkQualityEstimator::ObservationBuffer::GetPercentile(
455 int percentile
) const {
456 DCHECK(!observations_
.empty());
458 // Stores WeightedObservation in increasing order of value.
459 std::vector
<WeightedObservation
> weighted_observations
;
461 // Total weight of all observations in |weighted_observations|.
462 double total_weight
= 0.0;
464 ComputeWeightedObservations(weighted_observations
, &total_weight
);
465 DCHECK(!weighted_observations
.empty());
466 DCHECK_GT(total_weight
, 0.0);
467 DCHECK_EQ(observations_
.size(), weighted_observations
.size());
469 double desired_weight
= percentile
/ 100.0 * total_weight
;
471 double cumulative_weight_seen_so_far
= 0.0;
472 for (const auto& weighted_observation
: weighted_observations
) {
473 cumulative_weight_seen_so_far
+= weighted_observation
.weight
;
475 // TODO(tbansal): Consider interpolating between observations.
476 if (cumulative_weight_seen_so_far
>= desired_weight
)
477 return weighted_observation
.value
;
480 // Computation may reach here due to floating point errors. This may happen
481 // if |percentile| was 100 (or close to 100), and |desired_weight| was
482 // slightly larger than |total_weight| (due to floating point errors).
483 // In this case, we return the highest |value| among all observations.
484 // This is same as value of the last observation in the sorted vector.
485 return weighted_observations
.at(weighted_observations
.size() - 1).value
;