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