1 // Copyright 2012 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 // The tests in this file attempt to verify the following through simulation:
6 // a) That a server experiencing overload will actually benefit from the
7 // anti-DDoS throttling logic, i.e. that its traffic spike will subside
8 // and be distributed over a longer period of time;
9 // b) That "well-behaved" clients of a server under DDoS attack actually
10 // benefit from the anti-DDoS throttling logic; and
11 // c) That the approximate increase in "perceived downtime" introduced by
12 // anti-DDoS throttling for various different actual downtimes is what
13 // we expect it to be.
14
15 #include <cmath>
16 #include <limits>
17 #include <memory>
18 #include <vector>
19
20 #include "base/environment.h"
21 #include "base/memory/raw_ptr.h"
22 #include "base/rand_util.h"
23 #include "base/test/task_environment.h"
24 #include "base/time/time.h"
25 #include "net/base/request_priority.h"
26 #include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
27 #include "net/url_request/url_request.h"
28 #include "net/url_request/url_request_context.h"
29 #include "net/url_request/url_request_context_builder.h"
30 #include "net/url_request/url_request_test_util.h"
31 #include "net/url_request/url_request_throttler_manager.h"
32 #include "net/url_request/url_request_throttler_test_support.h"
33 #include "testing/gtest/include/gtest/gtest.h"
34
35 using base::TimeTicks;
36
37 namespace net {
38 namespace {
39
40 // Set this variable in your environment if you want to see verbose results
41 // of the simulation tests.
42 const char kShowSimulationVariableName[] = "SHOW_SIMULATION_RESULTS";
43
44 // Prints output only if a given environment variable is set. We use this
45 // to not print any output for human evaluation when the test is run without
46 // supervision.
VerboseOut(const char * format,...)47 void VerboseOut(const char* format, ...) {
48 static bool have_checked_environment = false;
49 static bool should_print = false;
50 if (!have_checked_environment) {
51 have_checked_environment = true;
52 std::unique_ptr<base::Environment> env(base::Environment::Create());
53 if (env->HasVar(kShowSimulationVariableName))
54 should_print = true;
55 }
56
57 if (should_print) {
58 va_list arglist;
59 va_start(arglist, format);
60 vprintf(format, arglist);
61 va_end(arglist);
62 }
63 }
64
65 // A simple two-phase discrete time simulation. Actors are added in the order
66 // they should take action at every tick of the clock. Ticks of the clock
67 // are two-phase:
68 // - Phase 1 advances every actor's time to a new absolute time.
69 // - Phase 2 asks each actor to perform their action.
70 class DiscreteTimeSimulation {
71 public:
72 class Actor {
73 public:
74 virtual ~Actor() = default;
75 virtual void AdvanceTime(const TimeTicks& absolute_time) = 0;
76 virtual void PerformAction() = 0;
77 };
78
79 DiscreteTimeSimulation() = default;
80
81 DiscreteTimeSimulation(const DiscreteTimeSimulation&) = delete;
82 DiscreteTimeSimulation& operator=(const DiscreteTimeSimulation&) = delete;
83
84 // Adds an |actor| to the simulation. The client of the simulation maintains
85 // ownership of |actor| and must ensure its lifetime exceeds that of the
86 // simulation. Actors should be added in the order you wish for them to
87 // act at each tick of the simulation.
AddActor(Actor * actor)88 void AddActor(Actor* actor) {
89 actors_.push_back(actor);
90 }
91
92 // Runs the simulation for, pretending |time_between_ticks| passes from one
93 // tick to the next. The start time will be the current real time. The
94 // simulation will stop when the simulated duration is equal to or greater
95 // than |maximum_simulated_duration|.
RunSimulation(const base::TimeDelta & maximum_simulated_duration,const base::TimeDelta & time_between_ticks)96 void RunSimulation(const base::TimeDelta& maximum_simulated_duration,
97 const base::TimeDelta& time_between_ticks) {
98 TimeTicks start_time = TimeTicks();
99 TimeTicks now = start_time;
100 while ((now - start_time) <= maximum_simulated_duration) {
101 for (auto* actor : actors_) {
102 actor->AdvanceTime(now);
103 }
104
105 for (auto* actor : actors_) {
106 actor->PerformAction();
107 }
108
109 now += time_between_ticks;
110 }
111 }
112
113 private:
114 std::vector<Actor*> actors_;
115 };
116
117 // Represents a web server in a simulation of a server under attack by
118 // a lot of clients. Must be added to the simulation's list of actors
119 // after all |Requester| objects.
120 class Server : public DiscreteTimeSimulation::Actor {
121 public:
Server(int max_queries_per_tick,double request_drop_ratio)122 Server(int max_queries_per_tick, double request_drop_ratio)
123 : max_queries_per_tick_(max_queries_per_tick),
124 request_drop_ratio_(request_drop_ratio),
125 context_(CreateTestURLRequestContextBuilder()->Build()),
126 mock_request_(context_->CreateRequest(GURL(),
127 DEFAULT_PRIORITY,
128 nullptr,
129 TRAFFIC_ANNOTATION_FOR_TESTS)) {}
130
131 Server(const Server&) = delete;
132 Server& operator=(const Server&) = delete;
133
SetDowntime(const TimeTicks & start_time,const base::TimeDelta & duration)134 void SetDowntime(const TimeTicks& start_time,
135 const base::TimeDelta& duration) {
136 start_downtime_ = start_time;
137 end_downtime_ = start_time + duration;
138 }
139
AdvanceTime(const TimeTicks & absolute_time)140 void AdvanceTime(const TimeTicks& absolute_time) override {
141 now_ = absolute_time;
142 }
143
PerformAction()144 void PerformAction() override {
145 // We are inserted at the end of the actor's list, so all Requester
146 // instances have already done their bit.
147 if (num_current_tick_queries_ > max_experienced_queries_per_tick_)
148 max_experienced_queries_per_tick_ = num_current_tick_queries_;
149
150 if (num_current_tick_queries_ > max_queries_per_tick_) {
151 // We pretend the server fails for the next several ticks after it
152 // gets overloaded.
153 num_overloaded_ticks_remaining_ = 5;
154 ++num_overloaded_ticks_;
155 } else if (num_overloaded_ticks_remaining_ > 0) {
156 --num_overloaded_ticks_remaining_;
157 }
158
159 requests_per_tick_.push_back(num_current_tick_queries_);
160 num_current_tick_queries_ = 0;
161 }
162
163 // This is called by Requester. It returns the response code from
164 // the server.
HandleRequest()165 int HandleRequest() {
166 ++num_current_tick_queries_;
167 if (!start_downtime_.is_null() &&
168 start_downtime_ < now_ && now_ < end_downtime_) {
169 // For the simulation measuring the increase in perceived
170 // downtime, it might be interesting to count separately the
171 // queries seen by the server (assuming a front-end reverse proxy
172 // is what actually serves up the 503s in this case) so that we could
173 // visualize the traffic spike seen by the server when it comes up,
174 // which would in many situations be ameliorated by the anti-DDoS
175 // throttling.
176 return 503;
177 }
178
179 if ((num_overloaded_ticks_remaining_ > 0 ||
180 num_current_tick_queries_ > max_queries_per_tick_) &&
181 base::RandDouble() < request_drop_ratio_) {
182 return 503;
183 }
184
185 return 200;
186 }
187
num_overloaded_ticks() const188 int num_overloaded_ticks() const {
189 return num_overloaded_ticks_;
190 }
191
max_experienced_queries_per_tick() const192 int max_experienced_queries_per_tick() const {
193 return max_experienced_queries_per_tick_;
194 }
195
mock_request() const196 const URLRequest& mock_request() const {
197 return *mock_request_.get();
198 }
199
VisualizeASCII(int terminal_width)200 std::string VisualizeASCII(int terminal_width) {
201 // Account for | characters we place at left of graph.
202 terminal_width -= 1;
203
204 VerboseOut("Overloaded for %d of %d ticks.\n",
205 num_overloaded_ticks_, requests_per_tick_.size());
206 VerboseOut("Got maximum of %d requests in a tick.\n\n",
207 max_experienced_queries_per_tick_);
208
209 VerboseOut("Traffic graph:\n\n");
210
211 // Printing the graph like this is a bit overkill, but was very useful
212 // while developing the various simulations to see if they were testing
213 // the corner cases we want to simulate.
214
215 // Find the smallest number of whole ticks we need to group into a
216 // column that will let all ticks fit into the column width we have.
217 int num_ticks = requests_per_tick_.size();
218 double ticks_per_column_exact =
219 static_cast<double>(num_ticks) / static_cast<double>(terminal_width);
220 int ticks_per_column = std::ceil(ticks_per_column_exact);
221 DCHECK_GE(ticks_per_column * terminal_width, num_ticks);
222
223 // Sum up the column values.
224 int num_columns = num_ticks / ticks_per_column;
225 if (num_ticks % ticks_per_column)
226 ++num_columns;
227 DCHECK_LE(num_columns, terminal_width);
228 auto columns = std::make_unique<int[]>(num_columns);
229 for (int tx = 0; tx < num_ticks; ++tx) {
230 int cx = tx / ticks_per_column;
231 if (tx % ticks_per_column == 0)
232 columns[cx] = 0;
233 columns[cx] += requests_per_tick_[tx];
234 }
235
236 // Find the lowest integer divisor that will let the column values
237 // be represented in a graph of maximum height 50.
238 int max_value = 0;
239 for (int cx = 0; cx < num_columns; ++cx)
240 max_value = std::max(max_value, columns[cx]);
241 const int kNumRows = 50;
242 double row_divisor_exact = max_value / static_cast<double>(kNumRows);
243 int row_divisor = std::ceil(row_divisor_exact);
244 DCHECK_GE(row_divisor * kNumRows, max_value);
245
246 // To show the overload line, we calculate the appropriate value.
247 int overload_value = max_queries_per_tick_ * ticks_per_column;
248
249 // When num_ticks is not a whole multiple of ticks_per_column, the last
250 // column includes fewer ticks than the others. In this case, don't
251 // print it so that we don't show an inconsistent value.
252 int num_printed_columns = num_columns;
253 if (num_ticks % ticks_per_column)
254 --num_printed_columns;
255
256 // This is a top-to-bottom traversal of rows, left-to-right per row.
257 std::string output;
258 for (int rx = 0; rx < kNumRows; ++rx) {
259 int range_min = (kNumRows - rx) * row_divisor;
260 int range_max = range_min + row_divisor;
261 if (range_min == 0)
262 range_min = -1; // Make 0 values fit in the bottom range.
263 output.append("|");
264 for (int cx = 0; cx < num_printed_columns; ++cx) {
265 char block = ' ';
266 // Show the overload line.
267 if (range_min < overload_value && overload_value <= range_max)
268 block = '-';
269
270 // Preferentially, show the graph line.
271 if (range_min < columns[cx] && columns[cx] <= range_max)
272 block = '#';
273
274 output.append(1, block);
275 }
276 output.append("\n");
277 }
278 output.append("|");
279 output.append(num_printed_columns, '=');
280
281 return output;
282 }
283
context() const284 const URLRequestContext& context() const { return *context_; }
285
286 private:
287 TimeTicks now_;
288 TimeTicks start_downtime_; // Can be 0 to say "no downtime".
289 TimeTicks end_downtime_;
290 const int max_queries_per_tick_;
291 const double request_drop_ratio_; // Ratio of requests to 503 when failing.
292 int num_overloaded_ticks_remaining_ = 0;
293 int num_current_tick_queries_ = 0;
294 int num_overloaded_ticks_ = 0;
295 int max_experienced_queries_per_tick_ = 0;
296 std::vector<int> requests_per_tick_;
297
298 std::unique_ptr<URLRequestContext> context_;
299 std::unique_ptr<URLRequest> mock_request_;
300 };
301
302 // Mock throttler entry used by Requester class.
303 class MockURLRequestThrottlerEntry : public URLRequestThrottlerEntry {
304 public:
MockURLRequestThrottlerEntry(URLRequestThrottlerManager * manager)305 explicit MockURLRequestThrottlerEntry(URLRequestThrottlerManager* manager)
306 : URLRequestThrottlerEntry(manager, std::string()),
307 backoff_entry_(&backoff_policy_, &fake_clock_) {}
308
GetBackoffEntry() const309 const BackoffEntry* GetBackoffEntry() const override {
310 return &backoff_entry_;
311 }
312
GetBackoffEntry()313 BackoffEntry* GetBackoffEntry() override { return &backoff_entry_; }
314
ImplGetTimeNow() const315 TimeTicks ImplGetTimeNow() const override { return fake_clock_.NowTicks(); }
316
SetFakeNow(const TimeTicks & fake_time)317 void SetFakeNow(const TimeTicks& fake_time) {
318 fake_clock_.set_now(fake_time);
319 }
320
321 protected:
322 ~MockURLRequestThrottlerEntry() override = default;
323
324 private:
325 mutable TestTickClock fake_clock_;
326 BackoffEntry backoff_entry_;
327 };
328
329 // Registry of results for a class of |Requester| objects (e.g. attackers vs.
330 // regular clients).
331 class RequesterResults {
332 public:
333 RequesterResults() = default;
334
AddSuccess()335 void AddSuccess() {
336 ++num_attempts_;
337 ++num_successful_;
338 }
339
AddFailure()340 void AddFailure() {
341 ++num_attempts_;
342 ++num_failed_;
343 }
344
AddBlocked()345 void AddBlocked() {
346 ++num_attempts_;
347 ++num_blocked_;
348 }
349
num_attempts() const350 int num_attempts() const { return num_attempts_; }
num_successful() const351 int num_successful() const { return num_successful_; }
num_failed() const352 int num_failed() const { return num_failed_; }
num_blocked() const353 int num_blocked() const { return num_blocked_; }
354
GetBlockedRatio()355 double GetBlockedRatio() {
356 DCHECK(num_attempts_);
357 return static_cast<double>(num_blocked_) /
358 static_cast<double>(num_attempts_);
359 }
360
GetSuccessRatio()361 double GetSuccessRatio() {
362 DCHECK(num_attempts_);
363 return static_cast<double>(num_successful_) /
364 static_cast<double>(num_attempts_);
365 }
366
PrintResults(const char * class_description)367 void PrintResults(const char* class_description) {
368 if (num_attempts_ == 0) {
369 VerboseOut("No data for %s\n", class_description);
370 return;
371 }
372
373 VerboseOut("Requester results for %s\n", class_description);
374 VerboseOut(" %d attempts\n", num_attempts_);
375 VerboseOut(" %d successes\n", num_successful_);
376 VerboseOut(" %d 5xx responses\n", num_failed_);
377 VerboseOut(" %d requests blocked\n", num_blocked_);
378 VerboseOut(" %.2f success ratio\n", GetSuccessRatio());
379 VerboseOut(" %.2f blocked ratio\n", GetBlockedRatio());
380 VerboseOut("\n");
381 }
382
383 private:
384 int num_attempts_ = 0;
385 int num_successful_ = 0;
386 int num_failed_ = 0;
387 int num_blocked_ = 0;
388 };
389
390 // Represents an Requester in a simulated DDoS situation, that periodically
391 // requests a specific resource.
392 class Requester : public DiscreteTimeSimulation::Actor {
393 public:
Requester(MockURLRequestThrottlerEntry * throttler_entry,const base::TimeDelta & time_between_requests,Server * server,RequesterResults * results)394 Requester(MockURLRequestThrottlerEntry* throttler_entry,
395 const base::TimeDelta& time_between_requests,
396 Server* server,
397 RequesterResults* results)
398 : throttler_entry_(throttler_entry),
399 time_between_requests_(time_between_requests),
400 server_(server),
401 results_(results) {
402 DCHECK(server_);
403 }
404
405 Requester(const Requester&) = delete;
406 Requester& operator=(const Requester&) = delete;
407
AdvanceTime(const TimeTicks & absolute_time)408 void AdvanceTime(const TimeTicks& absolute_time) override {
409 if (time_of_last_success_.is_null())
410 time_of_last_success_ = absolute_time;
411
412 throttler_entry_->SetFakeNow(absolute_time);
413 }
414
PerformAction()415 void PerformAction() override {
416 const auto current_jitter =
417 request_jitter_.is_zero()
418 ? base::TimeDelta()
419 : base::RandTimeDelta(-request_jitter_, request_jitter_);
420 const base::TimeDelta effective_delay =
421 time_between_requests_ + current_jitter;
422
423 if (throttler_entry_->ImplGetTimeNow() - time_of_last_attempt_ >
424 effective_delay) {
425 if (!throttler_entry_->ShouldRejectRequest(server_->mock_request())) {
426 int status_code = server_->HandleRequest();
427 throttler_entry_->UpdateWithResponse(status_code);
428
429 if (status_code == 200) {
430 if (results_)
431 results_->AddSuccess();
432
433 if (last_attempt_was_failure_) {
434 last_downtime_duration_ =
435 throttler_entry_->ImplGetTimeNow() - time_of_last_success_;
436 }
437
438 time_of_last_success_ = throttler_entry_->ImplGetTimeNow();
439 } else if (results_) {
440 results_->AddFailure();
441 }
442 last_attempt_was_failure_ = status_code != 200;
443 } else {
444 if (results_)
445 results_->AddBlocked();
446 last_attempt_was_failure_ = true;
447 }
448
449 time_of_last_attempt_ = throttler_entry_->ImplGetTimeNow();
450 }
451 }
452
453 // Adds a delay until the first request, equal to a uniformly distributed
454 // value between now and now + max_delay.
SetStartupJitter(const base::TimeDelta & max_delay)455 void SetStartupJitter(const base::TimeDelta& max_delay) {
456 const auto delay = max_delay.is_zero() ? base::TimeDelta()
457 : base::RandTimeDeltaUpTo(max_delay);
458 time_of_last_attempt_ = TimeTicks() + delay - time_between_requests_;
459 }
460
SetRequestJitter(const base::TimeDelta & request_jitter)461 void SetRequestJitter(const base::TimeDelta& request_jitter) {
462 request_jitter_ = request_jitter;
463 }
464
last_downtime_duration() const465 base::TimeDelta last_downtime_duration() const {
466 return last_downtime_duration_;
467 }
468
469 private:
470 scoped_refptr<MockURLRequestThrottlerEntry> throttler_entry_;
471 const base::TimeDelta time_between_requests_;
472 base::TimeDelta request_jitter_;
473 TimeTicks time_of_last_attempt_;
474 TimeTicks time_of_last_success_;
475 bool last_attempt_was_failure_ = false;
476 base::TimeDelta last_downtime_duration_;
477 const raw_ptr<Server> server_;
478 const raw_ptr<RequesterResults> results_; // May be NULL.
479 };
480
SimulateAttack(Server * server,RequesterResults * attacker_results,RequesterResults * client_results,bool enable_throttling)481 void SimulateAttack(Server* server,
482 RequesterResults* attacker_results,
483 RequesterResults* client_results,
484 bool enable_throttling) {
485 const size_t kNumAttackers = 50;
486 const size_t kNumClients = 50;
487 DiscreteTimeSimulation simulation;
488 URLRequestThrottlerManager manager;
489 std::vector<std::unique_ptr<Requester>> requesters;
490 for (size_t i = 0; i < kNumAttackers; ++i) {
491 // Use a tiny time_between_requests so the attackers will ping the
492 // server at every tick of the simulation.
493 auto throttler_entry =
494 base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
495 if (!enable_throttling)
496 throttler_entry->DisableBackoffThrottling();
497
498 auto attacker = std::make_unique<Requester>(
499 throttler_entry.get(), base::Milliseconds(1), server, attacker_results);
500 attacker->SetStartupJitter(base::Seconds(120));
501 simulation.AddActor(attacker.get());
502 requesters.push_back(std::move(attacker));
503 }
504 for (size_t i = 0; i < kNumClients; ++i) {
505 // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
506 auto throttler_entry =
507 base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
508 if (!enable_throttling)
509 throttler_entry->DisableBackoffThrottling();
510
511 auto client = std::make_unique<Requester>(
512 throttler_entry.get(), base::Minutes(2), server, client_results);
513 client->SetStartupJitter(base::Seconds(120));
514 client->SetRequestJitter(base::Minutes(1));
515 simulation.AddActor(client.get());
516 requesters.push_back(std::move(client));
517 }
518 simulation.AddActor(server);
519
520 simulation.RunSimulation(base::Minutes(6), base::Seconds(1));
521 }
522
TEST(URLRequestThrottlerSimulation,HelpsInAttack)523 TEST(URLRequestThrottlerSimulation, HelpsInAttack) {
524 base::test::TaskEnvironment task_environment;
525
526 Server unprotected_server(30, 1.0);
527 RequesterResults unprotected_attacker_results;
528 RequesterResults unprotected_client_results;
529 Server protected_server(30, 1.0);
530 RequesterResults protected_attacker_results;
531 RequesterResults protected_client_results;
532 SimulateAttack(&unprotected_server,
533 &unprotected_attacker_results,
534 &unprotected_client_results,
535 false);
536 SimulateAttack(&protected_server,
537 &protected_attacker_results,
538 &protected_client_results,
539 true);
540
541 // These assert that the DDoS protection actually benefits the
542 // server. Manual inspection of the traffic graphs will show this
543 // even more clearly.
544 EXPECT_GT(unprotected_server.num_overloaded_ticks(),
545 protected_server.num_overloaded_ticks());
546 EXPECT_GT(unprotected_server.max_experienced_queries_per_tick(),
547 protected_server.max_experienced_queries_per_tick());
548
549 // These assert that the DDoS protection actually benefits non-malicious
550 // (and non-degenerate/accidentally DDoSing) users.
551 EXPECT_LT(protected_client_results.GetBlockedRatio(),
552 protected_attacker_results.GetBlockedRatio());
553 EXPECT_GT(protected_client_results.GetSuccessRatio(),
554 unprotected_client_results.GetSuccessRatio());
555
556 // The rest is just for optional manual evaluation of the results;
557 // in particular the traffic pattern is interesting.
558
559 VerboseOut("\nUnprotected server's results:\n\n");
560 VerboseOut(unprotected_server.VisualizeASCII(132).c_str());
561 VerboseOut("\n\n");
562 VerboseOut("Protected server's results:\n\n");
563 VerboseOut(protected_server.VisualizeASCII(132).c_str());
564 VerboseOut("\n\n");
565
566 unprotected_attacker_results.PrintResults(
567 "attackers attacking unprotected server.");
568 unprotected_client_results.PrintResults(
569 "normal clients making requests to unprotected server.");
570 protected_attacker_results.PrintResults(
571 "attackers attacking protected server.");
572 protected_client_results.PrintResults(
573 "normal clients making requests to protected server.");
574 }
575
576 // Returns the downtime perceived by the client, as a ratio of the
577 // actual downtime.
SimulateDowntime(const base::TimeDelta & duration,const base::TimeDelta & average_client_interval,bool enable_throttling)578 double SimulateDowntime(const base::TimeDelta& duration,
579 const base::TimeDelta& average_client_interval,
580 bool enable_throttling) {
581 base::TimeDelta time_between_ticks = duration / 200;
582 TimeTicks start_downtime = TimeTicks() + (duration / 2);
583
584 // A server that never rejects requests, but will go down for maintenance.
585 Server server(std::numeric_limits<int>::max(), 1.0);
586 server.SetDowntime(start_downtime, duration);
587
588 URLRequestThrottlerManager manager;
589 auto throttler_entry =
590 base::MakeRefCounted<MockURLRequestThrottlerEntry>(&manager);
591 if (!enable_throttling)
592 throttler_entry->DisableBackoffThrottling();
593
594 Requester requester(throttler_entry.get(), average_client_interval, &server,
595 nullptr);
596 requester.SetStartupJitter(duration / 3);
597 requester.SetRequestJitter(average_client_interval);
598
599 DiscreteTimeSimulation simulation;
600 simulation.AddActor(&requester);
601 simulation.AddActor(&server);
602
603 simulation.RunSimulation(duration * 2, time_between_ticks);
604
605 return static_cast<double>(
606 requester.last_downtime_duration().InMilliseconds()) /
607 static_cast<double>(duration.InMilliseconds());
608 }
609
TEST(URLRequestThrottlerSimulation,PerceivedDowntimeRatio)610 TEST(URLRequestThrottlerSimulation, PerceivedDowntimeRatio) {
611 base::test::TaskEnvironment task_environment;
612
613 struct Stats {
614 // Expected interval that we expect the ratio of downtime when anti-DDoS
615 // is enabled and downtime when anti-DDoS is not enabled to fall within.
616 //
617 // The expected interval depends on two things: The exponential back-off
618 // policy encoded in URLRequestThrottlerEntry, and the test or set of
619 // tests that the Stats object is tracking (e.g. a test where the client
620 // retries very rapidly on a very long downtime will tend to increase the
621 // number).
622 //
623 // To determine an appropriate new interval when parameters have changed,
624 // run the test a few times (you may have to Ctrl-C out of it after a few
625 // seconds) and choose an interval that the test converges quickly and
626 // reliably to. Then set the new interval, and run the test e.g. 20 times
627 // in succession to make sure it never takes an obscenely long time to
628 // converge to this interval.
629 double expected_min_increase;
630 double expected_max_increase;
631
632 size_t num_runs;
633 double total_ratio_unprotected;
634 double total_ratio_protected;
635
636 bool DidConverge(double* increase_ratio_out) {
637 double unprotected_ratio = total_ratio_unprotected / num_runs;
638 double protected_ratio = total_ratio_protected / num_runs;
639 double increase_ratio = protected_ratio / unprotected_ratio;
640 if (increase_ratio_out)
641 *increase_ratio_out = increase_ratio;
642 return expected_min_increase <= increase_ratio &&
643 increase_ratio <= expected_max_increase;
644 }
645
646 void ReportTrialResult(double increase_ratio) {
647 VerboseOut(
648 " Perceived downtime with throttling is %.4f times without.\n",
649 increase_ratio);
650 VerboseOut(" Test result after %d trials.\n", num_runs);
651 }
652 };
653
654 Stats global_stats = { 1.08, 1.15 };
655
656 struct Trial {
657 base::TimeDelta duration;
658 base::TimeDelta average_client_interval;
659 Stats stats;
660
661 void PrintTrialDescription() {
662 const double duration_minutes = duration / base::Minutes(1);
663 const double interval_minutes =
664 average_client_interval / base::Minutes(1);
665 VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
666 duration_minutes, interval_minutes);
667 }
668 };
669
670 // We don't set or check expected ratio intervals on individual
671 // experiments as this might make the test too fragile, but we
672 // print them out at the end for manual evaluation (we want to be
673 // able to make claims about the expected ratios depending on the
674 // type of behavior of the client and the downtime, e.g. the difference
675 // in behavior between a client making requests every few minutes vs.
676 // one that makes a request every 15 seconds).
677 Trial trials[] = {
678 {base::Seconds(10), base::Seconds(3)},
679 {base::Seconds(30), base::Seconds(7)},
680 {base::Minutes(5), base::Seconds(30)},
681 {base::Minutes(10), base::Seconds(20)},
682 {base::Minutes(20), base::Seconds(15)},
683 {base::Minutes(20), base::Seconds(50)},
684 {base::Minutes(30), base::Minutes(2)},
685 {base::Minutes(30), base::Minutes(5)},
686 {base::Minutes(40), base::Minutes(7)},
687 {base::Minutes(40), base::Minutes(2)},
688 {base::Minutes(40), base::Seconds(15)},
689 {base::Minutes(60), base::Minutes(7)},
690 {base::Minutes(60), base::Minutes(2)},
691 {base::Minutes(60), base::Seconds(15)},
692 {base::Minutes(80), base::Minutes(20)},
693 {base::Minutes(80), base::Minutes(3)},
694 {base::Minutes(80), base::Seconds(15)},
695
696 // Most brutal?
697 {base::Minutes(45), base::Milliseconds(500)},
698 };
699
700 // If things don't converge by the time we've done 100K trials, then
701 // clearly one or more of the expected intervals are wrong.
702 while (global_stats.num_runs < 100000) {
703 for (auto& trial : trials) {
704 ++global_stats.num_runs;
705 ++trial.stats.num_runs;
706 double ratio_unprotected = SimulateDowntime(
707 trial.duration, trial.average_client_interval, false);
708 double ratio_protected =
709 SimulateDowntime(trial.duration, trial.average_client_interval, true);
710 global_stats.total_ratio_unprotected += ratio_unprotected;
711 global_stats.total_ratio_protected += ratio_protected;
712 trial.stats.total_ratio_unprotected += ratio_unprotected;
713 trial.stats.total_ratio_protected += ratio_protected;
714 }
715
716 double increase_ratio;
717 if (global_stats.DidConverge(&increase_ratio))
718 break;
719
720 if (global_stats.num_runs > 200) {
721 VerboseOut("Test has not yet converged on expected interval.\n");
722 global_stats.ReportTrialResult(increase_ratio);
723 }
724 }
725
726 double average_increase_ratio;
727 EXPECT_TRUE(global_stats.DidConverge(&average_increase_ratio));
728
729 // Print individual trial results for optional manual evaluation.
730 double max_increase_ratio = 0.0;
731 for (auto& trial : trials) {
732 double increase_ratio;
733 trial.stats.DidConverge(&increase_ratio);
734 max_increase_ratio = std::max(max_increase_ratio, increase_ratio);
735 trial.PrintTrialDescription();
736 trial.stats.ReportTrialResult(increase_ratio);
737 }
738
739 VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio);
740 VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio);
741 }
742
743 } // namespace
744 } // namespace net
745