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"
13 #include "base/logging.h"
14 #include "base/metrics/histogram.h"
15 #include "base/metrics/histogram_base.h"
16 #include "base/strings/string_number_conversions.h"
17 #include "build/build_config.h"
18 #include "net/base/load_flags.h"
19 #include "net/base/load_timing_info.h"
20 #include "net/base/net_util.h"
21 #include "net/base/network_interfaces.h"
22 #include "net/url_request/url_request.h"
25 #if defined(OS_ANDROID)
26 #include "net/android/network_library.h"
31 // Default value of the half life (in seconds) for computing time weighted
32 // percentiles. Every half life, the weight of all observations reduces by
33 // half. Lowering the half life would reduce the weight of older values faster.
34 const int kDefaultHalfLifeSeconds
= 60;
36 // Name of the variation parameter that holds the value of the half life (in
37 // seconds) of the observations.
38 const char kHalfLifeSecondsParamName
[] = "HalfLifeSeconds";
40 // Returns a descriptive name corresponding to |connection_type|.
41 const char* GetNameForConnectionType(
42 net::NetworkChangeNotifier::ConnectionType connection_type
) {
43 switch (connection_type
) {
44 case net::NetworkChangeNotifier::CONNECTION_UNKNOWN
:
46 case net::NetworkChangeNotifier::CONNECTION_ETHERNET
:
48 case net::NetworkChangeNotifier::CONNECTION_WIFI
:
50 case net::NetworkChangeNotifier::CONNECTION_2G
:
52 case net::NetworkChangeNotifier::CONNECTION_3G
:
54 case net::NetworkChangeNotifier::CONNECTION_4G
:
56 case net::NetworkChangeNotifier::CONNECTION_NONE
:
58 case net::NetworkChangeNotifier::CONNECTION_BLUETOOTH
:
67 // Suffix of the name of the variation parameter that contains the default RTT
68 // observation (in milliseconds). Complete name of the variation parameter
69 // would be |ConnectionType|.|kDefaultRTTMsecObservationSuffix| where
70 // |ConnectionType| is from |kConnectionTypeNames|. For example, variation
71 // parameter for Wi-Fi would be "WiFi.DefaultMedianRTTMsec".
72 const char kDefaultRTTMsecObservationSuffix
[] = ".DefaultMedianRTTMsec";
74 // Suffix of the name of the variation parameter that contains the default
75 // downstream throughput observation (in Kbps). Complete name of the variation
76 // parameter would be |ConnectionType|.|kDefaultKbpsObservationSuffix| where
77 // |ConnectionType| is from |kConnectionTypeNames|. For example, variation
78 // parameter for Wi-Fi would be "WiFi.DefaultMedianKbps".
79 const char kDefaultKbpsObservationSuffix
[] = ".DefaultMedianKbps";
81 // Computes and returns the weight multiplier per second.
82 // |variation_params| is the map containing all field trial parameters
83 // related to NetworkQualityEstimator field trial.
84 double GetWeightMultiplierPerSecond(
85 const std::map
<std::string
, std::string
>& variation_params
) {
86 int half_life_seconds
= kDefaultHalfLifeSeconds
;
87 int32_t variations_value
= 0;
88 auto it
= variation_params
.find(kHalfLifeSecondsParamName
);
89 if (it
!= variation_params
.end() &&
90 base::StringToInt(it
->second
, &variations_value
) &&
91 variations_value
>= 1) {
92 half_life_seconds
= variations_value
;
94 DCHECK_GT(half_life_seconds
, 0);
95 return exp(log(0.5) / half_life_seconds
);
98 // Returns the histogram that should be used to record the given statistic.
99 // |max_limit| is the maximum value that can be stored in the histogram.
100 base::HistogramBase
* GetHistogram(
101 const std::string
& statistic_name
,
102 net::NetworkChangeNotifier::ConnectionType type
,
104 const base::LinearHistogram::Sample kLowerLimit
= 1;
105 DCHECK_GT(max_limit
, kLowerLimit
);
106 const size_t kBucketCount
= 50;
108 // Prefix of network quality estimator histograms.
109 const char prefix
[] = "NQE.";
110 return base::Histogram::FactoryGet(
111 prefix
+ statistic_name
+ GetNameForConnectionType(type
), kLowerLimit
,
112 max_limit
, kBucketCount
, base::HistogramBase::kUmaTargetedHistogramFlag
);
119 const int32_t NetworkQualityEstimator::kInvalidThroughput
= 0;
121 NetworkQualityEstimator::NetworkQualityEstimator(
122 const std::map
<std::string
, std::string
>& variation_params
)
123 : NetworkQualityEstimator(variation_params
, false, false) {
126 NetworkQualityEstimator::NetworkQualityEstimator(
127 const std::map
<std::string
, std::string
>& variation_params
,
128 bool allow_local_host_requests_for_tests
,
129 bool allow_smaller_responses_for_tests
)
130 : allow_localhost_requests_(allow_local_host_requests_for_tests
),
131 allow_small_responses_(allow_smaller_responses_for_tests
),
132 last_connection_change_(base::TimeTicks::Now()),
134 NetworkID(NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN
,
136 downstream_throughput_kbps_observations_(
137 GetWeightMultiplierPerSecond(variation_params
)),
138 rtt_msec_observations_(GetWeightMultiplierPerSecond(variation_params
)) {
139 static_assert(kMinRequestDurationMicroseconds
> 0,
140 "Minimum request duration must be > 0");
141 static_assert(kDefaultHalfLifeSeconds
> 0,
142 "Default half life duration must be > 0");
143 static_assert(kMaximumNetworkQualityCacheSize
> 0,
144 "Size of the network quality cache must be > 0");
145 // This limit should not be increased unless the logic for removing the
146 // oldest cache entry is rewritten to use a doubly-linked-list LRU queue.
147 static_assert(kMaximumNetworkQualityCacheSize
<= 10,
148 "Size of the network quality cache must <= 10");
150 ObtainOperatingParams(variation_params
);
151 NetworkChangeNotifier::AddConnectionTypeObserver(this);
152 current_network_id_
= GetCurrentNetworkID();
153 AddDefaultEstimates();
157 const base::TimeDelta
NetworkQualityEstimator::InvalidRTT() {
158 return base::TimeDelta::Max();
161 void NetworkQualityEstimator::ObtainOperatingParams(
162 const std::map
<std::string
, std::string
>& variation_params
) {
163 DCHECK(thread_checker_
.CalledOnValidThread());
165 for (size_t i
= 0; i
<= NetworkChangeNotifier::CONNECTION_LAST
; ++i
) {
166 NetworkChangeNotifier::ConnectionType type
=
167 static_cast<NetworkChangeNotifier::ConnectionType
>(i
);
168 int32_t variations_value
= kMinimumRTTVariationParameterMsec
- 1;
169 // Name of the parameter that holds the RTT value for this connection type.
170 std::string rtt_parameter_name
=
171 std::string(GetNameForConnectionType(type
))
172 .append(kDefaultRTTMsecObservationSuffix
);
173 auto it
= variation_params
.find(rtt_parameter_name
);
174 if (it
!= variation_params
.end() &&
175 base::StringToInt(it
->second
, &variations_value
) &&
176 variations_value
>= kMinimumRTTVariationParameterMsec
) {
177 default_observations_
[i
] =
178 NetworkQuality(base::TimeDelta::FromMilliseconds(variations_value
),
179 default_observations_
[i
].downstream_throughput_kbps());
182 variations_value
= kMinimumThroughputVariationParameterKbps
- 1;
183 // Name of the parameter that holds the Kbps value for this connection
185 std::string kbps_parameter_name
=
186 std::string(GetNameForConnectionType(type
))
187 .append(kDefaultKbpsObservationSuffix
);
188 it
= variation_params
.find(kbps_parameter_name
);
189 if (it
!= variation_params
.end() &&
190 base::StringToInt(it
->second
, &variations_value
) &&
191 variations_value
>= kMinimumThroughputVariationParameterKbps
) {
192 default_observations_
[i
] =
193 NetworkQuality(default_observations_
[i
].rtt(), variations_value
);
198 void NetworkQualityEstimator::AddDefaultEstimates() {
199 DCHECK(thread_checker_
.CalledOnValidThread());
200 if (default_observations_
[current_network_id_
.type
].rtt() != InvalidRTT()) {
201 rtt_msec_observations_
.AddObservation(Observation(
202 default_observations_
[current_network_id_
.type
].rtt().InMilliseconds(),
203 base::TimeTicks::Now()));
205 if (default_observations_
[current_network_id_
.type
]
206 .downstream_throughput_kbps() != kInvalidThroughput
) {
207 downstream_throughput_kbps_observations_
.AddObservation(
208 Observation(default_observations_
[current_network_id_
.type
]
209 .downstream_throughput_kbps(),
210 base::TimeTicks::Now()));
214 NetworkQualityEstimator::~NetworkQualityEstimator() {
215 DCHECK(thread_checker_
.CalledOnValidThread());
216 NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
219 void NetworkQualityEstimator::NotifyHeadersReceived(const URLRequest
& request
) {
220 DCHECK(thread_checker_
.CalledOnValidThread());
222 if (!RequestProvidesUsefulObservations(request
))
225 // Update |estimated_median_network_quality_| if this is a main frame request.
226 if (request
.load_flags() & LOAD_MAIN_FRAME
) {
227 estimated_median_network_quality_
= NetworkQuality(
228 GetRTTEstimateInternal(base::TimeTicks(), 50),
229 GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 50));
232 base::TimeTicks now
= base::TimeTicks::Now();
233 LoadTimingInfo load_timing_info
;
234 request
.GetLoadTimingInfo(&load_timing_info
);
236 // If the load timing info is unavailable, it probably means that the request
237 // did not go over the network.
238 if (load_timing_info
.send_start
.is_null() ||
239 load_timing_info
.receive_headers_end
.is_null()) {
243 // Time when the resource was requested.
244 base::TimeTicks request_start_time
= load_timing_info
.send_start
;
246 // Time when the headers were received.
247 base::TimeTicks headers_received_time
= load_timing_info
.receive_headers_end
;
249 // Duration between when the resource was requested and when response
250 // headers were received.
251 base::TimeDelta observed_rtt
= headers_received_time
- request_start_time
;
252 DCHECK_GE(observed_rtt
, base::TimeDelta());
253 if (observed_rtt
< peak_network_quality_
.rtt()) {
254 peak_network_quality_
= NetworkQuality(
255 observed_rtt
, peak_network_quality_
.downstream_throughput_kbps());
258 rtt_msec_observations_
.AddObservation(
259 Observation(observed_rtt
.InMilliseconds(), now
));
261 // Compare the RTT observation with the estimated value and record it.
262 if (estimated_median_network_quality_
.rtt() != InvalidRTT()) {
263 RecordRTTUMA(estimated_median_network_quality_
.rtt().InMilliseconds(),
264 observed_rtt
.InMilliseconds());
268 void NetworkQualityEstimator::NotifyRequestCompleted(
269 const URLRequest
& request
) {
270 DCHECK(thread_checker_
.CalledOnValidThread());
272 if (!RequestProvidesUsefulObservations(request
))
275 base::TimeTicks now
= base::TimeTicks::Now();
276 LoadTimingInfo load_timing_info
;
277 request
.GetLoadTimingInfo(&load_timing_info
);
279 // If the load timing info is unavailable, it probably means that the request
280 // did not go over the network.
281 if (load_timing_info
.send_start
.is_null() ||
282 load_timing_info
.receive_headers_end
.is_null()) {
286 // Time since the resource was requested.
287 // TODO(tbansal): Change the start time to receive_headers_end, once we use
288 // NetworkActivityMonitor.
289 base::TimeDelta request_start_to_completed
=
290 now
- load_timing_info
.send_start
;
291 DCHECK_GE(request_start_to_completed
, base::TimeDelta());
293 // Ignore tiny transfers which will not produce accurate rates.
294 // Ignore short duration transfers.
295 // Skip the checks if |allow_small_responses_| is true.
296 if (!allow_small_responses_
&&
297 (request
.GetTotalReceivedBytes() < kMinTransferSizeInBytes
||
298 request_start_to_completed
< base::TimeDelta::FromMicroseconds(
299 kMinRequestDurationMicroseconds
))) {
303 double downstream_kbps
= request
.GetTotalReceivedBytes() * 8.0 / 1000.0 /
304 request_start_to_completed
.InSecondsF();
305 DCHECK_GE(downstream_kbps
, 0.0);
307 // Check overflow errors. This may happen if the downstream_kbps is more than
308 // 2 * 10^9 (= 2000 Gbps).
309 if (downstream_kbps
>= std::numeric_limits
<int32_t>::max())
310 downstream_kbps
= std::numeric_limits
<int32_t>::max();
312 int32_t downstream_kbps_as_integer
= static_cast<int32_t>(downstream_kbps
);
314 // Round up |downstream_kbps_as_integer|. If the |downstream_kbps_as_integer|
315 // is less than 1, it is set to 1 to differentiate from case when there is no
317 if (downstream_kbps
- downstream_kbps_as_integer
> 0)
318 downstream_kbps_as_integer
++;
320 DCHECK_GT(downstream_kbps_as_integer
, 0.0);
321 if (downstream_kbps_as_integer
>
322 peak_network_quality_
.downstream_throughput_kbps())
323 peak_network_quality_
=
324 NetworkQuality(peak_network_quality_
.rtt(), downstream_kbps_as_integer
);
326 downstream_throughput_kbps_observations_
.AddObservation(
327 Observation(downstream_kbps_as_integer
, now
));
330 void NetworkQualityEstimator::RecordRTTUMA(int32_t estimated_value_msec
,
331 int32_t actual_value_msec
) const {
332 DCHECK(thread_checker_
.CalledOnValidThread());
334 // Record the difference between the actual and the estimated value.
335 if (estimated_value_msec
>= actual_value_msec
) {
336 base::HistogramBase
* difference_rtt
=
337 GetHistogram("DifferenceRTTEstimatedAndActual.",
338 current_network_id_
.type
, 10 * 1000); // 10 seconds
339 difference_rtt
->Add(estimated_value_msec
- actual_value_msec
);
341 base::HistogramBase
* difference_rtt
=
342 GetHistogram("DifferenceRTTActualAndEstimated.",
343 current_network_id_
.type
, 10 * 1000); // 10 seconds
344 difference_rtt
->Add(actual_value_msec
- estimated_value_msec
);
347 // Record all the RTT observations.
348 base::HistogramBase
* rtt_observations
=
349 GetHistogram("RTTObservations.", current_network_id_
.type
,
350 10 * 1000); // 10 seconds upper bound
351 rtt_observations
->Add(actual_value_msec
);
353 if (actual_value_msec
== 0)
356 int32 ratio
= (estimated_value_msec
* 100) / actual_value_msec
;
358 // Record the accuracy of estimation by recording the ratio of estimated
359 // value to the actual value.
360 base::HistogramBase
* ratio_median_rtt
= GetHistogram(
361 "RatioEstimatedToActualRTT.", current_network_id_
.type
, 1000);
362 ratio_median_rtt
->Add(ratio
);
365 bool NetworkQualityEstimator::RequestProvidesUsefulObservations(
366 const URLRequest
& request
) const {
367 return request
.url().is_valid() &&
368 (allow_localhost_requests_
|| !IsLocalhost(request
.url().host())) &&
369 request
.url().SchemeIsHTTPOrHTTPS() &&
370 // Verify that response headers are received, so it can be ensured that
371 // response is not cached.
372 !request
.response_info().response_time
.is_null() &&
373 !request
.was_cached() &&
374 request
.creation_time() >= last_connection_change_
;
377 void NetworkQualityEstimator::OnConnectionTypeChanged(
378 NetworkChangeNotifier::ConnectionType type
) {
379 DCHECK(thread_checker_
.CalledOnValidThread());
380 if (peak_network_quality_
.rtt() != InvalidRTT()) {
381 switch (current_network_id_
.type
) {
382 case NetworkChangeNotifier::CONNECTION_UNKNOWN
:
383 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Unknown",
384 peak_network_quality_
.rtt());
386 case NetworkChangeNotifier::CONNECTION_ETHERNET
:
387 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Ethernet",
388 peak_network_quality_
.rtt());
390 case NetworkChangeNotifier::CONNECTION_WIFI
:
391 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Wifi", peak_network_quality_
.rtt());
393 case NetworkChangeNotifier::CONNECTION_2G
:
394 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.2G", peak_network_quality_
.rtt());
396 case NetworkChangeNotifier::CONNECTION_3G
:
397 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.3G", peak_network_quality_
.rtt());
399 case NetworkChangeNotifier::CONNECTION_4G
:
400 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.4G", peak_network_quality_
.rtt());
402 case NetworkChangeNotifier::CONNECTION_NONE
:
403 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.None", peak_network_quality_
.rtt());
405 case NetworkChangeNotifier::CONNECTION_BLUETOOTH
:
406 UMA_HISTOGRAM_TIMES("NQE.FastestRTT.Bluetooth",
407 peak_network_quality_
.rtt());
410 NOTREACHED() << "Unexpected connection type = "
411 << current_network_id_
.type
;
416 if (peak_network_quality_
.downstream_throughput_kbps() !=
417 kInvalidThroughput
) {
418 switch (current_network_id_
.type
) {
419 case NetworkChangeNotifier::CONNECTION_UNKNOWN
:
420 UMA_HISTOGRAM_COUNTS(
421 "NQE.PeakKbps.Unknown",
422 peak_network_quality_
.downstream_throughput_kbps());
424 case NetworkChangeNotifier::CONNECTION_ETHERNET
:
425 UMA_HISTOGRAM_COUNTS(
426 "NQE.PeakKbps.Ethernet",
427 peak_network_quality_
.downstream_throughput_kbps());
429 case NetworkChangeNotifier::CONNECTION_WIFI
:
430 UMA_HISTOGRAM_COUNTS(
432 peak_network_quality_
.downstream_throughput_kbps());
434 case NetworkChangeNotifier::CONNECTION_2G
:
435 UMA_HISTOGRAM_COUNTS(
437 peak_network_quality_
.downstream_throughput_kbps());
439 case NetworkChangeNotifier::CONNECTION_3G
:
440 UMA_HISTOGRAM_COUNTS(
442 peak_network_quality_
.downstream_throughput_kbps());
444 case NetworkChangeNotifier::CONNECTION_4G
:
445 UMA_HISTOGRAM_COUNTS(
447 peak_network_quality_
.downstream_throughput_kbps());
449 case NetworkChangeNotifier::CONNECTION_NONE
:
450 UMA_HISTOGRAM_COUNTS(
452 peak_network_quality_
.downstream_throughput_kbps());
454 case NetworkChangeNotifier::CONNECTION_BLUETOOTH
:
455 UMA_HISTOGRAM_COUNTS(
456 "NQE.PeakKbps.Bluetooth",
457 peak_network_quality_
.downstream_throughput_kbps());
460 NOTREACHED() << "Unexpected connection type = "
461 << current_network_id_
.type
;
466 base::TimeDelta rtt
= GetRTTEstimateInternal(base::TimeTicks(), 50);
467 if (rtt
!= InvalidRTT()) {
468 // Add the 50th percentile value.
469 base::HistogramBase
* rtt_percentile
=
470 GetHistogram("RTT.Percentile50.", current_network_id_
.type
,
471 10 * 1000); // 10 seconds
472 rtt_percentile
->Add(rtt
.InMilliseconds());
474 // Add the remaining percentile values.
475 static const int kPercentiles
[] = {0, 10, 90, 100};
476 for (size_t i
= 0; i
< arraysize(kPercentiles
); ++i
) {
477 rtt
= GetRTTEstimateInternal(base::TimeTicks(), kPercentiles
[i
]);
479 rtt_percentile
= GetHistogram(
480 "RTT.Percentile" + base::IntToString(kPercentiles
[i
]) + ".",
481 current_network_id_
.type
, 10 * 1000); // 10 seconds
482 rtt_percentile
->Add(rtt
.InMilliseconds());
486 // Write the estimates of the previous network to the cache.
487 CacheNetworkQualityEstimate();
489 // Clear the local state.
490 last_connection_change_
= base::TimeTicks::Now();
491 peak_network_quality_
= NetworkQuality();
492 downstream_throughput_kbps_observations_
.Clear();
493 rtt_msec_observations_
.Clear();
494 current_network_id_
= GetCurrentNetworkID();
496 // Read any cached estimates for the new network. If cached estimates are
497 // unavailable, add the default estimates.
498 if (!ReadCachedNetworkQualityEstimate())
499 AddDefaultEstimates();
500 estimated_median_network_quality_
= NetworkQuality();
503 bool NetworkQualityEstimator::GetRTTEstimate(base::TimeDelta
* rtt
) const {
504 if (rtt_msec_observations_
.Size() == 0) {
508 *rtt
= GetRTTEstimateInternal(base::TimeTicks(), 50);
509 return (*rtt
!= InvalidRTT());
512 bool NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimate(
513 int32_t* kbps
) const {
514 if (downstream_throughput_kbps_observations_
.Size() == 0) {
515 *kbps
= kInvalidThroughput
;
518 *kbps
= GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 50);
519 return (*kbps
!= kInvalidThroughput
);
522 bool NetworkQualityEstimator::GetMedianRTTSince(
523 const base::TimeTicks
& begin_timestamp
,
524 base::TimeDelta
* rtt
) const {
525 DCHECK(thread_checker_
.CalledOnValidThread());
526 *rtt
= GetRTTEstimateInternal(begin_timestamp
, 50);
527 return (*rtt
!= InvalidRTT());
530 NetworkQualityEstimator::Observation::Observation(int32_t value
,
531 base::TimeTicks timestamp
)
532 : value(value
), timestamp(timestamp
) {
534 DCHECK(!timestamp
.is_null());
537 NetworkQualityEstimator::Observation::~Observation() {
540 NetworkQualityEstimator::ObservationBuffer::ObservationBuffer(
541 double weight_multiplier_per_second
)
542 : weight_multiplier_per_second_(weight_multiplier_per_second
) {
543 static_assert(kMaximumObservationsBufferSize
> 0U,
544 "Minimum size of observation buffer must be > 0");
545 DCHECK_GE(weight_multiplier_per_second_
, 0.0);
546 DCHECK_LE(weight_multiplier_per_second_
, 1.0);
549 NetworkQualityEstimator::ObservationBuffer::~ObservationBuffer() {
552 void NetworkQualityEstimator::ObservationBuffer::AddObservation(
553 const Observation
& observation
) {
554 DCHECK_LE(observations_
.size(),
555 static_cast<size_t>(kMaximumObservationsBufferSize
));
556 // Evict the oldest element if the buffer is already full.
557 if (observations_
.size() == kMaximumObservationsBufferSize
)
558 observations_
.pop_front();
560 observations_
.push_back(observation
);
561 DCHECK_LE(observations_
.size(),
562 static_cast<size_t>(kMaximumObservationsBufferSize
));
565 size_t NetworkQualityEstimator::ObservationBuffer::Size() const {
566 return observations_
.size();
569 void NetworkQualityEstimator::ObservationBuffer::Clear() {
570 observations_
.clear();
571 DCHECK(observations_
.empty());
574 base::TimeDelta
NetworkQualityEstimator::GetRTTEstimateInternal(
575 const base::TimeTicks
& begin_timestamp
,
576 int percentile
) const {
577 DCHECK(thread_checker_
.CalledOnValidThread());
578 DCHECK_GE(percentile
, 0);
579 DCHECK_LE(percentile
, 100);
580 if (rtt_msec_observations_
.Size() == 0)
583 // RTT observations are sorted by duration from shortest to longest, thus
584 // a higher percentile RTT will have a longer RTT than a lower percentile.
585 base::TimeDelta rtt
= InvalidRTT();
586 int32_t rtt_result
= -1;
587 if (rtt_msec_observations_
.GetPercentile(begin_timestamp
, &rtt_result
,
589 rtt
= base::TimeDelta::FromMilliseconds(rtt_result
);
594 int32_t NetworkQualityEstimator::GetDownlinkThroughputKbpsEstimateInternal(
595 const base::TimeTicks
& begin_timestamp
,
596 int percentile
) const {
597 DCHECK(thread_checker_
.CalledOnValidThread());
598 DCHECK_GE(percentile
, 0);
599 DCHECK_LE(percentile
, 100);
600 if (downstream_throughput_kbps_observations_
.Size() == 0)
601 return kInvalidThroughput
;
603 // Throughput observations are sorted by kbps from slowest to fastest,
604 // thus a higher percentile throughput will be faster than a lower one.
605 int32_t kbps
= kInvalidThroughput
;
606 downstream_throughput_kbps_observations_
.GetPercentile(begin_timestamp
, &kbps
,
611 void NetworkQualityEstimator::ObservationBuffer::ComputeWeightedObservations(
612 const base::TimeTicks
& begin_timestamp
,
613 std::vector
<WeightedObservation
>& weighted_observations
,
614 double* total_weight
) const {
615 weighted_observations
.clear();
616 double total_weight_observations
= 0.0;
617 base::TimeTicks now
= base::TimeTicks::Now();
619 for (const auto& observation
: observations_
) {
620 if (observation
.timestamp
< begin_timestamp
)
622 base::TimeDelta time_since_sample_taken
= now
- observation
.timestamp
;
624 pow(weight_multiplier_per_second_
, time_since_sample_taken
.InSeconds());
625 weight
= std::max(DBL_MIN
, std::min(1.0, weight
));
627 weighted_observations
.push_back(
628 WeightedObservation(observation
.value
, weight
));
629 total_weight_observations
+= weight
;
632 // Sort the samples by value in ascending order.
633 std::sort(weighted_observations
.begin(), weighted_observations
.end());
634 *total_weight
= total_weight_observations
;
637 bool NetworkQualityEstimator::ObservationBuffer::GetPercentile(
638 const base::TimeTicks
& begin_timestamp
,
640 int percentile
) const {
642 // Stores WeightedObservation in increasing order of value.
643 std::vector
<WeightedObservation
> weighted_observations
;
645 // Total weight of all observations in |weighted_observations|.
646 double total_weight
= 0.0;
648 ComputeWeightedObservations(begin_timestamp
, weighted_observations
,
650 if (weighted_observations
.empty())
653 DCHECK(!weighted_observations
.empty());
654 DCHECK_GT(total_weight
, 0.0);
656 // weighted_observations may have a smaller size than observations_ since the
657 // former contains only the observations later than begin_timestamp.
658 DCHECK_GE(observations_
.size(), weighted_observations
.size());
660 double desired_weight
= percentile
/ 100.0 * total_weight
;
662 double cumulative_weight_seen_so_far
= 0.0;
663 for (const auto& weighted_observation
: weighted_observations
) {
664 cumulative_weight_seen_so_far
+= weighted_observation
.weight
;
666 // TODO(tbansal): Consider interpolating between observations.
667 if (cumulative_weight_seen_so_far
>= desired_weight
) {
668 *result
= weighted_observation
.value
;
673 // Computation may reach here due to floating point errors. This may happen
674 // if |percentile| was 100 (or close to 100), and |desired_weight| was
675 // slightly larger than |total_weight| (due to floating point errors).
676 // In this case, we return the highest |value| among all observations.
677 // This is same as value of the last observation in the sorted vector.
678 *result
= weighted_observations
.at(weighted_observations
.size() - 1).value
;
682 NetworkQualityEstimator::NetworkID
683 NetworkQualityEstimator::GetCurrentNetworkID() const {
684 DCHECK(thread_checker_
.CalledOnValidThread());
686 // TODO(tbansal): crbug.com/498068 Add NetworkQualityEstimatorAndroid class
687 // that overrides this method on the Android platform.
689 // It is possible that the connection type changed between when
690 // GetConnectionType() was called and when the API to determine the
691 // network name was called. Check if that happened and retry until the
692 // connection type stabilizes. This is an imperfect solution but should
693 // capture majority of cases, and should not significantly affect estimates
694 // (that are approximate to begin with).
696 NetworkQualityEstimator::NetworkID
network_id(
697 NetworkChangeNotifier::GetConnectionType(), std::string());
699 switch (network_id
.type
) {
700 case NetworkChangeNotifier::ConnectionType::CONNECTION_UNKNOWN
:
701 case NetworkChangeNotifier::ConnectionType::CONNECTION_NONE
:
702 case NetworkChangeNotifier::ConnectionType::CONNECTION_BLUETOOTH
:
703 case NetworkChangeNotifier::ConnectionType::CONNECTION_ETHERNET
:
705 case NetworkChangeNotifier::ConnectionType::CONNECTION_WIFI
:
706 #if defined(OS_ANDROID) || defined(OS_LINUX) || defined(OS_CHROMEOS) || \
708 network_id
.id
= GetWifiSSID();
711 case NetworkChangeNotifier::ConnectionType::CONNECTION_2G
:
712 case NetworkChangeNotifier::ConnectionType::CONNECTION_3G
:
713 case NetworkChangeNotifier::ConnectionType::CONNECTION_4G
:
714 #if defined(OS_ANDROID)
715 network_id
.id
= android::GetTelephonyNetworkOperator();
719 NOTREACHED() << "Unexpected connection type = " << network_id
.type
;
723 if (network_id
.type
== NetworkChangeNotifier::GetConnectionType())
729 bool NetworkQualityEstimator::ReadCachedNetworkQualityEstimate() {
730 DCHECK(thread_checker_
.CalledOnValidThread());
732 // If the network name is unavailable, caching should not be performed.
733 if (current_network_id_
.id
.empty())
736 CachedNetworkQualities::const_iterator it
=
737 cached_network_qualities_
.find(current_network_id_
);
739 if (it
== cached_network_qualities_
.end())
742 NetworkQuality
network_quality(it
->second
.network_quality());
744 DCHECK_NE(InvalidRTT(), network_quality
.rtt());
745 DCHECK_NE(kInvalidThroughput
, network_quality
.downstream_throughput_kbps());
747 downstream_throughput_kbps_observations_
.AddObservation(Observation(
748 network_quality
.downstream_throughput_kbps(), base::TimeTicks::Now()));
749 rtt_msec_observations_
.AddObservation(Observation(
750 network_quality
.rtt().InMilliseconds(), base::TimeTicks::Now()));
754 void NetworkQualityEstimator::CacheNetworkQualityEstimate() {
755 DCHECK(thread_checker_
.CalledOnValidThread());
756 DCHECK_LE(cached_network_qualities_
.size(),
757 static_cast<size_t>(kMaximumNetworkQualityCacheSize
));
759 // If the network name is unavailable, caching should not be performed.
760 if (current_network_id_
.id
.empty())
763 NetworkQuality network_quality
= NetworkQuality(
764 GetRTTEstimateInternal(base::TimeTicks(), 50),
765 GetDownlinkThroughputKbpsEstimateInternal(base::TimeTicks(), 50));
766 if (network_quality
.rtt() == InvalidRTT() ||
767 network_quality
.downstream_throughput_kbps() == kInvalidThroughput
) {
771 if (cached_network_qualities_
.size() == kMaximumNetworkQualityCacheSize
) {
772 // Remove the oldest entry.
773 CachedNetworkQualities::iterator oldest_entry_iterator
=
774 cached_network_qualities_
.begin();
776 for (CachedNetworkQualities::iterator it
=
777 cached_network_qualities_
.begin();
778 it
!= cached_network_qualities_
.end(); ++it
) {
779 if ((it
->second
).OlderThan(oldest_entry_iterator
->second
))
780 oldest_entry_iterator
= it
;
782 cached_network_qualities_
.erase(oldest_entry_iterator
);
784 DCHECK_LT(cached_network_qualities_
.size(),
785 static_cast<size_t>(kMaximumNetworkQualityCacheSize
));
787 cached_network_qualities_
.insert(std::make_pair(
788 current_network_id_
, CachedNetworkQuality(network_quality
)));
789 DCHECK_LE(cached_network_qualities_
.size(),
790 static_cast<size_t>(kMaximumNetworkQualityCacheSize
));
793 NetworkQualityEstimator::CachedNetworkQuality::CachedNetworkQuality(
794 const NetworkQuality
& network_quality
)
795 : last_update_time_(base::TimeTicks::Now()),
796 network_quality_(network_quality
) {
799 NetworkQualityEstimator::CachedNetworkQuality::CachedNetworkQuality(
800 const CachedNetworkQuality
& other
)
801 : last_update_time_(other
.last_update_time_
),
802 network_quality_(other
.network_quality_
) {
805 NetworkQualityEstimator::CachedNetworkQuality::~CachedNetworkQuality() {
808 bool NetworkQualityEstimator::CachedNetworkQuality::OlderThan(
809 const CachedNetworkQuality
& cached_network_quality
) const {
810 return last_update_time_
< cached_network_quality
.last_update_time_
;
813 NetworkQualityEstimator::NetworkQuality::NetworkQuality()
814 : NetworkQuality(NetworkQualityEstimator::InvalidRTT(),
815 NetworkQualityEstimator::kInvalidThroughput
) {}
817 NetworkQualityEstimator::NetworkQuality::NetworkQuality(
818 const base::TimeDelta
& rtt
,
819 int32_t downstream_throughput_kbps
)
820 : rtt_(rtt
), downstream_throughput_kbps_(downstream_throughput_kbps
) {
821 DCHECK_GE(rtt_
, base::TimeDelta());
822 DCHECK_GE(downstream_throughput_kbps_
, 0);
825 NetworkQualityEstimator::NetworkQuality::NetworkQuality(
826 const NetworkQuality
& other
)
827 : NetworkQuality(other
.rtt_
, other
.downstream_throughput_kbps_
) {}
829 NetworkQualityEstimator::NetworkQuality::~NetworkQuality() {}
831 NetworkQualityEstimator::NetworkQuality
&
832 NetworkQualityEstimator::NetworkQuality::
833 operator=(const NetworkQuality
& other
) {
835 downstream_throughput_kbps_
= other
.downstream_throughput_kbps_
;