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 <string>
8
9#include "base/base64.h"
10#include "base/bind.h"
11#include "base/bind_helpers.h"
12#include "base/file_util.h"
13#include "base/json/json_reader.h"
14#include "base/json/json_writer.h"
15#include "base/memory/scoped_ptr.h"
16#include "base/strings/string_number_conversions.h"
17#include "base/strings/string_util.h"
18#include "base/values.h"
19#include "content/browser/tracing/grit/tracing_resources.h"
20#include "content/browser/tracing/tracing_controller_impl.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/browser/tracing_controller.h"
23#include "content/public/browser/web_contents.h"
24#include "content/public/browser/web_ui.h"
25#include "content/public/browser/web_ui_data_source.h"
26#include "content/public/common/url_constants.h"
27
28namespace content {
29namespace {
30
31void OnGotCategories(const WebUIDataSource::GotDataCallback& callback,
32                     const std::set<std::string>& categorySet) {
33
34  scoped_ptr<base::ListValue> category_list(new base::ListValue());
35  for (std::set<std::string>::const_iterator it = categorySet.begin();
36       it != categorySet.end(); it++) {
37    category_list->AppendString(*it);
38  }
39
40  base::RefCountedString* res = new base::RefCountedString();
41  base::JSONWriter::Write(category_list.get(), &res->data());
42  callback.Run(res);
43}
44
45bool GetTracingOptions(const std::string& data64,
46                       std::string* category_filter_string,
47                       int* tracing_options) {
48  std::string data;
49  if (!base::Base64Decode(data64, &data)) {
50    LOG(ERROR) << "Options were not base64 encoded.";
51    return false;
52  }
53
54  scoped_ptr<base::Value> optionsRaw(base::JSONReader::Read(data));
55  if (!optionsRaw) {
56    LOG(ERROR) << "Options were not valid JSON";
57    return false;
58  }
59  base::DictionaryValue* options;
60  if (!optionsRaw->GetAsDictionary(&options)) {
61    LOG(ERROR) << "Options must be dict";
62    return false;
63  }
64
65  bool use_system_tracing;
66  bool use_continuous_tracing;
67  bool use_sampling;
68
69  bool options_ok = true;
70  options_ok &= options->GetString("categoryFilter", category_filter_string);
71  options_ok &= options->GetBoolean("useSystemTracing", &use_system_tracing);
72  options_ok &= options->GetBoolean("useContinuousTracing",
73                                    &use_continuous_tracing);
74  options_ok &= options->GetBoolean("useSampling", &use_sampling);
75  if (!options_ok) {
76    LOG(ERROR) << "Malformed options";
77    return false;
78  }
79
80  *tracing_options = 0;
81  if (use_system_tracing)
82    *tracing_options |= TracingController::ENABLE_SYSTRACE;
83  if (use_sampling)
84    *tracing_options |= TracingController::ENABLE_SAMPLING;
85  if (use_continuous_tracing)
86    *tracing_options |= TracingController::RECORD_CONTINUOUSLY;
87  return true;
88}
89
90void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback);
91
92bool BeginRecording(const std::string& data64,
93                    const WebUIDataSource::GotDataCallback& callback) {
94  std::string category_filter_string;
95  int tracing_options = 0;
96  if (!GetTracingOptions(data64, &category_filter_string, &tracing_options))
97    return false;
98
99  return TracingController::GetInstance()->EnableRecording(
100      category_filter_string,
101      static_cast<TracingController::Options>(tracing_options),
102      base::Bind(&OnRecordingEnabledAck, callback));
103}
104
105void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
106  base::RefCountedString* res = new base::RefCountedString();
107  callback.Run(res);
108}
109
110void OnTraceBufferPercentFullResult(
111    const WebUIDataSource::GotDataCallback& callback, float result) {
112  std::string str = base::DoubleToString(result);
113  callback.Run(base::RefCountedString::TakeString(&str));
114}
115
116void ReadRecordingResult(const WebUIDataSource::GotDataCallback& callback,
117                         const base::FilePath& path) {
118  std::string tmp;
119  if (!base::ReadFileToString(path, &tmp))
120    LOG(ERROR) << "Failed to read file " << path.value();
121  base::DeleteFile(path, false);
122  callback.Run(base::RefCountedString::TakeString(&tmp));
123}
124
125void BeginReadingRecordingResult(
126    const WebUIDataSource::GotDataCallback& callback,
127    const base::FilePath& path) {
128  BrowserThread::PostTask(
129      BrowserThread::FILE, FROM_HERE,
130      base::Bind(ReadRecordingResult, callback, path));
131}
132
133void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback);
134
135bool EnableMonitoring(const std::string& data64,
136                      const WebUIDataSource::GotDataCallback& callback) {
137  std::string category_filter_string;
138  int tracing_options = 0;
139  if (!GetTracingOptions(data64, &category_filter_string, &tracing_options))
140    return false;
141
142  return TracingController::GetInstance()->EnableMonitoring(
143      category_filter_string,
144      static_cast<TracingController::Options>(tracing_options),
145      base::Bind(OnMonitoringEnabledAck, callback));
146}
147
148void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
149  base::RefCountedString* res = new base::RefCountedString();
150  callback.Run(res);
151}
152
153void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback& callback) {
154  base::RefCountedString* res = new base::RefCountedString();
155  callback.Run(res);
156}
157
158void GetMonitoringStatus(const WebUIDataSource::GotDataCallback& callback) {
159  bool is_monitoring;
160  std::string category_filter;
161  TracingController::Options options;
162  TracingController::GetInstance()->GetMonitoringStatus(
163      &is_monitoring, &category_filter, &options);
164
165  scoped_ptr<base::DictionaryValue>
166    monitoring_options(new base::DictionaryValue());
167  monitoring_options->SetBoolean("isMonitoring", is_monitoring);
168  monitoring_options->SetString("categoryFilter", category_filter);
169  monitoring_options->SetBoolean("useSystemTracing",
170      (options & TracingController::ENABLE_SYSTRACE) != 0);
171  monitoring_options->SetBoolean("useContinuousTracing",
172      (options & TracingController::RECORD_CONTINUOUSLY) != 0);
173  monitoring_options->SetBoolean("useSampling",
174      (options & TracingController::ENABLE_SAMPLING) != 0);
175
176  std::string monitoring_options_json;
177  base::JSONWriter::Write(monitoring_options.get(), &monitoring_options_json);
178
179  base::RefCountedString* monitoring_options_base64 =
180    new base::RefCountedString();
181  base::Base64Encode(monitoring_options_json,
182                     &monitoring_options_base64->data());
183  callback.Run(monitoring_options_base64);
184}
185
186void ReadMonitoringSnapshot(const WebUIDataSource::GotDataCallback& callback,
187                            const base::FilePath& path) {
188  std::string tmp;
189  if (!base::ReadFileToString(path, &tmp))
190    LOG(ERROR) << "Failed to read file " << path.value();
191  base::DeleteFile(path, false);
192  callback.Run(base::RefCountedString::TakeString(&tmp));
193}
194
195void OnMonitoringSnapshotCaptured(
196    const WebUIDataSource::GotDataCallback& callback,
197    const base::FilePath& path) {
198  BrowserThread::PostTask(
199      BrowserThread::FILE, FROM_HERE,
200      base::Bind(ReadMonitoringSnapshot, callback, path));
201}
202
203bool OnBeginJSONRequest(const std::string& path,
204                        const WebUIDataSource::GotDataCallback& callback) {
205  if (path == "json/categories") {
206    return TracingController::GetInstance()->GetCategories(
207        base::Bind(OnGotCategories, callback));
208  }
209
210  const char* beginRecordingPath = "json/begin_recording?";
211  if (StartsWithASCII(path, beginRecordingPath, true)) {
212    std::string data = path.substr(strlen(beginRecordingPath));
213    return BeginRecording(data, callback);
214  }
215  if (path == "json/get_buffer_percent_full") {
216    return TracingController::GetInstance()->GetTraceBufferPercentFull(
217        base::Bind(OnTraceBufferPercentFullResult, callback));
218  }
219  if (path == "json/end_recording") {
220    return TracingController::GetInstance()->DisableRecording(
221        base::FilePath(), base::Bind(BeginReadingRecordingResult, callback));
222  }
223
224  const char* enableMonitoringPath = "json/begin_monitoring?";
225  if (path.find(enableMonitoringPath) == 0) {
226    std::string data = path.substr(strlen(enableMonitoringPath));
227    return EnableMonitoring(data, callback);
228  }
229  if (path == "json/end_monitoring") {
230    return TracingController::GetInstance()->DisableMonitoring(
231        base::Bind(OnMonitoringDisabled, callback));
232  }
233  if (path == "json/capture_monitoring") {
234    TracingController::GetInstance()->CaptureMonitoringSnapshot(
235        base::FilePath(), base::Bind(OnMonitoringSnapshotCaptured, callback));
236    return true;
237  }
238  if (path == "json/get_monitoring_status") {
239    GetMonitoringStatus(callback);
240    return true;
241  }
242
243  LOG(ERROR) << "Unhandled request to " << path;
244  return false;
245}
246
247bool OnTracingRequest(const std::string& path,
248                      const WebUIDataSource::GotDataCallback& callback) {
249  if (StartsWithASCII(path, "json/", true)) {
250    if (!OnBeginJSONRequest(path, callback)) {
251      std::string error("##ERROR##");
252      callback.Run(base::RefCountedString::TakeString(&error));
253    }
254    return true;
255  }
256  return false;
257}
258
259}  // namespace
260
261
262////////////////////////////////////////////////////////////////////////////////
263//
264// TracingUI
265//
266////////////////////////////////////////////////////////////////////////////////
267
268TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui) {
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
291}  // namespace content
292