xref: /aosp_15_r20/external/cronet/net/nqe/throughput_analyzer_unittest.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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                                                &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,
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                                                &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(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                                                &params, 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                                                &params, 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           &not_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                                              &params, &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                                              &params, &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                                              &params, &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                                                &params, 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           &not_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                                                &params, 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                                              &params, 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                                              &params, &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