• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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                                                &params, 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                                                &params, 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                                                &params, 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                                                &params, 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           &not_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                                              &params, &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                                              &params, &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                                              &params, &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                                                &params, 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           &not_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                                                &params, 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                                              &params, 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                                              &params, &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