1// Copyright (c) 2010 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.
4
5#include "chrome/browser/net/websocket_experiment/websocket_experiment_runner.h"
6
7#include "base/command_line.h"
8#include "base/compiler_specific.h"
9#include "base/message_loop.h"
10#include "base/metrics/field_trial.h"
11#include "base/task.h"
12#include "base/string_util.h"
13#include "chrome/common/chrome_switches.h"
14#include "content/browser/browser_thread.h"
15#include "net/base/host_resolver.h"
16#include "net/base/net_errors.h"
17#include "net/websockets/websocket.h"
18
19namespace chrome_browser_net_websocket_experiment {
20
21static const char *kExperimentHost = "websocket-experiment.chromium.org";
22static const int kAlternativePort = 61985;
23
24// Hold reference while experiment is running.
25static scoped_refptr<WebSocketExperimentRunner> runner;
26
27/* static */
28void WebSocketExperimentRunner::Start() {
29  DCHECK(!runner.get());
30
31  // After June 30, 2011 builds, it will always be in default group.
32  scoped_refptr<base::FieldTrial> trial(
33      new base::FieldTrial(
34          "WebSocketExperiment", 1000, "default", 2011, 6, 30));
35  int active = trial->AppendGroup("active", 5);  // 0.5% in active group.
36
37  bool run_experiment = (trial->group() == active);
38#ifndef NDEBUG
39  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
40  std::string experiment_host = command_line.GetSwitchValueASCII(
41      switches::kWebSocketLiveExperimentHost);
42  if (!experiment_host.empty())
43    run_experiment = true;
44#else
45  run_experiment = false;
46#endif
47  if (!run_experiment)
48    return;
49
50  runner = new WebSocketExperimentRunner;
51  runner->Run();
52}
53
54/* static */
55void WebSocketExperimentRunner::Stop() {
56  if (runner.get())
57    runner->Cancel();
58  runner = NULL;
59}
60
61WebSocketExperimentRunner::WebSocketExperimentRunner()
62    : next_state_(STATE_NONE),
63      task_state_(STATE_NONE),
64      ALLOW_THIS_IN_INITIALIZER_LIST(
65          task_callback_(this, &WebSocketExperimentRunner::OnTaskCompleted)) {
66  WebSocketExperimentTask::InitHistogram();
67  InitConfig();
68}
69
70WebSocketExperimentRunner::~WebSocketExperimentRunner() {
71  DCHECK(!task_.get());
72  WebSocketExperimentTask::ReleaseHistogram();
73}
74
75void WebSocketExperimentRunner::Run() {
76  DCHECK_EQ(next_state_, STATE_NONE);
77  next_state_ = STATE_RUN_WS;
78  BrowserThread::PostDelayedTask(
79      BrowserThread::IO,
80      FROM_HERE,
81      NewRunnableMethod(this, &WebSocketExperimentRunner::DoLoop),
82      config_.initial_delay_ms);
83}
84
85void WebSocketExperimentRunner::Cancel() {
86  next_state_ = STATE_NONE;
87  BrowserThread::PostTask(
88      BrowserThread::IO,
89      FROM_HERE,
90      NewRunnableMethod(this, &WebSocketExperimentRunner::DoLoop));
91}
92
93void WebSocketExperimentRunner::InitConfig() {
94  config_.initial_delay_ms = 5 * 60 * 1000;  // 5 mins
95  config_.next_delay_ms = 12 * 60 * 60 * 1000;  // 12 hours
96
97  std::string experiment_host = kExperimentHost;
98#ifndef NDEBUG
99  const CommandLine& command_line = *CommandLine::ForCurrentProcess();
100  std::string experiment_host_override = command_line.GetSwitchValueASCII(
101      switches::kWebSocketLiveExperimentHost);
102  if (!experiment_host_override.empty()) {
103    experiment_host = experiment_host_override;
104    config_.initial_delay_ms = 5 * 1000;  // 5 secs.
105  }
106#endif
107
108  WebSocketExperimentTask::Config* config;
109  WebSocketExperimentTask::Config task_config;
110
111  task_config.protocol_version = net::WebSocket::DEFAULT_VERSION;
112  config = &config_.ws_config[STATE_RUN_WS - STATE_RUN_WS];
113  *config = task_config;
114  config->url =
115      GURL(StringPrintf("ws://%s/live_exp", experiment_host.c_str()));
116  config->ws_location =
117      StringPrintf("ws://%s/live_exp", experiment_host.c_str());
118  config->http_url =
119      GURL(StringPrintf("http://%s/", experiment_host.c_str()));
120
121  config = &config_.ws_config[STATE_RUN_WSS - STATE_RUN_WS];
122  *config = task_config;
123  config->url =
124      GURL(StringPrintf("wss://%s/live_exp", experiment_host.c_str()));
125  config->ws_location =
126      StringPrintf("wss://%s/live_exp", experiment_host.c_str());
127  config->http_url =
128      GURL(StringPrintf("https://%s/", experiment_host.c_str()));
129
130  config = &config_.ws_config[STATE_RUN_WS_NODEFAULT_PORT -
131                              STATE_RUN_WS];
132  *config = task_config;
133  config->url =
134      GURL(StringPrintf("ws://%s:%d/live_exp",
135                        experiment_host.c_str(), kAlternativePort));
136  config->ws_location =
137      StringPrintf("ws://%s:%d/live_exp",
138                   experiment_host.c_str(), kAlternativePort);
139  config->http_url =
140      GURL(StringPrintf("http://%s:%d/",
141                        experiment_host.c_str(), kAlternativePort));
142
143  task_config.protocol_version = net::WebSocket::DRAFT75;
144  config = &config_.ws_config[STATE_RUN_WS_DRAFT75 - STATE_RUN_WS];
145  *config = task_config;
146  config->url =
147      GURL(StringPrintf("ws://%s/live_exp", experiment_host.c_str()));
148  config->ws_location =
149      StringPrintf("ws://%s/live_exp", experiment_host.c_str());
150  config->http_url =
151      GURL(StringPrintf("http://%s/", experiment_host.c_str()));
152
153  config = &config_.ws_config[STATE_RUN_WSS_DRAFT75 - STATE_RUN_WS];
154  *config = task_config;
155  config->url =
156      GURL(StringPrintf("wss://%s/live_exp", experiment_host.c_str()));
157  config->ws_location =
158      StringPrintf("wss://%s/live_exp", experiment_host.c_str());
159  config->http_url =
160      GURL(StringPrintf("https://%s/", experiment_host.c_str()));
161
162  config = &config_.ws_config[STATE_RUN_WS_NODEFAULT_PORT_DRAFT75 -
163                              STATE_RUN_WS];
164  *config = task_config;
165  config->url =
166      GURL(StringPrintf("ws://%s:%d/live_exp",
167                        experiment_host.c_str(), kAlternativePort));
168  config->ws_location =
169      StringPrintf("ws://%s:%d/live_exp",
170                   experiment_host.c_str(), kAlternativePort);
171  config->http_url =
172      GURL(StringPrintf("http://%s:%d/",
173                        experiment_host.c_str(), kAlternativePort));
174
175}
176
177void WebSocketExperimentRunner::DoLoop() {
178  if (next_state_ == STATE_NONE) {
179    if (task_.get()) {
180      AddRef();  // Release in OnTaskCompleted after Cancelled.
181      task_->Cancel();
182    }
183    return;
184  }
185
186  State state = next_state_;
187  task_state_ = STATE_NONE;
188  next_state_ = STATE_NONE;
189
190  switch (state) {
191    case STATE_IDLE:
192      task_.reset();
193      next_state_ = STATE_RUN_WS;
194      BrowserThread::PostDelayedTask(
195          BrowserThread::IO,
196          FROM_HERE,
197          NewRunnableMethod(this, &WebSocketExperimentRunner::DoLoop),
198          config_.next_delay_ms);
199      break;
200    case STATE_RUN_WS:
201    case STATE_RUN_WSS:
202    case STATE_RUN_WS_NODEFAULT_PORT:
203    case STATE_RUN_WS_DRAFT75:
204    case STATE_RUN_WSS_DRAFT75:
205    case STATE_RUN_WS_NODEFAULT_PORT_DRAFT75:
206      task_.reset(new WebSocketExperimentTask(
207          config_.ws_config[state - STATE_RUN_WS], &task_callback_));
208      task_state_ = state;
209      if (static_cast<State>(state + 1) == NUM_STATES)
210        next_state_ = STATE_IDLE;
211      else
212        next_state_ = static_cast<State>(state + 1);
213      break;
214    default:
215      NOTREACHED();
216      break;
217  }
218  if (task_.get())
219    task_->Run();
220}
221
222void WebSocketExperimentRunner::OnTaskCompleted(int result) {
223  if (next_state_ == STATE_NONE) {
224    task_.reset();
225    // Task is Canceled.
226    DVLOG(1) << "WebSocketExperiment Task is canceled.";
227    Release();
228    return;
229  }
230  task_->SaveResult();
231  task_.reset();
232
233  DoLoop();
234}
235
236}  // namespace chrome_browser_net_websocket_experiment
237