1 // Copyright 2016 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/nqe/throughput_analyzer.h"
6
7 #include <stdint.h>
8
9 #include <map>
10 #include <memory>
11 #include <string>
12 #include <utility>
13 #include <vector>
14
15 #include "base/containers/circular_deque.h"
16 #include "base/functional/bind.h"
17 #include "base/functional/callback_helpers.h"
18 #include "base/location.h"
19 #include "base/run_loop.h"
20 #include "base/strings/string_number_conversions.h"
21 #include "base/task/single_thread_task_runner.h"
22 #include "base/test/metrics/histogram_tester.h"
23 #include "base/test/scoped_feature_list.h"
24 #include "base/test/simple_test_tick_clock.h"
25 #include "base/test/test_timeouts.h"
26 #include "base/threading/platform_thread.h"
27 #include "base/time/default_tick_clock.h"
28 #include "base/time/time.h"
29 #include "build/build_config.h"
30 #include "net/base/features.h"
31 #include "net/base/isolation_info.h"
32 #include "net/base/schemeful_site.h"
33 #include "net/dns/mock_host_resolver.h"
34 #include "net/nqe/network_quality_estimator.h"
35 #include "net/nqe/network_quality_estimator_params.h"
36 #include "net/nqe/network_quality_estimator_test_util.h"
37 #include "net/nqe/network_quality_estimator_util.h"
38 #include "net/test/test_with_task_environment.h"
39 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
40 #include "net/url_request/url_request.h"
41 #include "net/url_request/url_request_context.h"
42 #include "net/url_request/url_request_context_builder.h"
43 #include "net/url_request/url_request_test_util.h"
44 #include "testing/gtest/include/gtest/gtest.h"
45 #include "url/gurl.h"
46
47 namespace net::nqe {
48
49 namespace {
50
51 // Creates a mock resolver mapping example.com to a public IP address.
CreateMockHostResolver()52 std::unique_ptr<HostResolver> CreateMockHostResolver() {
53 auto host_resolver = std::make_unique<MockCachingHostResolver>(
54 /*cache_invalidation_num=*/0,
55 /*default_result=*/ERR_NAME_NOT_RESOLVED);
56
57 // local.com resolves to a private IP address.
58 host_resolver->rules()->AddRule("local.com", "127.0.0.1");
59 host_resolver->LoadIntoCache(HostPortPair("local.com", 80),
60 NetworkAnonymizationKey(), absl::nullopt);
61 // Hosts not listed here (e.g., "example.com") are treated as external. See
62 // ThroughputAnalyzerTest.PrivateHost below.
63
64 return host_resolver;
65 }
66
67 class TestThroughputAnalyzer : public internal::ThroughputAnalyzer {
68 public:
TestThroughputAnalyzer(NetworkQualityEstimator * network_quality_estimator,NetworkQualityEstimatorParams * params,const base::TickClock * tick_clock)69 TestThroughputAnalyzer(NetworkQualityEstimator* network_quality_estimator,
70 NetworkQualityEstimatorParams* params,
71 const base::TickClock* tick_clock)
72 : internal::ThroughputAnalyzer(
73 network_quality_estimator,
74 params,
75 base::SingleThreadTaskRunner::GetCurrentDefault(),
76 base::BindRepeating(
77 &TestThroughputAnalyzer::OnNewThroughputObservationAvailable,
78 base::Unretained(this)),
79 tick_clock,
80 NetLogWithSource::Make(NetLogSourceType::NONE)) {}
81
82 TestThroughputAnalyzer(const TestThroughputAnalyzer&) = delete;
83 TestThroughputAnalyzer& operator=(const TestThroughputAnalyzer&) = delete;
84
85 ~TestThroughputAnalyzer() override = default;
86
throughput_observations_received() const87 int32_t throughput_observations_received() const {
88 return throughput_observations_received_;
89 }
90
OnNewThroughputObservationAvailable(int32_t downstream_kbps)91 void OnNewThroughputObservationAvailable(int32_t downstream_kbps) {
92 throughput_observations_received_++;
93 }
94
GetBitsReceived() const95 int64_t GetBitsReceived() const override { return bits_received_; }
96
IncrementBitsReceived(int64_t additional_bits_received)97 void IncrementBitsReceived(int64_t additional_bits_received) {
98 bits_received_ += additional_bits_received;
99 }
100
101 using internal::ThroughputAnalyzer::CountActiveInFlightRequests;
102 using internal::ThroughputAnalyzer::
103 disable_throughput_measurements_for_testing;
104 using internal::ThroughputAnalyzer::EraseHangingRequests;
105 using internal::ThroughputAnalyzer::IsHangingWindow;
106
107 private:
108 int throughput_observations_received_ = 0;
109
110 int64_t bits_received_ = 0;
111 };
112
113 using ThroughputAnalyzerTest = TestWithTaskEnvironment;
114
TEST_F(ThroughputAnalyzerTest,PrivateHost)115 TEST_F(ThroughputAnalyzerTest, PrivateHost) {
116 auto host_resolver = CreateMockHostResolver();
117 EXPECT_FALSE(nqe::internal::IsPrivateHostForTesting(
118 host_resolver.get(), HostPortPair("example.com", 80),
119 NetworkAnonymizationKey()));
120 EXPECT_TRUE(nqe::internal::IsPrivateHostForTesting(
121 host_resolver.get(), HostPortPair("local.com", 80),
122 NetworkAnonymizationKey()));
123 }
124
125 #if BUILDFLAG(IS_IOS) || BUILDFLAG(IS_ANDROID)
126 // Flaky on iOS: crbug.com/672917.
127 // Flaky on Android: crbug.com/1223950.
128 #define MAYBE_MaximumRequests DISABLED_MaximumRequests
129 #else
130 #define MAYBE_MaximumRequests MaximumRequests
131 #endif
TEST_F(ThroughputAnalyzerTest,MAYBE_MaximumRequests)132 TEST_F(ThroughputAnalyzerTest, MAYBE_MaximumRequests) {
133 const struct TestCase {
134 GURL url;
135 bool is_local;
136 } kTestCases[] = {
137 {GURL("http://127.0.0.1/test.html"), true /* is_local */},
138 {GURL("http://example.com/test.html"), false /* is_local */},
139 {GURL("http://local.com/test.html"), true /* is_local */},
140 };
141
142 for (const auto& test_case : kTestCases) {
143 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
144 TestNetworkQualityEstimator network_quality_estimator;
145 std::map<std::string, std::string> variation_params;
146 NetworkQualityEstimatorParams params(variation_params);
147 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
148 ¶ms, tick_clock);
149
150 TestDelegate test_delegate;
151 auto context_builder = CreateTestURLRequestContextBuilder();
152 context_builder->set_host_resolver(CreateMockHostResolver());
153 auto context = context_builder->Build();
154
155 ASSERT_FALSE(
156 throughput_analyzer.disable_throughput_measurements_for_testing());
157 base::circular_deque<std::unique_ptr<URLRequest>> requests;
158
159 // Start more requests than the maximum number of requests that can be held
160 // in the memory.
161 EXPECT_EQ(test_case.is_local, nqe::internal::IsPrivateHostForTesting(
162 context->host_resolver(),
163 HostPortPair::FromURL(test_case.url),
164 NetworkAnonymizationKey()));
165 for (size_t i = 0; i < 1000; ++i) {
166 std::unique_ptr<URLRequest> request(
167 context->CreateRequest(test_case.url, DEFAULT_PRIORITY,
168 &test_delegate, TRAFFIC_ANNOTATION_FOR_TESTS));
169 throughput_analyzer.NotifyStartTransaction(*(request.get()));
170 requests.push_back(std::move(request));
171 }
172 // Too many local requests should cause the |throughput_analyzer| to disable
173 // throughput measurements.
174 EXPECT_NE(test_case.is_local,
175 throughput_analyzer.IsCurrentlyTrackingThroughput());
176 }
177 }
178
179 #if BUILDFLAG(IS_IOS)
180 // Flaky on iOS: crbug.com/672917.
181 #define MAYBE_MaximumRequestsWithNetworkAnonymizationKey \
182 DISABLED_MaximumRequestsWithNetworkAnonymizationKey
183 #else
184 #define MAYBE_MaximumRequestsWithNetworkAnonymizationKey \
185 MaximumRequestsWithNetworkAnonymizationKey
186 #endif
187 // Make sure that the NetworkAnonymizationKey is respected when resolving a host
188 // from the cache.
TEST_F(ThroughputAnalyzerTest,MAYBE_MaximumRequestsWithNetworkAnonymizationKey)189 TEST_F(ThroughputAnalyzerTest,
190 MAYBE_MaximumRequestsWithNetworkAnonymizationKey) {
191 const SchemefulSite kSite(GURL("https://foo.test/"));
192 const auto kNetworkAnonymizationKey =
193 NetworkAnonymizationKey::CreateSameSite(kSite);
194 const net::NetworkIsolationKey kNetworkIsolationKey(kSite, kSite);
195 const GURL kUrl = GURL("http://foo.test/test.html");
196 const url::Origin kSiteOrigin = url::Origin::Create(kSite.GetURL());
197
198 base::test::ScopedFeatureList feature_list;
199 feature_list.InitAndEnableFeature(
200 features::kSplitHostCacheByNetworkIsolationKey);
201
202 for (bool use_network_isolation_key : {false, true}) {
203 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
204 TestNetworkQualityEstimator network_quality_estimator;
205 std::map<std::string, std::string> variation_params;
206 NetworkQualityEstimatorParams params(variation_params);
207 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
208 ¶ms, tick_clock);
209
210 TestDelegate test_delegate;
211 auto context_builder = CreateTestURLRequestContextBuilder();
212 auto mock_host_resolver = std::make_unique<MockCachingHostResolver>();
213
214 // Add an entry to the host cache mapping kUrl to non-local IP when using an
215 // empty NetworkAnonymizationKey.
216 mock_host_resolver->rules()->AddRule(kUrl.host(), "1.2.3.4");
217 mock_host_resolver->LoadIntoCache(HostPortPair::FromURL(kUrl),
218 NetworkAnonymizationKey(), absl::nullopt);
219
220 // Add an entry to the host cache mapping kUrl to local IP when using
221 // kNetworkAnonymizationKey.
222 mock_host_resolver->rules()->ClearRules();
223 mock_host_resolver->rules()->AddRule(kUrl.host(), "127.0.0.1");
224 mock_host_resolver->LoadIntoCache(HostPortPair::FromURL(kUrl),
225 kNetworkAnonymizationKey, absl::nullopt);
226
227 context_builder->set_host_resolver(std::move(mock_host_resolver));
228 auto context = context_builder->Build();
229 ASSERT_FALSE(
230 throughput_analyzer.disable_throughput_measurements_for_testing());
231 base::circular_deque<std::unique_ptr<URLRequest>> requests;
232
233 // Start more requests than the maximum number of requests that can be held
234 // in the memory.
235 EXPECT_EQ(use_network_isolation_key,
236 nqe::internal::IsPrivateHostForTesting(
237 context->host_resolver(), HostPortPair::FromURL(kUrl),
238 use_network_isolation_key ? kNetworkAnonymizationKey
239 : NetworkAnonymizationKey()));
240 for (size_t i = 0; i < 1000; ++i) {
241 std::unique_ptr<URLRequest> request(
242 context->CreateRequest(kUrl, DEFAULT_PRIORITY, &test_delegate,
243 TRAFFIC_ANNOTATION_FOR_TESTS));
244 if (use_network_isolation_key) {
245 request->set_isolation_info(net::IsolationInfo::Create(
246 net::IsolationInfo::RequestType::kOther, kSiteOrigin, kSiteOrigin,
247 net::SiteForCookies()));
248 }
249 throughput_analyzer.NotifyStartTransaction(*(request.get()));
250 requests.push_back(std::move(request));
251 }
252 // Too many local requests should cause the |throughput_analyzer| to disable
253 // throughput measurements.
254 EXPECT_NE(use_network_isolation_key,
255 throughput_analyzer.IsCurrentlyTrackingThroughput());
256 }
257 }
258
259 // Tests that the throughput observation is taken only if there are sufficient
260 // number of requests in-flight.
TEST_F(ThroughputAnalyzerTest,TestMinRequestsForThroughputSample)261 TEST_F(ThroughputAnalyzerTest, TestMinRequestsForThroughputSample) {
262 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
263 TestNetworkQualityEstimator network_quality_estimator;
264 std::map<std::string, std::string> variation_params;
265 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
266 NetworkQualityEstimatorParams params(variation_params);
267 // Set HTTP RTT to a large value so that the throughput observation window
268 // is not detected as hanging. In practice, this would be provided by
269 // |network_quality_estimator| based on the recent observations.
270 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(100));
271
272 for (size_t num_requests = 1;
273 num_requests <= params.throughput_min_requests_in_flight() + 1;
274 ++num_requests) {
275 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
276 ¶ms, tick_clock);
277 auto context_builder = CreateTestURLRequestContextBuilder();
278 context_builder->set_host_resolver(CreateMockHostResolver());
279 auto context = context_builder->Build();
280 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
281
282 std::vector<TestDelegate> not_local_test_delegates(num_requests);
283 for (auto& delegate : not_local_test_delegates) {
284 // We don't care about completion, except for the first one (see below).
285 delegate.set_on_complete(base::DoNothing());
286 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
287 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &delegate,
288 TRAFFIC_ANNOTATION_FOR_TESTS));
289 request_not_local->Start();
290 requests_not_local.push_back(std::move(request_not_local));
291 }
292 not_local_test_delegates[0].RunUntilComplete();
293
294 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
295
296 for (const auto& request : requests_not_local) {
297 throughput_analyzer.NotifyStartTransaction(*request);
298 }
299
300 // Increment the bytes received count to emulate the bytes received for
301 // |request_local| and |requests_not_local|.
302 throughput_analyzer.IncrementBitsReceived(100 * 1000 * 8);
303
304 for (const auto& request : requests_not_local) {
305 throughput_analyzer.NotifyRequestCompleted(*request);
306 }
307 base::RunLoop().RunUntilIdle();
308
309 int expected_throughput_observations =
310 num_requests >= params.throughput_min_requests_in_flight() ? 1 : 0;
311 EXPECT_EQ(expected_throughput_observations,
312 throughput_analyzer.throughput_observations_received());
313 }
314 }
315
316 // Tests that the hanging requests are dropped from the |requests_|, and
317 // throughput observation window is ended.
TEST_F(ThroughputAnalyzerTest,TestHangingRequests)318 TEST_F(ThroughputAnalyzerTest, TestHangingRequests) {
319 static const struct {
320 int hanging_request_duration_http_rtt_multiplier;
321 base::TimeDelta http_rtt;
322 base::TimeDelta requests_hang_duration;
323 bool expect_throughput_observation;
324 } tests[] = {
325 {
326 // |requests_hang_duration| is less than 5 times the HTTP RTT.
327 // Requests should not be marked as hanging.
328 5,
329 base::Milliseconds(1000),
330 base::Milliseconds(3000),
331 true,
332 },
333 {
334 // |requests_hang_duration| is more than 5 times the HTTP RTT.
335 // Requests should be marked as hanging.
336 5,
337 base::Milliseconds(200),
338 base::Milliseconds(3000),
339 false,
340 },
341 {
342 // |requests_hang_duration| is less than
343 // |hanging_request_min_duration_msec|. Requests should not be marked
344 // as hanging.
345 1,
346 base::Milliseconds(100),
347 base::Milliseconds(100),
348 true,
349 },
350 {
351 // |requests_hang_duration| is more than
352 // |hanging_request_min_duration_msec|. Requests should be marked as
353 // hanging.
354 1,
355 base::Milliseconds(2000),
356 base::Milliseconds(3100),
357 false,
358 },
359 {
360 // |requests_hang_duration| is less than 5 times the HTTP RTT.
361 // Requests should not be marked as hanging.
362 5,
363 base::Seconds(2),
364 base::Seconds(1),
365 true,
366 },
367 {
368 // HTTP RTT is unavailable. Requests should not be marked as hanging.
369 5,
370 base::Seconds(-1),
371 base::Seconds(-1),
372 true,
373 },
374 };
375
376 for (const auto& test : tests) {
377 base::HistogramTester histogram_tester;
378 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
379 TestNetworkQualityEstimator network_quality_estimator;
380 if (test.http_rtt >= base::TimeDelta())
381 network_quality_estimator.SetStartTimeNullHttpRtt(test.http_rtt);
382 std::map<std::string, std::string> variation_params;
383 // Set the transport RTT multiplier to a large value so that the hanging
384 // request decision is made only on the basis of the HTTP RTT.
385 variation_params
386 ["hanging_request_http_rtt_upper_bound_transport_rtt_multiplier"] =
387 "10000";
388 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
389 variation_params["hanging_request_duration_http_rtt_multiplier"] =
390 base::NumberToString(test.hanging_request_duration_http_rtt_multiplier);
391
392 NetworkQualityEstimatorParams params(variation_params);
393
394 const size_t num_requests = params.throughput_min_requests_in_flight();
395 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
396 ¶ms, tick_clock);
397 auto context_builder = CreateTestURLRequestContextBuilder();
398 context_builder->set_host_resolver(CreateMockHostResolver());
399 auto context = context_builder->Build();
400 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
401
402 std::vector<TestDelegate> not_local_test_delegates(num_requests);
403 for (size_t i = 0; i < num_requests; ++i) {
404 // We don't care about completion, except for the first one (see below).
405 not_local_test_delegates[i].set_on_complete(base::DoNothing());
406 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
407 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY,
408 ¬_local_test_delegates[i], TRAFFIC_ANNOTATION_FOR_TESTS));
409 request_not_local->Start();
410 requests_not_local.push_back(std::move(request_not_local));
411 }
412
413 not_local_test_delegates[0].RunUntilComplete();
414
415 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
416
417 for (size_t i = 0; i < num_requests; ++i) {
418 throughput_analyzer.NotifyStartTransaction(*requests_not_local.at(i));
419 }
420
421 // Increment the bytes received count to emulate the bytes received for
422 // |request_local| and |requests_not_local|.
423 throughput_analyzer.IncrementBitsReceived(100 * 1000 * 8);
424
425 // Mark in-flight requests as hanging requests (if specified in the test
426 // params).
427 if (test.requests_hang_duration >= base::TimeDelta())
428 base::PlatformThread::Sleep(test.requests_hang_duration);
429
430 EXPECT_EQ(num_requests, throughput_analyzer.CountActiveInFlightRequests());
431
432 for (size_t i = 0; i < num_requests; ++i) {
433 throughput_analyzer.NotifyRequestCompleted(*requests_not_local.at(i));
434 if (!test.expect_throughput_observation) {
435 // All in-flight requests should be marked as hanging, and thus should
436 // be deleted from the set of in-flight requests.
437 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
438 } else {
439 // One request should be deleted at one time.
440 EXPECT_EQ(requests_not_local.size() - i - 1,
441 throughput_analyzer.CountActiveInFlightRequests());
442 }
443 }
444
445 base::RunLoop().RunUntilIdle();
446
447 EXPECT_EQ(test.expect_throughput_observation,
448 throughput_analyzer.throughput_observations_received() > 0);
449 }
450 }
451
452 // Tests that the check for hanging requests is done at most once per second.
TEST_F(ThroughputAnalyzerTest,TestHangingRequestsCheckedOnlyPeriodically)453 TEST_F(ThroughputAnalyzerTest, TestHangingRequestsCheckedOnlyPeriodically) {
454 base::SimpleTestTickClock tick_clock;
455
456 TestNetworkQualityEstimator network_quality_estimator;
457 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(1));
458 std::map<std::string, std::string> variation_params;
459 variation_params["hanging_request_duration_http_rtt_multiplier"] = "5";
460 variation_params["hanging_request_min_duration_msec"] = "2000";
461 NetworkQualityEstimatorParams params(variation_params);
462
463 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
464 ¶ms, &tick_clock);
465
466 TestDelegate test_delegate;
467 auto context_builder = CreateTestURLRequestContextBuilder();
468 context_builder->set_host_resolver(CreateMockHostResolver());
469 auto context = context_builder->Build();
470 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
471
472 for (size_t i = 0; i < 2; ++i) {
473 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
474 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
475 TRAFFIC_ANNOTATION_FOR_TESTS));
476 request_not_local->Start();
477 requests_not_local.push_back(std::move(request_not_local));
478 }
479
480 std::unique_ptr<URLRequest> some_other_request(context->CreateRequest(
481 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
482 TRAFFIC_ANNOTATION_FOR_TESTS));
483
484 test_delegate.RunUntilComplete();
485
486 // First request starts at t=1. The second request starts at t=2. The first
487 // request would be marked as hanging at t=6, and the second request at t=7
488 // seconds.
489 for (size_t i = 0; i < 2; ++i) {
490 tick_clock.Advance(base::Milliseconds(1000));
491 throughput_analyzer.NotifyStartTransaction(*requests_not_local.at(i));
492 }
493
494 EXPECT_EQ(2u, throughput_analyzer.CountActiveInFlightRequests());
495 tick_clock.Advance(base::Milliseconds(3500));
496 // Current time is t = 5.5 seconds.
497 throughput_analyzer.EraseHangingRequests(*some_other_request);
498 EXPECT_EQ(2u, throughput_analyzer.CountActiveInFlightRequests());
499
500 tick_clock.Advance(base::Milliseconds(1000));
501 // Current time is t = 6.5 seconds. One request should be marked as hanging.
502 throughput_analyzer.EraseHangingRequests(*some_other_request);
503 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
504
505 // Current time is t = 6.5 seconds. Calling NotifyBytesRead again should not
506 // run the hanging request checker since the last check was at t=6.5 seconds.
507 throughput_analyzer.EraseHangingRequests(*some_other_request);
508 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
509
510 tick_clock.Advance(base::Milliseconds(600));
511 // Current time is t = 7.1 seconds. Calling NotifyBytesRead again should not
512 // run the hanging request checker since the last check was at t=6.5 seconds
513 // (less than 1 second ago).
514 throughput_analyzer.EraseHangingRequests(*some_other_request);
515 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
516
517 tick_clock.Advance(base::Milliseconds(400));
518 // Current time is t = 7.5 seconds. Calling NotifyBytesRead again should run
519 // the hanging request checker since the last check was at t=6.5 seconds (at
520 // least 1 second ago).
521 throughput_analyzer.EraseHangingRequests(*some_other_request);
522 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
523 }
524
525 // Tests that the last received time for a request is updated when data is
526 // received for that request.
TEST_F(ThroughputAnalyzerTest,TestLastReceivedTimeIsUpdated)527 TEST_F(ThroughputAnalyzerTest, TestLastReceivedTimeIsUpdated) {
528 base::SimpleTestTickClock tick_clock;
529
530 TestNetworkQualityEstimator network_quality_estimator;
531 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(1));
532 std::map<std::string, std::string> variation_params;
533 variation_params["hanging_request_duration_http_rtt_multiplier"] = "5";
534 variation_params["hanging_request_min_duration_msec"] = "2000";
535 NetworkQualityEstimatorParams params(variation_params);
536
537 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
538 ¶ms, &tick_clock);
539
540 TestDelegate test_delegate;
541 auto context_builder = CreateTestURLRequestContextBuilder();
542 context_builder->set_host_resolver(CreateMockHostResolver());
543 auto context = context_builder->Build();
544
545 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
546 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
547 TRAFFIC_ANNOTATION_FOR_TESTS));
548 request_not_local->Start();
549
550 test_delegate.RunUntilComplete();
551
552 std::unique_ptr<URLRequest> some_other_request(context->CreateRequest(
553 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
554 TRAFFIC_ANNOTATION_FOR_TESTS));
555
556 // Start time for the request is t=0 second. The request will be marked as
557 // hanging at t=5 seconds.
558 throughput_analyzer.NotifyStartTransaction(*request_not_local);
559
560 tick_clock.Advance(base::Milliseconds(4000));
561 // Current time is t=4.0 seconds.
562
563 throughput_analyzer.EraseHangingRequests(*some_other_request);
564 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
565
566 // The request will be marked as hanging at t=9 seconds.
567 throughput_analyzer.NotifyBytesRead(*request_not_local);
568 tick_clock.Advance(base::Milliseconds(4000));
569 // Current time is t=8 seconds.
570 throughput_analyzer.EraseHangingRequests(*some_other_request);
571 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
572
573 tick_clock.Advance(base::Milliseconds(2000));
574 // Current time is t=10 seconds.
575 throughput_analyzer.EraseHangingRequests(*some_other_request);
576 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
577 }
578
579 // Test that a request that has been hanging for a long time is deleted
580 // immediately when EraseHangingRequests is called even if the last hanging
581 // request check was done recently.
TEST_F(ThroughputAnalyzerTest,TestRequestDeletedImmediately)582 TEST_F(ThroughputAnalyzerTest, TestRequestDeletedImmediately) {
583 base::SimpleTestTickClock tick_clock;
584
585 TestNetworkQualityEstimator network_quality_estimator;
586 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(1));
587 std::map<std::string, std::string> variation_params;
588 variation_params["hanging_request_duration_http_rtt_multiplier"] = "2";
589 NetworkQualityEstimatorParams params(variation_params);
590
591 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
592 ¶ms, &tick_clock);
593
594 TestDelegate test_delegate;
595 auto context_builder = CreateTestURLRequestContextBuilder();
596 context_builder->set_host_resolver(CreateMockHostResolver());
597 auto context = context_builder->Build();
598
599 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
600 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
601 TRAFFIC_ANNOTATION_FOR_TESTS));
602 request_not_local->Start();
603
604 test_delegate.RunUntilComplete();
605
606 // Start time for the request is t=0 second. The request will be marked as
607 // hanging at t=2 seconds.
608 throughput_analyzer.NotifyStartTransaction(*request_not_local);
609 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
610
611 tick_clock.Advance(base::Milliseconds(2900));
612 // Current time is t=2.9 seconds.
613
614 throughput_analyzer.EraseHangingRequests(*request_not_local);
615 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
616
617 // |request_not_local| should be deleted since it has been idle for 2.4
618 // seconds.
619 tick_clock.Advance(base::Milliseconds(500));
620 throughput_analyzer.NotifyBytesRead(*request_not_local);
621 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
622 }
623
624 #if BUILDFLAG(IS_IOS)
625 // Flaky on iOS: crbug.com/672917.
626 #define MAYBE_TestThroughputWithMultipleRequestsOverlap \
627 DISABLED_TestThroughputWithMultipleRequestsOverlap
628 #else
629 #define MAYBE_TestThroughputWithMultipleRequestsOverlap \
630 TestThroughputWithMultipleRequestsOverlap
631 #endif
632 // Tests if the throughput observation is taken correctly when local and network
633 // requests overlap.
TEST_F(ThroughputAnalyzerTest,MAYBE_TestThroughputWithMultipleRequestsOverlap)634 TEST_F(ThroughputAnalyzerTest,
635 MAYBE_TestThroughputWithMultipleRequestsOverlap) {
636 static const struct {
637 bool start_local_request;
638 bool local_request_completes_first;
639 bool expect_throughput_observation;
640 } tests[] = {
641 {
642 false, false, true,
643 },
644 {
645 true, false, false,
646 },
647 {
648 true, true, true,
649 },
650 };
651
652 for (const auto& test : tests) {
653 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
654 TestNetworkQualityEstimator network_quality_estimator;
655 // Localhost requests are not allowed for estimation purposes.
656 std::map<std::string, std::string> variation_params;
657 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
658 NetworkQualityEstimatorParams params(variation_params);
659
660 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
661 ¶ms, tick_clock);
662
663 TestDelegate local_delegate;
664 local_delegate.set_on_complete(base::DoNothing());
665 auto context_builder = CreateTestURLRequestContextBuilder();
666 context_builder->set_host_resolver(CreateMockHostResolver());
667 auto context = context_builder->Build();
668 std::unique_ptr<URLRequest> request_local;
669
670 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
671 std::vector<TestDelegate> not_local_test_delegates(
672 params.throughput_min_requests_in_flight());
673 for (size_t i = 0; i < params.throughput_min_requests_in_flight(); ++i) {
674 // We don't care about completion, except for the first one (see below).
675 not_local_test_delegates[i].set_on_complete(base::DoNothing());
676 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
677 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY,
678 ¬_local_test_delegates[i], TRAFFIC_ANNOTATION_FOR_TESTS));
679 request_not_local->Start();
680 requests_not_local.push_back(std::move(request_not_local));
681 }
682
683 if (test.start_local_request) {
684 request_local = context->CreateRequest(GURL("http://127.0.0.1/echo.html"),
685 DEFAULT_PRIORITY, &local_delegate,
686 TRAFFIC_ANNOTATION_FOR_TESTS);
687 request_local->Start();
688 }
689
690 // Wait until the first not-local request completes.
691 not_local_test_delegates[0].RunUntilComplete();
692
693 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
694
695 // If |test.start_local_request| is true, then |request_local| starts
696 // before |request_not_local|, and ends after |request_not_local|. Thus,
697 // network quality estimator should not get a chance to record throughput
698 // observation from |request_not_local| because of ongoing local request
699 // at all times.
700 if (test.start_local_request)
701 throughput_analyzer.NotifyStartTransaction(*request_local);
702
703 for (const auto& request : requests_not_local) {
704 throughput_analyzer.NotifyStartTransaction(*request);
705 }
706
707 if (test.local_request_completes_first) {
708 ASSERT_TRUE(test.start_local_request);
709 throughput_analyzer.NotifyRequestCompleted(*request_local);
710 }
711
712 // Increment the bytes received count to emulate the bytes received for
713 // |request_local| and |requests_not_local|.
714 throughput_analyzer.IncrementBitsReceived(100 * 1000 * 8);
715
716 for (const auto& request : requests_not_local) {
717 throughput_analyzer.NotifyRequestCompleted(*request);
718 }
719 if (test.start_local_request && !test.local_request_completes_first)
720 throughput_analyzer.NotifyRequestCompleted(*request_local);
721
722 // Pump the message loop to let analyzer tasks get processed.
723 base::RunLoop().RunUntilIdle();
724
725 int expected_throughput_observations =
726 test.expect_throughput_observation ? 1 : 0;
727 EXPECT_EQ(expected_throughput_observations,
728 throughput_analyzer.throughput_observations_received());
729 }
730 }
731
732 // Tests if the throughput observation is taken correctly when two network
733 // requests overlap.
TEST_F(ThroughputAnalyzerTest,TestThroughputWithNetworkRequestsOverlap)734 TEST_F(ThroughputAnalyzerTest, TestThroughputWithNetworkRequestsOverlap) {
735 static const struct {
736 size_t throughput_min_requests_in_flight;
737 size_t number_requests_in_flight;
738 int64_t increment_bits;
739 bool expect_throughput_observation;
740 } tests[] = {
741 {
742 1, 2, 100 * 1000 * 8, true,
743 },
744 {
745 3, 1, 100 * 1000 * 8, false,
746 },
747 {
748 3, 2, 100 * 1000 * 8, false,
749 },
750 {
751 3, 3, 100 * 1000 * 8, true,
752 },
753 {
754 3, 4, 100 * 1000 * 8, true,
755 },
756 {
757 1, 2, 1, false,
758 },
759 };
760
761 for (const auto& test : tests) {
762 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
763 TestNetworkQualityEstimator network_quality_estimator;
764 // Localhost requests are not allowed for estimation purposes.
765 std::map<std::string, std::string> variation_params;
766 variation_params["throughput_min_requests_in_flight"] =
767 base::NumberToString(test.throughput_min_requests_in_flight);
768 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
769 NetworkQualityEstimatorParams params(variation_params);
770 // Set HTTP RTT to a large value so that the throughput observation window
771 // is not detected as hanging. In practice, this would be provided by
772 // |network_quality_estimator| based on the recent observations.
773 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(100));
774
775 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
776 ¶ms, tick_clock);
777 auto context_builder = CreateTestURLRequestContextBuilder();
778 context_builder->set_host_resolver(CreateMockHostResolver());
779 auto context = context_builder->Build();
780
781 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
782
783 std::vector<std::unique_ptr<URLRequest>> requests_in_flight;
784 std::vector<TestDelegate> in_flight_test_delegates(
785 test.number_requests_in_flight);
786 for (size_t i = 0; i < test.number_requests_in_flight; ++i) {
787 // We don't care about completion, except for the first one (see below).
788 in_flight_test_delegates[i].set_on_complete(base::DoNothing());
789 std::unique_ptr<URLRequest> request_network_1 = context->CreateRequest(
790 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY,
791 &in_flight_test_delegates[i], TRAFFIC_ANNOTATION_FOR_TESTS);
792 requests_in_flight.push_back(std::move(request_network_1));
793 requests_in_flight.back()->Start();
794 }
795
796 in_flight_test_delegates[0].RunUntilComplete();
797
798 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
799
800 for (size_t i = 0; i < test.number_requests_in_flight; ++i) {
801 URLRequest* request = requests_in_flight.at(i).get();
802 throughput_analyzer.NotifyStartTransaction(*request);
803 }
804
805 // Increment the bytes received count to emulate the bytes received for
806 // |request_network_1| and |request_network_2|.
807 throughput_analyzer.IncrementBitsReceived(test.increment_bits);
808
809 for (size_t i = 0; i < test.number_requests_in_flight; ++i) {
810 URLRequest* request = requests_in_flight.at(i).get();
811 throughput_analyzer.NotifyRequestCompleted(*request);
812 }
813
814 base::RunLoop().RunUntilIdle();
815
816 // Only one observation should be taken since two requests overlap.
817 if (test.expect_throughput_observation) {
818 EXPECT_EQ(1, throughput_analyzer.throughput_observations_received());
819 } else {
820 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
821 }
822 }
823 }
824
825 // Tests if the throughput observation is taken correctly when the start and end
826 // of network requests overlap, and the minimum number of in flight requests
827 // when taking an observation is more than 1.
TEST_F(ThroughputAnalyzerTest,TestThroughputWithMultipleNetworkRequests)828 TEST_F(ThroughputAnalyzerTest, TestThroughputWithMultipleNetworkRequests) {
829 const base::test::ScopedRunLoopTimeout increased_run_timeout(
830 FROM_HERE, TestTimeouts::action_max_timeout());
831
832 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
833 TestNetworkQualityEstimator network_quality_estimator;
834 std::map<std::string, std::string> variation_params;
835 variation_params["throughput_min_requests_in_flight"] = "3";
836 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
837 NetworkQualityEstimatorParams params(variation_params);
838 // Set HTTP RTT to a large value so that the throughput observation window
839 // is not detected as hanging. In practice, this would be provided by
840 // |network_quality_estimator| based on the recent observations.
841 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(100));
842
843 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
844 ¶ms, tick_clock);
845 TestDelegate test_delegate;
846 auto context_builder = CreateTestURLRequestContextBuilder();
847 context_builder->set_host_resolver(CreateMockHostResolver());
848 auto context = context_builder->Build();
849
850 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
851
852 std::unique_ptr<URLRequest> request_1 = context->CreateRequest(
853 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
854 TRAFFIC_ANNOTATION_FOR_TESTS);
855 std::unique_ptr<URLRequest> request_2 = context->CreateRequest(
856 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
857 TRAFFIC_ANNOTATION_FOR_TESTS);
858 std::unique_ptr<URLRequest> request_3 = context->CreateRequest(
859 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
860 TRAFFIC_ANNOTATION_FOR_TESTS);
861 std::unique_ptr<URLRequest> request_4 = context->CreateRequest(
862 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
863 TRAFFIC_ANNOTATION_FOR_TESTS);
864
865 request_1->Start();
866 request_2->Start();
867 request_3->Start();
868 request_4->Start();
869
870 // We dispatched four requests, so wait for four completions.
871 for (int i = 0; i < 4; ++i)
872 test_delegate.RunUntilComplete();
873
874 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
875
876 throughput_analyzer.NotifyStartTransaction(*(request_1.get()));
877 throughput_analyzer.NotifyStartTransaction(*(request_2.get()));
878
879 const size_t increment_bits = 100 * 1000 * 8;
880
881 // Increment the bytes received count to emulate the bytes received for
882 // |request_1| and |request_2|.
883 throughput_analyzer.IncrementBitsReceived(increment_bits);
884
885 throughput_analyzer.NotifyRequestCompleted(*(request_1.get()));
886 base::RunLoop().RunUntilIdle();
887
888 // No observation should be taken since only 1 request is in flight.
889 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
890
891 throughput_analyzer.NotifyStartTransaction(*(request_3.get()));
892 throughput_analyzer.NotifyStartTransaction(*(request_4.get()));
893 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
894
895 // 3 requests are in flight which is at least as many as the minimum number of
896 // in flight requests required. An observation should be taken.
897 throughput_analyzer.IncrementBitsReceived(increment_bits);
898
899 // Only one observation should be taken since two requests overlap.
900 throughput_analyzer.NotifyRequestCompleted(*(request_2.get()));
901 base::RunLoop().RunUntilIdle();
902
903 EXPECT_EQ(1, throughput_analyzer.throughput_observations_received());
904 throughput_analyzer.NotifyRequestCompleted(*(request_3.get()));
905 throughput_analyzer.NotifyRequestCompleted(*(request_4.get()));
906 EXPECT_EQ(1, throughput_analyzer.throughput_observations_received());
907 }
908
TEST_F(ThroughputAnalyzerTest,TestHangingWindow)909 TEST_F(ThroughputAnalyzerTest, TestHangingWindow) {
910 static constexpr size_t kCwndSizeKilobytes = 10 * 1.5;
911 static constexpr size_t kCwndSizeBits = kCwndSizeKilobytes * 1000 * 8;
912
913 base::SimpleTestTickClock tick_clock;
914
915 TestNetworkQualityEstimator network_quality_estimator;
916 int64_t http_rtt_msec = 1000;
917 network_quality_estimator.SetStartTimeNullHttpRtt(
918 base::Milliseconds(http_rtt_msec));
919 std::map<std::string, std::string> variation_params;
920 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "1";
921 NetworkQualityEstimatorParams params(variation_params);
922
923 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
924 ¶ms, &tick_clock);
925
926 const struct {
927 size_t bits_received;
928 base::TimeDelta window_duration;
929 bool expected_hanging;
930 } tests[] = {
931 {100, base::Milliseconds(http_rtt_msec), true},
932 {kCwndSizeBits - 1, base::Milliseconds(http_rtt_msec), true},
933 {kCwndSizeBits + 1, base::Milliseconds(http_rtt_msec), false},
934 {2 * (kCwndSizeBits - 1), base::Milliseconds(http_rtt_msec * 2), true},
935 {2 * (kCwndSizeBits + 1), base::Milliseconds(http_rtt_msec * 2), false},
936 {kCwndSizeBits / 2 - 1, base::Milliseconds(http_rtt_msec / 2), true},
937 {kCwndSizeBits / 2 + 1, base::Milliseconds(http_rtt_msec / 2), false},
938 };
939
940 for (const auto& test : tests) {
941 base::HistogramTester histogram_tester;
942 EXPECT_EQ(test.expected_hanging,
943 throughput_analyzer.IsHangingWindow(test.bits_received,
944 test.window_duration));
945 }
946 }
947
948 } // namespace
949
950 } // namespace net::nqe
951