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.
4
5#include "content/browser/tracing/tracing_ui.h"
6
7#include <set>
8#include <string>
9#include <vector>
10
11#include "base/base64.h"
12#include "base/bind.h"
13#include "base/bind_helpers.h"
14#include "base/command_line.h"
15#include "base/debug/trace_event.h"
16#include "base/format_macros.h"
17#include "base/json/json_reader.h"
18#include "base/json/json_writer.h"
19#include "base/memory/scoped_ptr.h"
20#include "base/strings/string_number_conversions.h"
21#include "base/strings/string_split.h"
22#include "base/strings/string_util.h"
23#include "base/strings/stringprintf.h"
24#include "base/values.h"
25#include "content/browser/tracing/grit/tracing_resources.h"
26#include "content/browser/tracing/trace_uploader.h"
27#include "content/browser/tracing/tracing_controller_impl.h"
28#include "content/public/browser/browser_context.h"
29#include "content/public/browser/browser_thread.h"
30#include "content/public/browser/tracing_controller.h"
31#include "content/public/browser/web_contents.h"
32#include "content/public/browser/web_ui.h"
33#include "content/public/browser/web_ui_data_source.h"
34#include "content/public/common/content_client.h"
35#include "content/public/common/content_switches.h"
36#include "content/public/common/url_constants.h"
37
38namespace content {
39namespace {
40
41const char kUploadURL[] = "https://clients2.google.com/cr/staging_report";
42
43void OnGotCategories(const WebUIDataSource::GotDataCallback& callback,
44                     const std::set<std::string>& categorySet) {
45  scoped_ptr<base::ListValue> category_list(new base::ListValue());
46  for (std::set<std::string>::const_iterator it = categorySet.begin();
47       it != categorySet.end(); it++) {
48    category_list->AppendString(*it);
49  }
50
51  base::RefCountedString* res = new base::RefCountedString();
52  base::JSONWriter::Write(category_list.get(), &res->data());
53  callback.Run(res);
54}
55
56bool GetTracingOptions(const std::string& data64,
57                       base::debug::CategoryFilter* category_filter,
58                       base::debug::TraceOptions* tracing_options) {
59  std::string data;
60  if (!base::Base64Decode(data64, &data)) {
61    LOG(ERROR) << "Options were not base64 encoded.";
62    return false;
63  }
64
65  scoped_ptr<base::Value> optionsRaw(base::JSONReader::Read(data));
66  if (!optionsRaw) {
67    LOG(ERROR) << "Options were not valid JSON";
68    return false;
69  }
70  base::DictionaryValue* options;
71  if (!optionsRaw->GetAsDictionary(&options)) {
72    LOG(ERROR) << "Options must be dict";
73    return false;
74  }
75
76  if (!category_filter) {
77    LOG(ERROR) << "category_filter can't be passed as NULL";
78    return false;
79  }
80
81  if (!tracing_options) {
82    LOG(ERROR) << "tracing_options can't be passed as NULL";
83    return false;
84  }
85
86  bool options_ok = true;
87  std::string category_filter_string;
88  options_ok &= options->GetString("categoryFilter", &category_filter_string);
89  *category_filter = base::debug::CategoryFilter(category_filter_string);
90
91  options_ok &= options->GetBoolean("useSystemTracing",
92                                    &tracing_options->enable_systrace);
93  options_ok &=
94      options->GetBoolean("useSampling", &tracing_options->enable_sampling);
95
96  bool use_continuous_tracing;
97  options_ok &=
98      options->GetBoolean("useContinuousTracing", &use_continuous_tracing);
99
100  if (use_continuous_tracing)
101    tracing_options->record_mode = base::debug::RECORD_CONTINUOUSLY;
102  else
103    tracing_options->record_mode = base::debug::RECORD_UNTIL_FULL;
104
105  if (!options_ok) {
106    LOG(ERROR) << "Malformed options";
107    return false;
108  }
109  return true;
110}
111
112void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback);
113
114bool BeginRecording(const std::string& data64,
115                    const WebUIDataSource::GotDataCallback& callback) {
116  base::debug::CategoryFilter category_filter("");
117  base::debug::TraceOptions tracing_options;
118  if (!GetTracingOptions(data64, &category_filter, &tracing_options))
119    return false;
120
121  return TracingController::GetInstance()->EnableRecording(
122      category_filter,
123      tracing_options,
124      base::Bind(&OnRecordingEnabledAck, callback));
125}
126
127void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
128  base::RefCountedString* res = new base::RefCountedString();
129  callback.Run(res);
130}
131
132void OnTraceBufferPercentFullResult(
133    const WebUIDataSource::GotDataCallback& callback, float result) {
134  std::string str = base::DoubleToString(result);
135  callback.Run(base::RefCountedString::TakeString(&str));
136}
137
138void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback);
139
140bool EnableMonitoring(const std::string& data64,
141                      const WebUIDataSource::GotDataCallback& callback) {
142  base::debug::TraceOptions tracing_options;
143  base::debug::CategoryFilter category_filter("");
144  if (!GetTracingOptions(data64, &category_filter, &tracing_options))
145    return false;
146
147  return TracingController::GetInstance()->EnableMonitoring(
148      category_filter,
149      tracing_options,
150      base::Bind(OnMonitoringEnabledAck, callback));
151}
152
153void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
154  base::RefCountedString* res = new base::RefCountedString();
155  callback.Run(res);
156}
157
158void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback& callback) {
159  base::RefCountedString* res = new base::RefCountedString();
160  callback.Run(res);
161}
162
163void GetMonitoringStatus(const WebUIDataSource::GotDataCallback& callback) {
164  bool is_monitoring;
165  base::debug::CategoryFilter category_filter("");
166  base::debug::TraceOptions options;
167  TracingController::GetInstance()->GetMonitoringStatus(
168      &is_monitoring, &category_filter, &options);
169
170  scoped_ptr<base::DictionaryValue>
171    monitoring_options(new base::DictionaryValue());
172  monitoring_options->SetBoolean("isMonitoring", is_monitoring);
173  monitoring_options->SetString("categoryFilter", category_filter.ToString());
174  monitoring_options->SetBoolean("useSystemTracing", options.enable_systrace);
175  monitoring_options->SetBoolean(
176      "useContinuousTracing",
177      options.record_mode == base::debug::RECORD_CONTINUOUSLY);
178  monitoring_options->SetBoolean("useSampling", options.enable_sampling);
179
180  std::string monitoring_options_json;
181  base::JSONWriter::Write(monitoring_options.get(), &monitoring_options_json);
182
183  base::RefCountedString* monitoring_options_base64 =
184    new base::RefCountedString();
185  base::Base64Encode(monitoring_options_json,
186                     &monitoring_options_base64->data());
187  callback.Run(monitoring_options_base64);
188}
189
190void TracingCallbackWrapper(const WebUIDataSource::GotDataCallback& callback,
191                            base::RefCountedString* data) {
192  callback.Run(data);
193}
194
195bool OnBeginJSONRequest(const std::string& path,
196                        const WebUIDataSource::GotDataCallback& callback) {
197  if (path == "json/categories") {
198    return TracingController::GetInstance()->GetCategories(
199        base::Bind(OnGotCategories, callback));
200  }
201
202  const char* beginRecordingPath = "json/begin_recording?";
203  if (StartsWithASCII(path, beginRecordingPath, true)) {
204    std::string data = path.substr(strlen(beginRecordingPath));
205    return BeginRecording(data, callback);
206  }
207  if (path == "json/get_buffer_percent_full") {
208    return TracingController::GetInstance()->GetTraceBufferPercentFull(
209        base::Bind(OnTraceBufferPercentFullResult, callback));
210  }
211  if (path == "json/end_recording") {
212    return TracingController::GetInstance()->DisableRecording(
213        TracingControllerImpl::CreateStringSink(
214            base::Bind(TracingCallbackWrapper, callback)));
215  }
216
217  const char* enableMonitoringPath = "json/begin_monitoring?";
218  if (path.find(enableMonitoringPath) == 0) {
219    std::string data = path.substr(strlen(enableMonitoringPath));
220    return EnableMonitoring(data, callback);
221  }
222  if (path == "json/end_monitoring") {
223    return TracingController::GetInstance()->DisableMonitoring(
224        base::Bind(OnMonitoringDisabled, callback));
225  }
226  if (path == "json/capture_monitoring") {
227    TracingController::GetInstance()->CaptureMonitoringSnapshot(
228        TracingControllerImpl::CreateStringSink(
229            base::Bind(TracingCallbackWrapper, callback)));
230    return true;
231  }
232  if (path == "json/get_monitoring_status") {
233    GetMonitoringStatus(callback);
234    return true;
235  }
236
237  LOG(ERROR) << "Unhandled request to " << path;
238  return false;
239}
240
241bool OnTracingRequest(const std::string& path,
242                      const WebUIDataSource::GotDataCallback& callback) {
243  if (StartsWithASCII(path, "json/", true)) {
244    if (!OnBeginJSONRequest(path, callback)) {
245      std::string error("##ERROR##");
246      callback.Run(base::RefCountedString::TakeString(&error));
247    }
248    return true;
249  }
250  return false;
251}
252
253}  // namespace
254
255
256////////////////////////////////////////////////////////////////////////////////
257//
258// TracingUI
259//
260////////////////////////////////////////////////////////////////////////////////
261
262TracingUI::TracingUI(WebUI* web_ui)
263    : WebUIController(web_ui),
264      weak_factory_(this) {
265  web_ui->RegisterMessageCallback(
266        "doUpload",
267        base::Bind(&TracingUI::DoUpload, base::Unretained(this)));
268
269  // Set up the chrome://tracing/ source.
270  BrowserContext* browser_context =
271      web_ui->GetWebContents()->GetBrowserContext();
272
273  WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost);
274  source->SetJsonPath("strings.js");
275  source->SetDefaultResource(IDR_TRACING_HTML);
276  source->AddResourcePath("tracing.js", IDR_TRACING_JS);
277  source->SetRequestFilter(base::Bind(OnTracingRequest));
278  WebUIDataSource::Add(browser_context, source);
279  TracingControllerImpl::GetInstance()->RegisterTracingUI(this);
280}
281
282TracingUI::~TracingUI() {
283  TracingControllerImpl::GetInstance()->UnregisterTracingUI(this);
284}
285
286void TracingUI::OnMonitoringStateChanged(bool is_monitoring) {
287  web_ui()->CallJavascriptFunction(
288      "onMonitoringStateChanged", base::FundamentalValue(is_monitoring));
289}
290
291void TracingUI::DoUpload(const base::ListValue* args) {
292  const base::CommandLine& command_line =
293      *base::CommandLine::ForCurrentProcess();
294  std::string upload_url = kUploadURL;
295  if (command_line.HasSwitch(switches::kTraceUploadURL)) {
296    upload_url =
297        command_line.GetSwitchValueASCII(switches::kTraceUploadURL);
298  }
299  if (!GURL(upload_url).is_valid()) {
300    upload_url.clear();
301  }
302
303  if (upload_url.empty()) {
304    web_ui()->CallJavascriptFunction("onUploadError",
305        base::StringValue("Upload URL empty or invalid"));
306    return;
307  }
308
309  std::string file_contents;
310  if (!args || args->empty() || !args->GetString(0, &file_contents)) {
311    web_ui()->CallJavascriptFunction("onUploadError",
312                                     base::StringValue("Missing data"));
313    return;
314  }
315
316  TraceUploader::UploadProgressCallback progress_callback =
317      base::Bind(&TracingUI::OnTraceUploadProgress,
318      weak_factory_.GetWeakPtr());
319  TraceUploader::UploadDoneCallback done_callback =
320      base::Bind(&TracingUI::OnTraceUploadComplete,
321      weak_factory_.GetWeakPtr());
322
323#if defined(OS_WIN)
324  const char product[] = "Chrome";
325#elif defined(OS_MACOSX)
326  const char product[] = "Chrome_Mac";
327#elif defined(OS_LINUX)
328  const char product[] = "Chrome_Linux";
329#elif defined(OS_ANDROID)
330  const char product[] = "Chrome_Android";
331#elif defined(OS_CHROMEOS)
332  const char product[] = "Chrome_ChromeOS";
333#else
334#error Platform not supported.
335#endif
336
337  // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out
338  // the part before the "/".
339  std::vector<std::string> product_components;
340  base::SplitString(content::GetContentClient()->GetProduct(), '/',
341                    &product_components);
342  DCHECK_EQ(2U, product_components.size());
343  std::string version;
344  if (product_components.size() == 2U) {
345    version = product_components[1];
346  } else {
347    version = "unknown";
348  }
349
350  BrowserContext* browser_context =
351      web_ui()->GetWebContents()->GetBrowserContext();
352  TraceUploader* uploader = new TraceUploader(
353      product, version, upload_url, browser_context->GetRequestContext());
354
355  BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind(
356     &TraceUploader::DoUpload,
357     base::Unretained(uploader),
358     file_contents,
359     progress_callback,
360     done_callback));
361  // TODO(mmandlis): Add support for stopping the upload in progress.
362}
363
364void TracingUI::OnTraceUploadProgress(int64 current, int64 total) {
365  DCHECK(current <= total);
366  int percent = (current / total) * 100;
367  web_ui()->CallJavascriptFunction(
368        "onUploadProgress",
369        base::FundamentalValue(percent),
370        base::StringValue(base::StringPrintf("%" PRId64, current)),
371        base::StringValue(base::StringPrintf("%" PRId64, total)));
372}
373
374void TracingUI::OnTraceUploadComplete(bool success,
375                                      const std::string& report_id,
376                                      const std::string& error_message) {
377  if (success) {
378    web_ui()->CallJavascriptFunction("onUploadComplete",
379                                     base::StringValue(report_id));
380  } else {
381    web_ui()->CallJavascriptFunction("onUploadError",
382                                     base::StringValue(error_message));
383  }
384}
385
386}  // namespace content
387