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 #include "content/browser/tracing/tracing_ui.h"
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/file_util.h"
17 #include "base/format_macros.h"
18 #include "base/json/json_reader.h"
19 #include "base/json/json_writer.h"
20 #include "base/memory/scoped_ptr.h"
21 #include "base/strings/string_number_conversions.h"
22 #include "base/strings/string_split.h"
23 #include "base/strings/string_util.h"
24 #include "base/strings/stringprintf.h"
25 #include "base/values.h"
26 #include "content/browser/tracing/grit/tracing_resources.h"
27 #include "content/browser/tracing/trace_uploader.h"
28 #include "content/browser/tracing/tracing_controller_impl.h"
29 #include "content/public/browser/browser_context.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/tracing_controller.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/browser/web_ui.h"
34 #include "content/public/browser/web_ui_data_source.h"
35 #include "content/public/common/content_client.h"
36 #include "content/public/common/content_switches.h"
37 #include "content/public/common/url_constants.h"
42 const char kUploadURL
[] = "https://clients2.google.com/cr/staging_report";
44 void OnGotCategories(const WebUIDataSource::GotDataCallback
& callback
,
45 const std::set
<std::string
>& categorySet
) {
46 scoped_ptr
<base::ListValue
> category_list(new base::ListValue());
47 for (std::set
<std::string
>::const_iterator it
= categorySet
.begin();
48 it
!= categorySet
.end(); it
++) {
49 category_list
->AppendString(*it
);
52 base::RefCountedString
* res
= new base::RefCountedString();
53 base::JSONWriter::Write(category_list
.get(), &res
->data());
57 bool GetTracingOptions(const std::string
& data64
,
58 base::debug::CategoryFilter
* category_filter
,
59 base::debug::TraceOptions
* tracing_options
) {
61 if (!base::Base64Decode(data64
, &data
)) {
62 LOG(ERROR
) << "Options were not base64 encoded.";
66 scoped_ptr
<base::Value
> optionsRaw(base::JSONReader::Read(data
));
68 LOG(ERROR
) << "Options were not valid JSON";
71 base::DictionaryValue
* options
;
72 if (!optionsRaw
->GetAsDictionary(&options
)) {
73 LOG(ERROR
) << "Options must be dict";
77 if (!category_filter
) {
78 LOG(ERROR
) << "category_filter can't be passed as NULL";
82 if (!tracing_options
) {
83 LOG(ERROR
) << "tracing_options can't be passed as NULL";
87 bool options_ok
= true;
88 std::string category_filter_string
;
89 options_ok
&= options
->GetString("categoryFilter", &category_filter_string
);
90 *category_filter
= base::debug::CategoryFilter(category_filter_string
);
92 options_ok
&= options
->GetBoolean("useSystemTracing",
93 &tracing_options
->enable_systrace
);
95 options
->GetBoolean("useSampling", &tracing_options
->enable_sampling
);
97 bool use_continuous_tracing
;
99 options
->GetBoolean("useContinuousTracing", &use_continuous_tracing
);
101 if (use_continuous_tracing
)
102 tracing_options
->record_mode
= base::debug::RECORD_CONTINUOUSLY
;
104 tracing_options
->record_mode
= base::debug::RECORD_UNTIL_FULL
;
107 LOG(ERROR
) << "Malformed options";
113 void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback
& callback
);
115 bool BeginRecording(const std::string
& data64
,
116 const WebUIDataSource::GotDataCallback
& callback
) {
117 base::debug::CategoryFilter
category_filter("");
118 base::debug::TraceOptions tracing_options
;
119 if (!GetTracingOptions(data64
, &category_filter
, &tracing_options
))
122 return TracingController::GetInstance()->EnableRecording(
125 base::Bind(&OnRecordingEnabledAck
, callback
));
128 void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback
& callback
) {
129 base::RefCountedString
* res
= new base::RefCountedString();
133 void OnTraceBufferPercentFullResult(
134 const WebUIDataSource::GotDataCallback
& callback
, float result
) {
135 std::string str
= base::DoubleToString(result
);
136 callback
.Run(base::RefCountedString::TakeString(&str
));
139 void ReadRecordingResult(const WebUIDataSource::GotDataCallback
& callback
,
140 const base::FilePath
& path
) {
142 if (!base::ReadFileToString(path
, &tmp
))
143 LOG(ERROR
) << "Failed to read file " << path
.value();
144 base::DeleteFile(path
, false);
145 callback
.Run(base::RefCountedString::TakeString(&tmp
));
148 void BeginReadingRecordingResult(
149 const WebUIDataSource::GotDataCallback
& callback
,
150 const base::FilePath
& path
) {
151 BrowserThread::PostTask(
152 BrowserThread::FILE, FROM_HERE
,
153 base::Bind(ReadRecordingResult
, callback
, path
));
156 void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback
& callback
);
158 bool EnableMonitoring(const std::string
& data64
,
159 const WebUIDataSource::GotDataCallback
& callback
) {
160 base::debug::TraceOptions tracing_options
;
161 base::debug::CategoryFilter
category_filter("");
162 if (!GetTracingOptions(data64
, &category_filter
, &tracing_options
))
165 return TracingController::GetInstance()->EnableMonitoring(
168 base::Bind(OnMonitoringEnabledAck
, callback
));
171 void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback
& callback
) {
172 base::RefCountedString
* res
= new base::RefCountedString();
176 void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback
& callback
) {
177 base::RefCountedString
* res
= new base::RefCountedString();
181 void GetMonitoringStatus(const WebUIDataSource::GotDataCallback
& callback
) {
183 base::debug::CategoryFilter
category_filter("");
184 base::debug::TraceOptions options
;
185 TracingController::GetInstance()->GetMonitoringStatus(
186 &is_monitoring
, &category_filter
, &options
);
188 scoped_ptr
<base::DictionaryValue
>
189 monitoring_options(new base::DictionaryValue());
190 monitoring_options
->SetBoolean("isMonitoring", is_monitoring
);
191 monitoring_options
->SetString("categoryFilter", category_filter
.ToString());
192 monitoring_options
->SetBoolean("useSystemTracing", options
.enable_systrace
);
193 monitoring_options
->SetBoolean(
194 "useContinuousTracing",
195 options
.record_mode
== base::debug::RECORD_CONTINUOUSLY
);
196 monitoring_options
->SetBoolean("useSampling", options
.enable_sampling
);
198 std::string monitoring_options_json
;
199 base::JSONWriter::Write(monitoring_options
.get(), &monitoring_options_json
);
201 base::RefCountedString
* monitoring_options_base64
=
202 new base::RefCountedString();
203 base::Base64Encode(monitoring_options_json
,
204 &monitoring_options_base64
->data());
205 callback
.Run(monitoring_options_base64
);
208 void ReadMonitoringSnapshot(const WebUIDataSource::GotDataCallback
& callback
,
209 const base::FilePath
& path
) {
211 if (!base::ReadFileToString(path
, &tmp
))
212 LOG(ERROR
) << "Failed to read file " << path
.value();
213 base::DeleteFile(path
, false);
214 callback
.Run(base::RefCountedString::TakeString(&tmp
));
217 void OnMonitoringSnapshotCaptured(
218 const WebUIDataSource::GotDataCallback
& callback
,
219 const base::FilePath
& path
) {
220 BrowserThread::PostTask(
221 BrowserThread::FILE, FROM_HERE
,
222 base::Bind(ReadMonitoringSnapshot
, callback
, path
));
225 bool OnBeginJSONRequest(const std::string
& path
,
226 const WebUIDataSource::GotDataCallback
& callback
) {
227 if (path
== "json/categories") {
228 return TracingController::GetInstance()->GetCategories(
229 base::Bind(OnGotCategories
, callback
));
232 const char* beginRecordingPath
= "json/begin_recording?";
233 if (StartsWithASCII(path
, beginRecordingPath
, true)) {
234 std::string data
= path
.substr(strlen(beginRecordingPath
));
235 return BeginRecording(data
, callback
);
237 if (path
== "json/get_buffer_percent_full") {
238 return TracingController::GetInstance()->GetTraceBufferPercentFull(
239 base::Bind(OnTraceBufferPercentFullResult
, callback
));
241 if (path
== "json/end_recording") {
242 return TracingController::GetInstance()->DisableRecording(
243 base::FilePath(), base::Bind(BeginReadingRecordingResult
, callback
));
246 const char* enableMonitoringPath
= "json/begin_monitoring?";
247 if (path
.find(enableMonitoringPath
) == 0) {
248 std::string data
= path
.substr(strlen(enableMonitoringPath
));
249 return EnableMonitoring(data
, callback
);
251 if (path
== "json/end_monitoring") {
252 return TracingController::GetInstance()->DisableMonitoring(
253 base::Bind(OnMonitoringDisabled
, callback
));
255 if (path
== "json/capture_monitoring") {
256 TracingController::GetInstance()->CaptureMonitoringSnapshot(
257 base::FilePath(), base::Bind(OnMonitoringSnapshotCaptured
, callback
));
260 if (path
== "json/get_monitoring_status") {
261 GetMonitoringStatus(callback
);
265 LOG(ERROR
) << "Unhandled request to " << path
;
269 bool OnTracingRequest(const std::string
& path
,
270 const WebUIDataSource::GotDataCallback
& callback
) {
271 if (StartsWithASCII(path
, "json/", true)) {
272 if (!OnBeginJSONRequest(path
, callback
)) {
273 std::string
error("##ERROR##");
274 callback
.Run(base::RefCountedString::TakeString(&error
));
284 ////////////////////////////////////////////////////////////////////////////////
288 ////////////////////////////////////////////////////////////////////////////////
290 TracingUI::TracingUI(WebUI
* web_ui
)
291 : WebUIController(web_ui
),
292 weak_factory_(this) {
293 web_ui
->RegisterMessageCallback(
295 base::Bind(&TracingUI::DoUpload
, base::Unretained(this)));
297 // Set up the chrome://tracing/ source.
298 BrowserContext
* browser_context
=
299 web_ui
->GetWebContents()->GetBrowserContext();
301 WebUIDataSource
* source
= WebUIDataSource::Create(kChromeUITracingHost
);
302 source
->SetJsonPath("strings.js");
303 source
->SetDefaultResource(IDR_TRACING_HTML
);
304 source
->AddResourcePath("tracing.js", IDR_TRACING_JS
);
305 source
->SetRequestFilter(base::Bind(OnTracingRequest
));
306 WebUIDataSource::Add(browser_context
, source
);
307 TracingControllerImpl::GetInstance()->RegisterTracingUI(this);
310 TracingUI::~TracingUI() {
311 TracingControllerImpl::GetInstance()->UnregisterTracingUI(this);
314 void TracingUI::OnMonitoringStateChanged(bool is_monitoring
) {
315 web_ui()->CallJavascriptFunction(
316 "onMonitoringStateChanged", base::FundamentalValue(is_monitoring
));
319 void TracingUI::DoUpload(const base::ListValue
* args
) {
320 const base::CommandLine
& command_line
=
321 *base::CommandLine::ForCurrentProcess();
322 std::string upload_url
= kUploadURL
;
323 if (command_line
.HasSwitch(switches::kTraceUploadURL
)) {
325 command_line
.GetSwitchValueASCII(switches::kTraceUploadURL
);
327 if (!GURL(upload_url
).is_valid()) {
331 if (upload_url
.empty()) {
332 web_ui()->CallJavascriptFunction("onUploadError",
333 base::StringValue("Upload URL empty or invalid"));
337 std::string file_contents
;
338 if (!args
|| args
->empty() || !args
->GetString(0, &file_contents
)) {
339 web_ui()->CallJavascriptFunction("onUploadError",
340 base::StringValue("Missing data"));
344 TraceUploader::UploadProgressCallback progress_callback
=
345 base::Bind(&TracingUI::OnTraceUploadProgress
,
346 weak_factory_
.GetWeakPtr());
347 TraceUploader::UploadDoneCallback done_callback
=
348 base::Bind(&TracingUI::OnTraceUploadComplete
,
349 weak_factory_
.GetWeakPtr());
352 const char product
[] = "Chrome";
353 #elif defined(OS_MACOSX)
354 const char product
[] = "Chrome_Mac";
355 #elif defined(OS_LINUX)
356 const char product
[] = "Chrome_Linux";
357 #elif defined(OS_ANDROID)
358 const char product
[] = "Chrome_Android";
359 #elif defined(OS_CHROMEOS)
360 const char product
[] = "Chrome_ChromeOS";
362 #error Platform not supported.
365 // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out
366 // the part before the "/".
367 std::vector
<std::string
> product_components
;
368 base::SplitString(content::GetContentClient()->GetProduct(), '/',
369 &product_components
);
370 DCHECK_EQ(2U, product_components
.size());
372 if (product_components
.size() == 2U) {
373 version
= product_components
[1];
378 BrowserContext
* browser_context
=
379 web_ui()->GetWebContents()->GetBrowserContext();
380 TraceUploader
* uploader
= new TraceUploader(
381 product
, version
, upload_url
, browser_context
->GetRequestContext());
383 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE
, base::Bind(
384 &TraceUploader::DoUpload
,
385 base::Unretained(uploader
),
389 // TODO(mmandlis): Add support for stopping the upload in progress.
392 void TracingUI::OnTraceUploadProgress(int64 current
, int64 total
) {
393 DCHECK(current
<= total
);
394 int percent
= (current
/ total
) * 100;
395 web_ui()->CallJavascriptFunction(
397 base::FundamentalValue(percent
),
398 base::StringValue(base::StringPrintf("%" PRId64
, current
)),
399 base::StringValue(base::StringPrintf("%" PRId64
, total
)));
402 void TracingUI::OnTraceUploadComplete(bool success
,
403 const std::string
& report_id
,
404 const std::string
& error_message
) {
406 web_ui()->CallJavascriptFunction("onUploadComplete",
407 base::StringValue(report_id
));
409 web_ui()->CallJavascriptFunction("onUploadError",
410 base::StringValue(error_message
));
414 } // namespace content