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(url::SchemeHostPort("http", "local.com", 80),
60 NetworkAnonymizationKey(), std::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(), url::SchemeHostPort("http", "example.com", 80),
119 NetworkAnonymizationKey()));
120 EXPECT_TRUE(nqe::internal::IsPrivateHostForTesting(
121 host_resolver.get(), url::SchemeHostPort("http", "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,
162 nqe::internal::IsPrivateHostForTesting(
163 context->host_resolver(), url::SchemeHostPort(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(url::SchemeHostPort(kUrl),
218 NetworkAnonymizationKey(), std::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(url::SchemeHostPort(kUrl),
225 kNetworkAnonymizationKey, std::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(), url::SchemeHostPort(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
281 // TestDelegates must be before URLRequests that point to them.
282 std::vector<TestDelegate> not_local_test_delegates(num_requests);
283 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
284 for (auto& delegate : not_local_test_delegates) {
285 // We don't care about completion, except for the first one (see below).
286 delegate.set_on_complete(base::DoNothing());
287 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
288 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &delegate,
289 TRAFFIC_ANNOTATION_FOR_TESTS));
290 request_not_local->Start();
291 requests_not_local.push_back(std::move(request_not_local));
292 }
293 not_local_test_delegates[0].RunUntilComplete();
294
295 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
296
297 for (const auto& request : requests_not_local) {
298 throughput_analyzer.NotifyStartTransaction(*request);
299 }
300
301 // Increment the bytes received count to emulate the bytes received for
302 // |request_local| and |requests_not_local|.
303 throughput_analyzer.IncrementBitsReceived(100 * 1000 * 8);
304
305 for (const auto& request : requests_not_local) {
306 throughput_analyzer.NotifyRequestCompleted(*request);
307 }
308 base::RunLoop().RunUntilIdle();
309
310 int expected_throughput_observations =
311 num_requests >= params.throughput_min_requests_in_flight() ? 1 : 0;
312 EXPECT_EQ(expected_throughput_observations,
313 throughput_analyzer.throughput_observations_received());
314 }
315 }
316
317 // Tests that the hanging requests are dropped from the |requests_|, and
318 // throughput observation window is ended.
TEST_F(ThroughputAnalyzerTest,TestHangingRequests)319 TEST_F(ThroughputAnalyzerTest, TestHangingRequests) {
320 static const struct {
321 int hanging_request_duration_http_rtt_multiplier;
322 base::TimeDelta http_rtt;
323 base::TimeDelta requests_hang_duration;
324 bool expect_throughput_observation;
325 } tests[] = {
326 {
327 // |requests_hang_duration| is less than 5 times the HTTP RTT.
328 // Requests should not be marked as hanging.
329 5,
330 base::Milliseconds(1000),
331 base::Milliseconds(3000),
332 true,
333 },
334 {
335 // |requests_hang_duration| is more than 5 times the HTTP RTT.
336 // Requests should be marked as hanging.
337 5,
338 base::Milliseconds(200),
339 base::Milliseconds(3000),
340 false,
341 },
342 {
343 // |requests_hang_duration| is less than
344 // |hanging_request_min_duration_msec|. Requests should not be marked
345 // as hanging.
346 1,
347 base::Milliseconds(100),
348 base::Milliseconds(100),
349 true,
350 },
351 {
352 // |requests_hang_duration| is more than
353 // |hanging_request_min_duration_msec|. Requests should be marked as
354 // hanging.
355 1,
356 base::Milliseconds(2000),
357 base::Milliseconds(3100),
358 false,
359 },
360 {
361 // |requests_hang_duration| is less than 5 times the HTTP RTT.
362 // Requests should not be marked as hanging.
363 5,
364 base::Seconds(2),
365 base::Seconds(1),
366 true,
367 },
368 {
369 // HTTP RTT is unavailable. Requests should not be marked as hanging.
370 5,
371 base::Seconds(-1),
372 base::Seconds(-1),
373 true,
374 },
375 };
376
377 for (const auto& test : tests) {
378 base::HistogramTester histogram_tester;
379 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
380 TestNetworkQualityEstimator network_quality_estimator;
381 if (test.http_rtt >= base::TimeDelta())
382 network_quality_estimator.SetStartTimeNullHttpRtt(test.http_rtt);
383 std::map<std::string, std::string> variation_params;
384 // Set the transport RTT multiplier to a large value so that the hanging
385 // request decision is made only on the basis of the HTTP RTT.
386 variation_params
387 ["hanging_request_http_rtt_upper_bound_transport_rtt_multiplier"] =
388 "10000";
389 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
390 variation_params["hanging_request_duration_http_rtt_multiplier"] =
391 base::NumberToString(test.hanging_request_duration_http_rtt_multiplier);
392
393 NetworkQualityEstimatorParams params(variation_params);
394
395 const size_t num_requests = params.throughput_min_requests_in_flight();
396 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
397 ¶ms, tick_clock);
398 auto context_builder = CreateTestURLRequestContextBuilder();
399 context_builder->set_host_resolver(CreateMockHostResolver());
400 auto context = context_builder->Build();
401
402 // TestDelegates must be before URLRequests that point to them.
403 std::vector<TestDelegate> not_local_test_delegates(num_requests);
404 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
405 for (size_t i = 0; i < num_requests; ++i) {
406 // We don't care about completion, except for the first one (see below).
407 not_local_test_delegates[i].set_on_complete(base::DoNothing());
408 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
409 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY,
410 ¬_local_test_delegates[i], TRAFFIC_ANNOTATION_FOR_TESTS));
411 request_not_local->Start();
412 requests_not_local.push_back(std::move(request_not_local));
413 }
414
415 not_local_test_delegates[0].RunUntilComplete();
416
417 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
418
419 for (size_t i = 0; i < num_requests; ++i) {
420 throughput_analyzer.NotifyStartTransaction(*requests_not_local.at(i));
421 }
422
423 // Increment the bytes received count to emulate the bytes received for
424 // |request_local| and |requests_not_local|.
425 throughput_analyzer.IncrementBitsReceived(100 * 1000 * 8);
426
427 // Mark in-flight requests as hanging requests (if specified in the test
428 // params).
429 if (test.requests_hang_duration >= base::TimeDelta())
430 base::PlatformThread::Sleep(test.requests_hang_duration);
431
432 EXPECT_EQ(num_requests, throughput_analyzer.CountActiveInFlightRequests());
433
434 for (size_t i = 0; i < num_requests; ++i) {
435 throughput_analyzer.NotifyRequestCompleted(*requests_not_local.at(i));
436 if (!test.expect_throughput_observation) {
437 // All in-flight requests should be marked as hanging, and thus should
438 // be deleted from the set of in-flight requests.
439 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
440 } else {
441 // One request should be deleted at one time.
442 EXPECT_EQ(requests_not_local.size() - i - 1,
443 throughput_analyzer.CountActiveInFlightRequests());
444 }
445 }
446
447 base::RunLoop().RunUntilIdle();
448
449 EXPECT_EQ(test.expect_throughput_observation,
450 throughput_analyzer.throughput_observations_received() > 0);
451 }
452 }
453
454 // Tests that the check for hanging requests is done at most once per second.
TEST_F(ThroughputAnalyzerTest,TestHangingRequestsCheckedOnlyPeriodically)455 TEST_F(ThroughputAnalyzerTest, TestHangingRequestsCheckedOnlyPeriodically) {
456 base::SimpleTestTickClock tick_clock;
457
458 TestNetworkQualityEstimator network_quality_estimator;
459 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(1));
460 std::map<std::string, std::string> variation_params;
461 variation_params["hanging_request_duration_http_rtt_multiplier"] = "5";
462 variation_params["hanging_request_min_duration_msec"] = "2000";
463 NetworkQualityEstimatorParams params(variation_params);
464
465 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
466 ¶ms, &tick_clock);
467
468 TestDelegate test_delegate;
469 auto context_builder = CreateTestURLRequestContextBuilder();
470 context_builder->set_host_resolver(CreateMockHostResolver());
471 auto context = context_builder->Build();
472 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
473
474 for (size_t i = 0; i < 2; ++i) {
475 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
476 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
477 TRAFFIC_ANNOTATION_FOR_TESTS));
478 request_not_local->Start();
479 requests_not_local.push_back(std::move(request_not_local));
480 }
481
482 std::unique_ptr<URLRequest> some_other_request(context->CreateRequest(
483 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
484 TRAFFIC_ANNOTATION_FOR_TESTS));
485
486 test_delegate.RunUntilComplete();
487
488 // First request starts at t=1. The second request starts at t=2. The first
489 // request would be marked as hanging at t=6, and the second request at t=7
490 // seconds.
491 for (size_t i = 0; i < 2; ++i) {
492 tick_clock.Advance(base::Milliseconds(1000));
493 throughput_analyzer.NotifyStartTransaction(*requests_not_local.at(i));
494 }
495
496 EXPECT_EQ(2u, throughput_analyzer.CountActiveInFlightRequests());
497 tick_clock.Advance(base::Milliseconds(3500));
498 // Current time is t = 5.5 seconds.
499 throughput_analyzer.EraseHangingRequests(*some_other_request);
500 EXPECT_EQ(2u, throughput_analyzer.CountActiveInFlightRequests());
501
502 tick_clock.Advance(base::Milliseconds(1000));
503 // Current time is t = 6.5 seconds. One request should be marked as hanging.
504 throughput_analyzer.EraseHangingRequests(*some_other_request);
505 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
506
507 // Current time is t = 6.5 seconds. Calling NotifyBytesRead again should not
508 // run the hanging request checker since the last check was at t=6.5 seconds.
509 throughput_analyzer.EraseHangingRequests(*some_other_request);
510 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
511
512 tick_clock.Advance(base::Milliseconds(600));
513 // Current time is t = 7.1 seconds. Calling NotifyBytesRead again should not
514 // run the hanging request checker since the last check was at t=6.5 seconds
515 // (less than 1 second ago).
516 throughput_analyzer.EraseHangingRequests(*some_other_request);
517 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
518
519 tick_clock.Advance(base::Milliseconds(400));
520 // Current time is t = 7.5 seconds. Calling NotifyBytesRead again should run
521 // the hanging request checker since the last check was at t=6.5 seconds (at
522 // least 1 second ago).
523 throughput_analyzer.EraseHangingRequests(*some_other_request);
524 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
525 }
526
527 // Tests that the last received time for a request is updated when data is
528 // received for that request.
TEST_F(ThroughputAnalyzerTest,TestLastReceivedTimeIsUpdated)529 TEST_F(ThroughputAnalyzerTest, TestLastReceivedTimeIsUpdated) {
530 base::SimpleTestTickClock tick_clock;
531
532 TestNetworkQualityEstimator network_quality_estimator;
533 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(1));
534 std::map<std::string, std::string> variation_params;
535 variation_params["hanging_request_duration_http_rtt_multiplier"] = "5";
536 variation_params["hanging_request_min_duration_msec"] = "2000";
537 NetworkQualityEstimatorParams params(variation_params);
538
539 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
540 ¶ms, &tick_clock);
541
542 TestDelegate test_delegate;
543 auto context_builder = CreateTestURLRequestContextBuilder();
544 context_builder->set_host_resolver(CreateMockHostResolver());
545 auto context = context_builder->Build();
546
547 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
548 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
549 TRAFFIC_ANNOTATION_FOR_TESTS));
550 request_not_local->Start();
551
552 test_delegate.RunUntilComplete();
553
554 std::unique_ptr<URLRequest> some_other_request(context->CreateRequest(
555 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
556 TRAFFIC_ANNOTATION_FOR_TESTS));
557
558 // Start time for the request is t=0 second. The request will be marked as
559 // hanging at t=5 seconds.
560 throughput_analyzer.NotifyStartTransaction(*request_not_local);
561
562 tick_clock.Advance(base::Milliseconds(4000));
563 // Current time is t=4.0 seconds.
564
565 throughput_analyzer.EraseHangingRequests(*some_other_request);
566 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
567
568 // The request will be marked as hanging at t=9 seconds.
569 throughput_analyzer.NotifyBytesRead(*request_not_local);
570 tick_clock.Advance(base::Milliseconds(4000));
571 // Current time is t=8 seconds.
572 throughput_analyzer.EraseHangingRequests(*some_other_request);
573 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
574
575 tick_clock.Advance(base::Milliseconds(2000));
576 // Current time is t=10 seconds.
577 throughput_analyzer.EraseHangingRequests(*some_other_request);
578 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
579 }
580
581 // Test that a request that has been hanging for a long time is deleted
582 // immediately when EraseHangingRequests is called even if the last hanging
583 // request check was done recently.
TEST_F(ThroughputAnalyzerTest,TestRequestDeletedImmediately)584 TEST_F(ThroughputAnalyzerTest, TestRequestDeletedImmediately) {
585 base::SimpleTestTickClock tick_clock;
586
587 TestNetworkQualityEstimator network_quality_estimator;
588 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(1));
589 std::map<std::string, std::string> variation_params;
590 variation_params["hanging_request_duration_http_rtt_multiplier"] = "2";
591 NetworkQualityEstimatorParams params(variation_params);
592
593 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
594 ¶ms, &tick_clock);
595
596 TestDelegate test_delegate;
597 auto context_builder = CreateTestURLRequestContextBuilder();
598 context_builder->set_host_resolver(CreateMockHostResolver());
599 auto context = context_builder->Build();
600
601 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
602 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
603 TRAFFIC_ANNOTATION_FOR_TESTS));
604 request_not_local->Start();
605
606 test_delegate.RunUntilComplete();
607
608 // Start time for the request is t=0 second. The request will be marked as
609 // hanging at t=2 seconds.
610 throughput_analyzer.NotifyStartTransaction(*request_not_local);
611 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
612
613 tick_clock.Advance(base::Milliseconds(2900));
614 // Current time is t=2.9 seconds.
615
616 throughput_analyzer.EraseHangingRequests(*request_not_local);
617 EXPECT_EQ(1u, throughput_analyzer.CountActiveInFlightRequests());
618
619 // |request_not_local| should be deleted since it has been idle for 2.4
620 // seconds.
621 tick_clock.Advance(base::Milliseconds(500));
622 throughput_analyzer.NotifyBytesRead(*request_not_local);
623 EXPECT_EQ(0u, throughput_analyzer.CountActiveInFlightRequests());
624 }
625
626 #if BUILDFLAG(IS_IOS)
627 // Flaky on iOS: crbug.com/672917.
628 #define MAYBE_TestThroughputWithMultipleRequestsOverlap \
629 DISABLED_TestThroughputWithMultipleRequestsOverlap
630 #else
631 #define MAYBE_TestThroughputWithMultipleRequestsOverlap \
632 TestThroughputWithMultipleRequestsOverlap
633 #endif
634 // Tests if the throughput observation is taken correctly when local and network
635 // requests overlap.
TEST_F(ThroughputAnalyzerTest,MAYBE_TestThroughputWithMultipleRequestsOverlap)636 TEST_F(ThroughputAnalyzerTest,
637 MAYBE_TestThroughputWithMultipleRequestsOverlap) {
638 static const struct {
639 bool start_local_request;
640 bool local_request_completes_first;
641 bool expect_throughput_observation;
642 } tests[] = {
643 {
644 false, false, true,
645 },
646 {
647 true, false, false,
648 },
649 {
650 true, true, true,
651 },
652 };
653
654 for (const auto& test : tests) {
655 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
656 TestNetworkQualityEstimator network_quality_estimator;
657 // Localhost requests are not allowed for estimation purposes.
658 std::map<std::string, std::string> variation_params;
659 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
660 NetworkQualityEstimatorParams params(variation_params);
661
662 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
663 ¶ms, tick_clock);
664
665 TestDelegate local_delegate;
666 local_delegate.set_on_complete(base::DoNothing());
667 auto context_builder = CreateTestURLRequestContextBuilder();
668 context_builder->set_host_resolver(CreateMockHostResolver());
669 auto context = context_builder->Build();
670 std::unique_ptr<URLRequest> request_local;
671
672 // TestDelegates must be before URLRequests that point to them.
673 std::vector<TestDelegate> not_local_test_delegates(
674 params.throughput_min_requests_in_flight());
675 std::vector<std::unique_ptr<URLRequest>> requests_not_local;
676 for (size_t i = 0; i < params.throughput_min_requests_in_flight(); ++i) {
677 // We don't care about completion, except for the first one (see below).
678 not_local_test_delegates[i].set_on_complete(base::DoNothing());
679 std::unique_ptr<URLRequest> request_not_local(context->CreateRequest(
680 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY,
681 ¬_local_test_delegates[i], TRAFFIC_ANNOTATION_FOR_TESTS));
682 request_not_local->Start();
683 requests_not_local.push_back(std::move(request_not_local));
684 }
685
686 if (test.start_local_request) {
687 request_local = context->CreateRequest(GURL("http://127.0.0.1/echo.html"),
688 DEFAULT_PRIORITY, &local_delegate,
689 TRAFFIC_ANNOTATION_FOR_TESTS);
690 request_local->Start();
691 }
692
693 // Wait until the first not-local request completes.
694 not_local_test_delegates[0].RunUntilComplete();
695
696 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
697
698 // If |test.start_local_request| is true, then |request_local| starts
699 // before |request_not_local|, and ends after |request_not_local|. Thus,
700 // network quality estimator should not get a chance to record throughput
701 // observation from |request_not_local| because of ongoing local request
702 // at all times.
703 if (test.start_local_request)
704 throughput_analyzer.NotifyStartTransaction(*request_local);
705
706 for (const auto& request : requests_not_local) {
707 throughput_analyzer.NotifyStartTransaction(*request);
708 }
709
710 if (test.local_request_completes_first) {
711 ASSERT_TRUE(test.start_local_request);
712 throughput_analyzer.NotifyRequestCompleted(*request_local);
713 }
714
715 // Increment the bytes received count to emulate the bytes received for
716 // |request_local| and |requests_not_local|.
717 throughput_analyzer.IncrementBitsReceived(100 * 1000 * 8);
718
719 for (const auto& request : requests_not_local) {
720 throughput_analyzer.NotifyRequestCompleted(*request);
721 }
722 if (test.start_local_request && !test.local_request_completes_first)
723 throughput_analyzer.NotifyRequestCompleted(*request_local);
724
725 // Pump the message loop to let analyzer tasks get processed.
726 base::RunLoop().RunUntilIdle();
727
728 int expected_throughput_observations =
729 test.expect_throughput_observation ? 1 : 0;
730 EXPECT_EQ(expected_throughput_observations,
731 throughput_analyzer.throughput_observations_received());
732 }
733 }
734
735 // Tests if the throughput observation is taken correctly when two network
736 // requests overlap.
TEST_F(ThroughputAnalyzerTest,TestThroughputWithNetworkRequestsOverlap)737 TEST_F(ThroughputAnalyzerTest, TestThroughputWithNetworkRequestsOverlap) {
738 static const struct {
739 size_t throughput_min_requests_in_flight;
740 size_t number_requests_in_flight;
741 int64_t increment_bits;
742 bool expect_throughput_observation;
743 } tests[] = {
744 {
745 1, 2, 100 * 1000 * 8, true,
746 },
747 {
748 3, 1, 100 * 1000 * 8, false,
749 },
750 {
751 3, 2, 100 * 1000 * 8, false,
752 },
753 {
754 3, 3, 100 * 1000 * 8, true,
755 },
756 {
757 3, 4, 100 * 1000 * 8, true,
758 },
759 {
760 1, 2, 1, false,
761 },
762 };
763
764 for (const auto& test : tests) {
765 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
766 TestNetworkQualityEstimator network_quality_estimator;
767 // Localhost requests are not allowed for estimation purposes.
768 std::map<std::string, std::string> variation_params;
769 variation_params["throughput_min_requests_in_flight"] =
770 base::NumberToString(test.throughput_min_requests_in_flight);
771 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
772 NetworkQualityEstimatorParams params(variation_params);
773 // Set HTTP RTT to a large value so that the throughput observation window
774 // is not detected as hanging. In practice, this would be provided by
775 // |network_quality_estimator| based on the recent observations.
776 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(100));
777
778 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
779 ¶ms, tick_clock);
780 auto context_builder = CreateTestURLRequestContextBuilder();
781 context_builder->set_host_resolver(CreateMockHostResolver());
782 auto context = context_builder->Build();
783
784 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
785
786 // TestDelegates must be before URLRequests that point to them.
787 std::vector<TestDelegate> in_flight_test_delegates(
788 test.number_requests_in_flight);
789 std::vector<std::unique_ptr<URLRequest>> requests_in_flight;
790 for (size_t i = 0; i < test.number_requests_in_flight; ++i) {
791 // We don't care about completion, except for the first one (see below).
792 in_flight_test_delegates[i].set_on_complete(base::DoNothing());
793 std::unique_ptr<URLRequest> request_network_1 = context->CreateRequest(
794 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY,
795 &in_flight_test_delegates[i], TRAFFIC_ANNOTATION_FOR_TESTS);
796 requests_in_flight.push_back(std::move(request_network_1));
797 requests_in_flight.back()->Start();
798 }
799
800 in_flight_test_delegates[0].RunUntilComplete();
801
802 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
803
804 for (size_t i = 0; i < test.number_requests_in_flight; ++i) {
805 URLRequest* request = requests_in_flight.at(i).get();
806 throughput_analyzer.NotifyStartTransaction(*request);
807 }
808
809 // Increment the bytes received count to emulate the bytes received for
810 // |request_network_1| and |request_network_2|.
811 throughput_analyzer.IncrementBitsReceived(test.increment_bits);
812
813 for (size_t i = 0; i < test.number_requests_in_flight; ++i) {
814 URLRequest* request = requests_in_flight.at(i).get();
815 throughput_analyzer.NotifyRequestCompleted(*request);
816 }
817
818 base::RunLoop().RunUntilIdle();
819
820 // Only one observation should be taken since two requests overlap.
821 if (test.expect_throughput_observation) {
822 EXPECT_EQ(1, throughput_analyzer.throughput_observations_received());
823 } else {
824 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
825 }
826 }
827 }
828
829 // Tests if the throughput observation is taken correctly when the start and end
830 // of network requests overlap, and the minimum number of in flight requests
831 // when taking an observation is more than 1.
TEST_F(ThroughputAnalyzerTest,TestThroughputWithMultipleNetworkRequests)832 TEST_F(ThroughputAnalyzerTest, TestThroughputWithMultipleNetworkRequests) {
833 const base::test::ScopedRunLoopTimeout increased_run_timeout(
834 FROM_HERE, TestTimeouts::action_max_timeout());
835
836 const base::TickClock* tick_clock = base::DefaultTickClock::GetInstance();
837 TestNetworkQualityEstimator network_quality_estimator;
838 std::map<std::string, std::string> variation_params;
839 variation_params["throughput_min_requests_in_flight"] = "3";
840 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "-1";
841 NetworkQualityEstimatorParams params(variation_params);
842 // Set HTTP RTT to a large value so that the throughput observation window
843 // is not detected as hanging. In practice, this would be provided by
844 // |network_quality_estimator| based on the recent observations.
845 network_quality_estimator.SetStartTimeNullHttpRtt(base::Seconds(100));
846
847 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
848 ¶ms, tick_clock);
849 TestDelegate test_delegate;
850 auto context_builder = CreateTestURLRequestContextBuilder();
851 context_builder->set_host_resolver(CreateMockHostResolver());
852 auto context = context_builder->Build();
853
854 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
855
856 std::unique_ptr<URLRequest> request_1 = context->CreateRequest(
857 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
858 TRAFFIC_ANNOTATION_FOR_TESTS);
859 std::unique_ptr<URLRequest> request_2 = context->CreateRequest(
860 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
861 TRAFFIC_ANNOTATION_FOR_TESTS);
862 std::unique_ptr<URLRequest> request_3 = context->CreateRequest(
863 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
864 TRAFFIC_ANNOTATION_FOR_TESTS);
865 std::unique_ptr<URLRequest> request_4 = context->CreateRequest(
866 GURL("http://example.com/echo.html"), DEFAULT_PRIORITY, &test_delegate,
867 TRAFFIC_ANNOTATION_FOR_TESTS);
868
869 request_1->Start();
870 request_2->Start();
871 request_3->Start();
872 request_4->Start();
873
874 // We dispatched four requests, so wait for four completions.
875 for (int i = 0; i < 4; ++i)
876 test_delegate.RunUntilComplete();
877
878 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
879
880 throughput_analyzer.NotifyStartTransaction(*(request_1.get()));
881 throughput_analyzer.NotifyStartTransaction(*(request_2.get()));
882
883 const size_t increment_bits = 100 * 1000 * 8;
884
885 // Increment the bytes received count to emulate the bytes received for
886 // |request_1| and |request_2|.
887 throughput_analyzer.IncrementBitsReceived(increment_bits);
888
889 throughput_analyzer.NotifyRequestCompleted(*(request_1.get()));
890 base::RunLoop().RunUntilIdle();
891
892 // No observation should be taken since only 1 request is in flight.
893 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
894
895 throughput_analyzer.NotifyStartTransaction(*(request_3.get()));
896 throughput_analyzer.NotifyStartTransaction(*(request_4.get()));
897 EXPECT_EQ(0, throughput_analyzer.throughput_observations_received());
898
899 // 3 requests are in flight which is at least as many as the minimum number of
900 // in flight requests required. An observation should be taken.
901 throughput_analyzer.IncrementBitsReceived(increment_bits);
902
903 // Only one observation should be taken since two requests overlap.
904 throughput_analyzer.NotifyRequestCompleted(*(request_2.get()));
905 base::RunLoop().RunUntilIdle();
906
907 EXPECT_EQ(1, throughput_analyzer.throughput_observations_received());
908 throughput_analyzer.NotifyRequestCompleted(*(request_3.get()));
909 throughput_analyzer.NotifyRequestCompleted(*(request_4.get()));
910 EXPECT_EQ(1, throughput_analyzer.throughput_observations_received());
911 }
912
TEST_F(ThroughputAnalyzerTest,TestHangingWindow)913 TEST_F(ThroughputAnalyzerTest, TestHangingWindow) {
914 static constexpr size_t kCwndSizeKilobytes = 10 * 1.5;
915 static constexpr size_t kCwndSizeBits = kCwndSizeKilobytes * 1000 * 8;
916
917 base::SimpleTestTickClock tick_clock;
918
919 TestNetworkQualityEstimator network_quality_estimator;
920 int64_t http_rtt_msec = 1000;
921 network_quality_estimator.SetStartTimeNullHttpRtt(
922 base::Milliseconds(http_rtt_msec));
923 std::map<std::string, std::string> variation_params;
924 variation_params["throughput_hanging_requests_cwnd_size_multiplier"] = "1";
925 NetworkQualityEstimatorParams params(variation_params);
926
927 TestThroughputAnalyzer throughput_analyzer(&network_quality_estimator,
928 ¶ms, &tick_clock);
929
930 const struct {
931 size_t bits_received;
932 base::TimeDelta window_duration;
933 bool expected_hanging;
934 } tests[] = {
935 {100, base::Milliseconds(http_rtt_msec), true},
936 {kCwndSizeBits - 1, base::Milliseconds(http_rtt_msec), true},
937 {kCwndSizeBits + 1, base::Milliseconds(http_rtt_msec), false},
938 {2 * (kCwndSizeBits - 1), base::Milliseconds(http_rtt_msec * 2), true},
939 {2 * (kCwndSizeBits + 1), base::Milliseconds(http_rtt_msec * 2), false},
940 {kCwndSizeBits / 2 - 1, base::Milliseconds(http_rtt_msec / 2), true},
941 {kCwndSizeBits / 2 + 1, base::Milliseconds(http_rtt_msec / 2), false},
942 };
943
944 for (const auto& test : tests) {
945 base::HistogramTester histogram_tester;
946 EXPECT_EQ(test.expected_hanging,
947 throughput_analyzer.IsHangingWindow(test.bits_received,
948 test.window_duration));
949 }
950 }
951
952 } // namespace
953
954 } // namespace net::nqe
955