1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
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.
19 #include "base/environment.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/memory/scoped_vector.h"
22 #include "base/rand_util.h"
23 #include "base/time/time.h"
24 #include "net/base/request_priority.h"
25 #include "net/url_request/url_request.h"
26 #include "net/url_request/url_request_context.h"
27 #include "net/url_request/url_request_test_util.h"
28 #include "net/url_request/url_request_throttler_manager.h"
29 #include "net/url_request/url_request_throttler_test_support.h"
30 #include "testing/gtest/include/gtest/gtest.h"
32 using base::TimeDelta
;
33 using base::TimeTicks
;
38 // Set this variable in your environment if you want to see verbose results
39 // of the simulation tests.
40 const char kShowSimulationVariableName
[] = "SHOW_SIMULATION_RESULTS";
42 // Prints output only if a given environment variable is set. We use this
43 // to not print any output for human evaluation when the test is run without
45 void VerboseOut(const char* format
, ...) {
46 static bool have_checked_environment
= false;
47 static bool should_print
= false;
48 if (!have_checked_environment
) {
49 have_checked_environment
= true;
50 scoped_ptr
<base::Environment
> env(base::Environment::Create());
51 if (env
->HasVar(kShowSimulationVariableName
))
57 va_start(arglist
, format
);
58 vprintf(format
, arglist
);
63 // A simple two-phase discrete time simulation. Actors are added in the order
64 // they should take action at every tick of the clock. Ticks of the clock
66 // - Phase 1 advances every actor's time to a new absolute time.
67 // - Phase 2 asks each actor to perform their action.
68 class DiscreteTimeSimulation
{
73 virtual void AdvanceTime(const TimeTicks
& absolute_time
) = 0;
74 virtual void PerformAction() = 0;
77 DiscreteTimeSimulation() {}
79 // Adds an |actor| to the simulation. The client of the simulation maintains
80 // ownership of |actor| and must ensure its lifetime exceeds that of the
81 // simulation. Actors should be added in the order you wish for them to
82 // act at each tick of the simulation.
83 void AddActor(Actor
* actor
) {
84 actors_
.push_back(actor
);
87 // Runs the simulation for, pretending |time_between_ticks| passes from one
88 // tick to the next. The start time will be the current real time. The
89 // simulation will stop when the simulated duration is equal to or greater
90 // than |maximum_simulated_duration|.
91 void RunSimulation(const TimeDelta
& maximum_simulated_duration
,
92 const TimeDelta
& time_between_ticks
) {
93 TimeTicks start_time
= TimeTicks();
94 TimeTicks now
= start_time
;
95 while ((now
- start_time
) <= maximum_simulated_duration
) {
96 for (std::vector
<Actor
*>::iterator it
= actors_
.begin();
99 (*it
)->AdvanceTime(now
);
102 for (std::vector
<Actor
*>::iterator it
= actors_
.begin();
105 (*it
)->PerformAction();
108 now
+= time_between_ticks
;
113 std::vector
<Actor
*> actors_
;
115 DISALLOW_COPY_AND_ASSIGN(DiscreteTimeSimulation
);
118 // Represents a web server in a simulation of a server under attack by
119 // a lot of clients. Must be added to the simulation's list of actors
120 // after all |Requester| objects.
121 class Server
: public DiscreteTimeSimulation::Actor
{
123 Server(int max_queries_per_tick
, double request_drop_ratio
)
124 : max_queries_per_tick_(max_queries_per_tick
),
125 request_drop_ratio_(request_drop_ratio
),
126 num_overloaded_ticks_remaining_(0),
127 num_current_tick_queries_(0),
128 num_overloaded_ticks_(0),
129 max_experienced_queries_per_tick_(0),
130 mock_request_(context_
.CreateRequest(
131 GURL(), DEFAULT_PRIORITY
, NULL
, NULL
)) {}
133 void SetDowntime(const TimeTicks
& start_time
, const TimeDelta
& duration
) {
134 start_downtime_
= start_time
;
135 end_downtime_
= start_time
+ duration
;
138 void AdvanceTime(const TimeTicks
& absolute_time
) override
{
139 now_
= absolute_time
;
142 void PerformAction() override
{
143 // We are inserted at the end of the actor's list, so all Requester
144 // instances have already done their bit.
145 if (num_current_tick_queries_
> max_experienced_queries_per_tick_
)
146 max_experienced_queries_per_tick_
= num_current_tick_queries_
;
148 if (num_current_tick_queries_
> max_queries_per_tick_
) {
149 // We pretend the server fails for the next several ticks after it
151 num_overloaded_ticks_remaining_
= 5;
152 ++num_overloaded_ticks_
;
153 } else if (num_overloaded_ticks_remaining_
> 0) {
154 --num_overloaded_ticks_remaining_
;
157 requests_per_tick_
.push_back(num_current_tick_queries_
);
158 num_current_tick_queries_
= 0;
161 // This is called by Requester. It returns the response code from
163 int HandleRequest() {
164 ++num_current_tick_queries_
;
165 if (!start_downtime_
.is_null() &&
166 start_downtime_
< now_
&& now_
< end_downtime_
) {
167 // For the simulation measuring the increase in perceived
168 // downtime, it might be interesting to count separately the
169 // queries seen by the server (assuming a front-end reverse proxy
170 // is what actually serves up the 503s in this case) so that we could
171 // visualize the traffic spike seen by the server when it comes up,
172 // which would in many situations be ameliorated by the anti-DDoS
177 if ((num_overloaded_ticks_remaining_
> 0 ||
178 num_current_tick_queries_
> max_queries_per_tick_
) &&
179 base::RandDouble() < request_drop_ratio_
) {
186 int num_overloaded_ticks() const {
187 return num_overloaded_ticks_
;
190 int max_experienced_queries_per_tick() const {
191 return max_experienced_queries_per_tick_
;
194 const URLRequest
& mock_request() const {
195 return *mock_request_
.get();
198 std::string
VisualizeASCII(int terminal_width
) {
199 // Account for | characters we place at left of graph.
202 VerboseOut("Overloaded for %d of %d ticks.\n",
203 num_overloaded_ticks_
, requests_per_tick_
.size());
204 VerboseOut("Got maximum of %d requests in a tick.\n\n",
205 max_experienced_queries_per_tick_
);
207 VerboseOut("Traffic graph:\n\n");
209 // Printing the graph like this is a bit overkill, but was very useful
210 // while developing the various simulations to see if they were testing
211 // the corner cases we want to simulate.
213 // Find the smallest number of whole ticks we need to group into a
214 // column that will let all ticks fit into the column width we have.
215 int num_ticks
= requests_per_tick_
.size();
216 double ticks_per_column_exact
=
217 static_cast<double>(num_ticks
) / static_cast<double>(terminal_width
);
218 int ticks_per_column
= std::ceil(ticks_per_column_exact
);
219 DCHECK_GE(ticks_per_column
* terminal_width
, num_ticks
);
221 // Sum up the column values.
222 int num_columns
= num_ticks
/ ticks_per_column
;
223 if (num_ticks
% ticks_per_column
)
225 DCHECK_LE(num_columns
, terminal_width
);
226 scoped_ptr
<int[]> columns(new int[num_columns
]);
227 for (int tx
= 0; tx
< num_ticks
; ++tx
) {
228 int cx
= tx
/ ticks_per_column
;
229 if (tx
% ticks_per_column
== 0)
231 columns
[cx
] += requests_per_tick_
[tx
];
234 // Find the lowest integer divisor that will let the column values
235 // be represented in a graph of maximum height 50.
237 for (int cx
= 0; cx
< num_columns
; ++cx
)
238 max_value
= std::max(max_value
, columns
[cx
]);
239 const int kNumRows
= 50;
240 double row_divisor_exact
= max_value
/ static_cast<double>(kNumRows
);
241 int row_divisor
= std::ceil(row_divisor_exact
);
242 DCHECK_GE(row_divisor
* kNumRows
, max_value
);
244 // To show the overload line, we calculate the appropriate value.
245 int overload_value
= max_queries_per_tick_
* ticks_per_column
;
247 // When num_ticks is not a whole multiple of ticks_per_column, the last
248 // column includes fewer ticks than the others. In this case, don't
249 // print it so that we don't show an inconsistent value.
250 int num_printed_columns
= num_columns
;
251 if (num_ticks
% ticks_per_column
)
252 --num_printed_columns
;
254 // This is a top-to-bottom traversal of rows, left-to-right per row.
256 for (int rx
= 0; rx
< kNumRows
; ++rx
) {
257 int range_min
= (kNumRows
- rx
) * row_divisor
;
258 int range_max
= range_min
+ row_divisor
;
260 range_min
= -1; // Make 0 values fit in the bottom range.
262 for (int cx
= 0; cx
< num_printed_columns
; ++cx
) {
264 // Show the overload line.
265 if (range_min
< overload_value
&& overload_value
<= range_max
)
268 // Preferentially, show the graph line.
269 if (range_min
< columns
[cx
] && columns
[cx
] <= range_max
)
272 output
.append(1, block
);
277 output
.append(num_printed_columns
, '=');
282 const URLRequestContext
& context() const { return context_
; }
286 TimeTicks start_downtime_
; // Can be 0 to say "no downtime".
287 TimeTicks end_downtime_
;
288 const int max_queries_per_tick_
;
289 const double request_drop_ratio_
; // Ratio of requests to 503 when failing.
290 int num_overloaded_ticks_remaining_
;
291 int num_current_tick_queries_
;
292 int num_overloaded_ticks_
;
293 int max_experienced_queries_per_tick_
;
294 std::vector
<int> requests_per_tick_
;
296 TestURLRequestContext context_
;
297 scoped_ptr
<URLRequest
> mock_request_
;
299 DISALLOW_COPY_AND_ASSIGN(Server
);
302 // Mock throttler entry used by Requester class.
303 class MockURLRequestThrottlerEntry
: public URLRequestThrottlerEntry
{
305 explicit MockURLRequestThrottlerEntry(URLRequestThrottlerManager
* manager
)
306 : URLRequestThrottlerEntry(manager
, std::string()),
307 mock_backoff_entry_(&backoff_policy_
) {}
309 const BackoffEntry
* GetBackoffEntry() const override
{
310 return &mock_backoff_entry_
;
313 BackoffEntry
* GetBackoffEntry() override
{ return &mock_backoff_entry_
; }
315 TimeTicks
ImplGetTimeNow() const override
{ return fake_now_
; }
317 void SetFakeNow(const TimeTicks
& fake_time
) {
318 fake_now_
= fake_time
;
319 mock_backoff_entry_
.set_fake_now(fake_time
);
322 TimeTicks
fake_now() const {
327 ~MockURLRequestThrottlerEntry() override
{}
331 MockBackoffEntry mock_backoff_entry_
;
334 // Registry of results for a class of |Requester| objects (e.g. attackers vs.
336 class RequesterResults
{
339 : num_attempts_(0), num_successful_(0), num_failed_(0), num_blocked_(0) {
357 int num_attempts() const { return num_attempts_
; }
358 int num_successful() const { return num_successful_
; }
359 int num_failed() const { return num_failed_
; }
360 int num_blocked() const { return num_blocked_
; }
362 double GetBlockedRatio() {
363 DCHECK(num_attempts_
);
364 return static_cast<double>(num_blocked_
) /
365 static_cast<double>(num_attempts_
);
368 double GetSuccessRatio() {
369 DCHECK(num_attempts_
);
370 return static_cast<double>(num_successful_
) /
371 static_cast<double>(num_attempts_
);
374 void PrintResults(const char* class_description
) {
375 if (num_attempts_
== 0) {
376 VerboseOut("No data for %s\n", class_description
);
380 VerboseOut("Requester results for %s\n", class_description
);
381 VerboseOut(" %d attempts\n", num_attempts_
);
382 VerboseOut(" %d successes\n", num_successful_
);
383 VerboseOut(" %d 5xx responses\n", num_failed_
);
384 VerboseOut(" %d requests blocked\n", num_blocked_
);
385 VerboseOut(" %.2f success ratio\n", GetSuccessRatio());
386 VerboseOut(" %.2f blocked ratio\n", GetBlockedRatio());
397 // Represents an Requester in a simulated DDoS situation, that periodically
398 // requests a specific resource.
399 class Requester
: public DiscreteTimeSimulation::Actor
{
401 Requester(MockURLRequestThrottlerEntry
* throttler_entry
,
402 const TimeDelta
& time_between_requests
,
404 RequesterResults
* results
)
405 : throttler_entry_(throttler_entry
),
406 time_between_requests_(time_between_requests
),
407 last_attempt_was_failure_(false),
413 void AdvanceTime(const TimeTicks
& absolute_time
) override
{
414 if (time_of_last_success_
.is_null())
415 time_of_last_success_
= absolute_time
;
417 throttler_entry_
->SetFakeNow(absolute_time
);
420 void PerformAction() override
{
421 TimeDelta effective_delay
= time_between_requests_
;
422 TimeDelta current_jitter
= TimeDelta::FromMilliseconds(
423 request_jitter_
.InMilliseconds() * base::RandDouble());
424 if (base::RandInt(0, 1)) {
425 effective_delay
-= current_jitter
;
427 effective_delay
+= current_jitter
;
430 if (throttler_entry_
->fake_now() - time_of_last_attempt_
>
432 if (!throttler_entry_
->ShouldRejectRequest(
433 server_
->mock_request(),
434 server_
->context().network_delegate())) {
435 int status_code
= server_
->HandleRequest();
436 MockURLRequestThrottlerHeaderAdapter
response_headers(status_code
);
437 throttler_entry_
->UpdateWithResponse(std::string(), &response_headers
);
439 if (status_code
== 200) {
441 results_
->AddSuccess();
443 if (last_attempt_was_failure_
) {
444 last_downtime_duration_
=
445 throttler_entry_
->fake_now() - time_of_last_success_
;
448 time_of_last_success_
= throttler_entry_
->fake_now();
449 last_attempt_was_failure_
= false;
452 results_
->AddFailure();
453 last_attempt_was_failure_
= true;
457 results_
->AddBlocked();
458 last_attempt_was_failure_
= true;
461 time_of_last_attempt_
= throttler_entry_
->fake_now();
465 // Adds a delay until the first request, equal to a uniformly distributed
466 // value between now and now + max_delay.
467 void SetStartupJitter(const TimeDelta
& max_delay
) {
468 int delay_ms
= base::RandInt(0, max_delay
.InMilliseconds());
469 time_of_last_attempt_
= TimeTicks() +
470 TimeDelta::FromMilliseconds(delay_ms
) - time_between_requests_
;
473 void SetRequestJitter(const TimeDelta
& request_jitter
) {
474 request_jitter_
= request_jitter
;
477 TimeDelta
last_downtime_duration() const { return last_downtime_duration_
; }
480 scoped_refptr
<MockURLRequestThrottlerEntry
> throttler_entry_
;
481 const TimeDelta time_between_requests_
;
482 TimeDelta request_jitter_
;
483 TimeTicks time_of_last_attempt_
;
484 TimeTicks time_of_last_success_
;
485 bool last_attempt_was_failure_
;
486 TimeDelta last_downtime_duration_
;
487 Server
* const server_
;
488 RequesterResults
* const results_
; // May be NULL.
490 DISALLOW_COPY_AND_ASSIGN(Requester
);
493 void SimulateAttack(Server
* server
,
494 RequesterResults
* attacker_results
,
495 RequesterResults
* client_results
,
496 bool enable_throttling
) {
497 const size_t kNumAttackers
= 50;
498 const size_t kNumClients
= 50;
499 DiscreteTimeSimulation simulation
;
500 URLRequestThrottlerManager manager
;
501 ScopedVector
<Requester
> requesters
;
502 for (size_t i
= 0; i
< kNumAttackers
; ++i
) {
503 // Use a tiny time_between_requests so the attackers will ping the
504 // server at every tick of the simulation.
505 scoped_refptr
<MockURLRequestThrottlerEntry
> throttler_entry(
506 new MockURLRequestThrottlerEntry(&manager
));
507 if (!enable_throttling
)
508 throttler_entry
->DisableBackoffThrottling();
510 Requester
* attacker
= new Requester(throttler_entry
.get(),
511 TimeDelta::FromMilliseconds(1),
514 attacker
->SetStartupJitter(TimeDelta::FromSeconds(120));
515 requesters
.push_back(attacker
);
516 simulation
.AddActor(attacker
);
518 for (size_t i
= 0; i
< kNumClients
; ++i
) {
519 // Normal clients only make requests every 2 minutes, plus/minus 1 minute.
520 scoped_refptr
<MockURLRequestThrottlerEntry
> throttler_entry(
521 new MockURLRequestThrottlerEntry(&manager
));
522 if (!enable_throttling
)
523 throttler_entry
->DisableBackoffThrottling();
525 Requester
* client
= new Requester(throttler_entry
.get(),
526 TimeDelta::FromMinutes(2),
529 client
->SetStartupJitter(TimeDelta::FromSeconds(120));
530 client
->SetRequestJitter(TimeDelta::FromMinutes(1));
531 requesters
.push_back(client
);
532 simulation
.AddActor(client
);
534 simulation
.AddActor(server
);
536 simulation
.RunSimulation(TimeDelta::FromMinutes(6),
537 TimeDelta::FromSeconds(1));
540 TEST(URLRequestThrottlerSimulation
, HelpsInAttack
) {
541 Server
unprotected_server(30, 1.0);
542 RequesterResults unprotected_attacker_results
;
543 RequesterResults unprotected_client_results
;
544 Server
protected_server(30, 1.0);
545 RequesterResults protected_attacker_results
;
546 RequesterResults protected_client_results
;
547 SimulateAttack(&unprotected_server
,
548 &unprotected_attacker_results
,
549 &unprotected_client_results
,
551 SimulateAttack(&protected_server
,
552 &protected_attacker_results
,
553 &protected_client_results
,
556 // These assert that the DDoS protection actually benefits the
557 // server. Manual inspection of the traffic graphs will show this
558 // even more clearly.
559 EXPECT_GT(unprotected_server
.num_overloaded_ticks(),
560 protected_server
.num_overloaded_ticks());
561 EXPECT_GT(unprotected_server
.max_experienced_queries_per_tick(),
562 protected_server
.max_experienced_queries_per_tick());
564 // These assert that the DDoS protection actually benefits non-malicious
565 // (and non-degenerate/accidentally DDoSing) users.
566 EXPECT_LT(protected_client_results
.GetBlockedRatio(),
567 protected_attacker_results
.GetBlockedRatio());
568 EXPECT_GT(protected_client_results
.GetSuccessRatio(),
569 unprotected_client_results
.GetSuccessRatio());
571 // The rest is just for optional manual evaluation of the results;
572 // in particular the traffic pattern is interesting.
574 VerboseOut("\nUnprotected server's results:\n\n");
575 VerboseOut(unprotected_server
.VisualizeASCII(132).c_str());
577 VerboseOut("Protected server's results:\n\n");
578 VerboseOut(protected_server
.VisualizeASCII(132).c_str());
581 unprotected_attacker_results
.PrintResults(
582 "attackers attacking unprotected server.");
583 unprotected_client_results
.PrintResults(
584 "normal clients making requests to unprotected server.");
585 protected_attacker_results
.PrintResults(
586 "attackers attacking protected server.");
587 protected_client_results
.PrintResults(
588 "normal clients making requests to protected server.");
591 // Returns the downtime perceived by the client, as a ratio of the
593 double SimulateDowntime(const TimeDelta
& duration
,
594 const TimeDelta
& average_client_interval
,
595 bool enable_throttling
) {
596 TimeDelta time_between_ticks
= duration
/ 200;
597 TimeTicks start_downtime
= TimeTicks() + (duration
/ 2);
599 // A server that never rejects requests, but will go down for maintenance.
600 Server
server(std::numeric_limits
<int>::max(), 1.0);
601 server
.SetDowntime(start_downtime
, duration
);
603 URLRequestThrottlerManager manager
;
604 scoped_refptr
<MockURLRequestThrottlerEntry
> throttler_entry(
605 new MockURLRequestThrottlerEntry(&manager
));
606 if (!enable_throttling
)
607 throttler_entry
->DisableBackoffThrottling();
610 throttler_entry
.get(), average_client_interval
, &server
, NULL
);
611 requester
.SetStartupJitter(duration
/ 3);
612 requester
.SetRequestJitter(average_client_interval
);
614 DiscreteTimeSimulation simulation
;
615 simulation
.AddActor(&requester
);
616 simulation
.AddActor(&server
);
618 simulation
.RunSimulation(duration
* 2, time_between_ticks
);
620 return static_cast<double>(
621 requester
.last_downtime_duration().InMilliseconds()) /
622 static_cast<double>(duration
.InMilliseconds());
625 TEST(URLRequestThrottlerSimulation
, PerceivedDowntimeRatio
) {
627 // Expected interval that we expect the ratio of downtime when anti-DDoS
628 // is enabled and downtime when anti-DDoS is not enabled to fall within.
630 // The expected interval depends on two things: The exponential back-off
631 // policy encoded in URLRequestThrottlerEntry, and the test or set of
632 // tests that the Stats object is tracking (e.g. a test where the client
633 // retries very rapidly on a very long downtime will tend to increase the
636 // To determine an appropriate new interval when parameters have changed,
637 // run the test a few times (you may have to Ctrl-C out of it after a few
638 // seconds) and choose an interval that the test converges quickly and
639 // reliably to. Then set the new interval, and run the test e.g. 20 times
640 // in succession to make sure it never takes an obscenely long time to
641 // converge to this interval.
642 double expected_min_increase
;
643 double expected_max_increase
;
646 double total_ratio_unprotected
;
647 double total_ratio_protected
;
649 bool DidConverge(double* increase_ratio_out
) {
650 double unprotected_ratio
= total_ratio_unprotected
/ num_runs
;
651 double protected_ratio
= total_ratio_protected
/ num_runs
;
652 double increase_ratio
= protected_ratio
/ unprotected_ratio
;
653 if (increase_ratio_out
)
654 *increase_ratio_out
= increase_ratio
;
655 return expected_min_increase
<= increase_ratio
&&
656 increase_ratio
<= expected_max_increase
;
659 void ReportTrialResult(double increase_ratio
) {
661 " Perceived downtime with throttling is %.4f times without.\n",
663 VerboseOut(" Test result after %d trials.\n", num_runs
);
667 Stats global_stats
= { 1.08, 1.15 };
671 TimeDelta average_client_interval
;
674 void PrintTrialDescription() {
675 double duration_minutes
=
676 static_cast<double>(duration
.InSeconds()) / 60.0;
677 double interval_minutes
=
678 static_cast<double>(average_client_interval
.InSeconds()) / 60.0;
679 VerboseOut("Trial with %.2f min downtime, avg. interval %.2f min.\n",
680 duration_minutes
, interval_minutes
);
684 // We don't set or check expected ratio intervals on individual
685 // experiments as this might make the test too fragile, but we
686 // print them out at the end for manual evaluation (we want to be
687 // able to make claims about the expected ratios depending on the
688 // type of behavior of the client and the downtime, e.g. the difference
689 // in behavior between a client making requests every few minutes vs.
690 // one that makes a request every 15 seconds).
692 { TimeDelta::FromSeconds(10), TimeDelta::FromSeconds(3) },
693 { TimeDelta::FromSeconds(30), TimeDelta::FromSeconds(7) },
694 { TimeDelta::FromMinutes(5), TimeDelta::FromSeconds(30) },
695 { TimeDelta::FromMinutes(10), TimeDelta::FromSeconds(20) },
696 { TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(15) },
697 { TimeDelta::FromMinutes(20), TimeDelta::FromSeconds(50) },
698 { TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(2) },
699 { TimeDelta::FromMinutes(30), TimeDelta::FromMinutes(5) },
700 { TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(7) },
701 { TimeDelta::FromMinutes(40), TimeDelta::FromMinutes(2) },
702 { TimeDelta::FromMinutes(40), TimeDelta::FromSeconds(15) },
703 { TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(7) },
704 { TimeDelta::FromMinutes(60), TimeDelta::FromMinutes(2) },
705 { TimeDelta::FromMinutes(60), TimeDelta::FromSeconds(15) },
706 { TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(20) },
707 { TimeDelta::FromMinutes(80), TimeDelta::FromMinutes(3) },
708 { TimeDelta::FromMinutes(80), TimeDelta::FromSeconds(15) },
711 { TimeDelta::FromMinutes(45), TimeDelta::FromMilliseconds(500) },
714 // If things don't converge by the time we've done 100K trials, then
715 // clearly one or more of the expected intervals are wrong.
716 while (global_stats
.num_runs
< 100000) {
717 for (size_t i
= 0; i
< arraysize(trials
); ++i
) {
718 ++global_stats
.num_runs
;
719 ++trials
[i
].stats
.num_runs
;
720 double ratio_unprotected
= SimulateDowntime(
721 trials
[i
].duration
, trials
[i
].average_client_interval
, false);
722 double ratio_protected
= SimulateDowntime(
723 trials
[i
].duration
, trials
[i
].average_client_interval
, true);
724 global_stats
.total_ratio_unprotected
+= ratio_unprotected
;
725 global_stats
.total_ratio_protected
+= ratio_protected
;
726 trials
[i
].stats
.total_ratio_unprotected
+= ratio_unprotected
;
727 trials
[i
].stats
.total_ratio_protected
+= ratio_protected
;
730 double increase_ratio
;
731 if (global_stats
.DidConverge(&increase_ratio
))
734 if (global_stats
.num_runs
> 200) {
735 VerboseOut("Test has not yet converged on expected interval.\n");
736 global_stats
.ReportTrialResult(increase_ratio
);
740 double average_increase_ratio
;
741 EXPECT_TRUE(global_stats
.DidConverge(&average_increase_ratio
));
743 // Print individual trial results for optional manual evaluation.
744 double max_increase_ratio
= 0.0;
745 for (size_t i
= 0; i
< arraysize(trials
); ++i
) {
746 double increase_ratio
;
747 trials
[i
].stats
.DidConverge(&increase_ratio
);
748 max_increase_ratio
= std::max(max_increase_ratio
, increase_ratio
);
749 trials
[i
].PrintTrialDescription();
750 trials
[i
].stats
.ReportTrialResult(increase_ratio
);
753 VerboseOut("Average increase ratio was %.4f\n", average_increase_ratio
);
754 VerboseOut("Maximum increase ratio was %.4f\n", max_increase_ratio
);