Save errno for logging before potentially overwriting it.
[chromium-blink-merge.git] / content / browser / tracing / tracing_ui.cc
blob8553f8fb8edfefda81a8e3a710c3f5af9c7892dd
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"
7 #include <string>
9 #include "base/bind.h"
10 #include "base/bind_helpers.h"
11 #include "base/command_line.h"
12 #include "base/debug/trace_event.h"
13 #include "base/file_util.h"
14 #include "base/json/string_escape.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/strings/stringprintf.h"
19 #include "base/strings/utf_string_conversions.h"
20 #include "base/values.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/content_browser_client.h"
23 #include "content/public/browser/render_view_host.h"
24 #include "content/public/browser/trace_controller.h"
25 #include "content/public/browser/trace_subscriber.h"
26 #include "content/public/browser/web_contents.h"
27 #include "content/public/browser/web_contents_view.h"
28 #include "content/public/browser/web_ui.h"
29 #include "content/public/browser/web_ui_data_source.h"
30 #include "content/public/browser/web_ui_message_handler.h"
31 #include "content/public/common/url_constants.h"
32 #include "grit/content_resources.h"
33 #include "ipc/ipc_channel.h"
34 #include "ui/shell_dialogs/select_file_dialog.h"
36 #if defined(OS_CHROMEOS)
37 #include "chromeos/dbus/dbus_thread_manager.h"
38 #include "chromeos/dbus/debug_daemon_client.h"
39 #endif
41 namespace content {
42 namespace {
44 WebUIDataSource* CreateTracingHTMLSource() {
45 WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost);
47 source->SetJsonPath("strings.js");
48 source->SetDefaultResource(IDR_TRACING_HTML);
49 source->AddResourcePath("tracing.js", IDR_TRACING_JS);
50 return source;
53 // This class receives javascript messages from the renderer.
54 // Note that the WebUI infrastructure runs on the UI thread, therefore all of
55 // this class's methods are expected to run on the UI thread.
56 class TracingMessageHandler
57 : public WebUIMessageHandler,
58 public ui::SelectFileDialog::Listener,
59 public base::SupportsWeakPtr<TracingMessageHandler>,
60 public TraceSubscriber {
61 public:
62 TracingMessageHandler();
63 virtual ~TracingMessageHandler();
65 // WebUIMessageHandler implementation.
66 virtual void RegisterMessages() OVERRIDE;
68 // SelectFileDialog::Listener implementation
69 virtual void FileSelected(const base::FilePath& path,
70 int index,
71 void* params) OVERRIDE;
72 virtual void FileSelectionCanceled(void* params) OVERRIDE;
74 // TraceSubscriber implementation.
75 virtual void OnEndTracingComplete() OVERRIDE;
76 virtual void OnTraceDataCollected(
77 const scoped_refptr<base::RefCountedString>& trace_fragment) OVERRIDE;
78 virtual void OnTraceBufferPercentFullReply(float percent_full) OVERRIDE;
79 virtual void OnKnownCategoriesCollected(
80 const std::set<std::string>& known_categories) OVERRIDE;
82 // Messages.
83 void OnTracingControllerInitialized(const base::ListValue* list);
84 void OnBeginTracing(const base::ListValue* list);
85 void OnEndTracingAsync(const base::ListValue* list);
86 void OnBeginRequestBufferPercentFull(const base::ListValue* list);
87 void OnLoadTraceFile(const base::ListValue* list);
88 void OnSaveTraceFile(const base::ListValue* list);
89 void OnGetKnownCategories(const base::ListValue* list);
91 // Callbacks.
92 void LoadTraceFileComplete(string16* file_contents,
93 const base::FilePath &path);
94 void SaveTraceFileComplete();
96 private:
97 // The file dialog to select a file for loading or saving traces.
98 scoped_refptr<ui::SelectFileDialog> select_trace_file_dialog_;
100 // The type of the file dialog as the same one is used for loading or saving
101 // traces.
102 ui::SelectFileDialog::Type select_trace_file_dialog_type_;
104 // The trace data that is to be written to the file on saving.
105 scoped_ptr<std::string> trace_data_to_save_;
107 // True while tracing is active.
108 bool trace_enabled_;
110 // True while system tracing is active.
111 bool system_trace_in_progress_;
113 void OnEndSystemTracingAck(
114 const scoped_refptr<base::RefCountedString>& events_str_ptr);
116 DISALLOW_COPY_AND_ASSIGN(TracingMessageHandler);
119 // A proxy passed to the Read and Write tasks used when loading or saving trace
120 // data.
121 class TaskProxy : public base::RefCountedThreadSafe<TaskProxy> {
122 public:
123 explicit TaskProxy(const base::WeakPtr<TracingMessageHandler>& handler)
124 : handler_(handler) {}
125 void LoadTraceFileCompleteProxy(string16* file_contents,
126 const base::FilePath& path) {
127 if (handler_.get())
128 handler_->LoadTraceFileComplete(file_contents, path);
129 delete file_contents;
132 void SaveTraceFileCompleteProxy() {
133 if (handler_.get())
134 handler_->SaveTraceFileComplete();
137 private:
138 friend class base::RefCountedThreadSafe<TaskProxy>;
139 ~TaskProxy() {}
141 // The message handler to call callbacks on.
142 base::WeakPtr<TracingMessageHandler> handler_;
144 DISALLOW_COPY_AND_ASSIGN(TaskProxy);
147 ////////////////////////////////////////////////////////////////////////////////
149 // TracingMessageHandler
151 ////////////////////////////////////////////////////////////////////////////////
153 TracingMessageHandler::TracingMessageHandler()
154 : select_trace_file_dialog_type_(ui::SelectFileDialog::SELECT_NONE),
155 trace_enabled_(false),
156 system_trace_in_progress_(false) {
159 TracingMessageHandler::~TracingMessageHandler() {
160 if (select_trace_file_dialog_.get())
161 select_trace_file_dialog_->ListenerDestroyed();
163 // If we are the current subscriber, this will result in ending tracing.
164 TraceController::GetInstance()->CancelSubscriber(this);
166 // Shutdown any system tracing too.
167 if (system_trace_in_progress_) {
168 #if defined(OS_CHROMEOS)
169 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
170 RequestStopSystemTracing(
171 chromeos::DebugDaemonClient::EmptyStopSystemTracingCallback());
172 #endif
176 void TracingMessageHandler::RegisterMessages() {
177 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
179 web_ui()->RegisterMessageCallback("tracingControllerInitialized",
180 base::Bind(&TracingMessageHandler::OnTracingControllerInitialized,
181 base::Unretained(this)));
182 web_ui()->RegisterMessageCallback("beginTracing",
183 base::Bind(&TracingMessageHandler::OnBeginTracing,
184 base::Unretained(this)));
185 web_ui()->RegisterMessageCallback("endTracingAsync",
186 base::Bind(&TracingMessageHandler::OnEndTracingAsync,
187 base::Unretained(this)));
188 web_ui()->RegisterMessageCallback("beginRequestBufferPercentFull",
189 base::Bind(&TracingMessageHandler::OnBeginRequestBufferPercentFull,
190 base::Unretained(this)));
191 web_ui()->RegisterMessageCallback("loadTraceFile",
192 base::Bind(&TracingMessageHandler::OnLoadTraceFile,
193 base::Unretained(this)));
194 web_ui()->RegisterMessageCallback("saveTraceFile",
195 base::Bind(&TracingMessageHandler::OnSaveTraceFile,
196 base::Unretained(this)));
197 web_ui()->RegisterMessageCallback("getKnownCategories",
198 base::Bind(&TracingMessageHandler::OnGetKnownCategories,
199 base::Unretained(this)));
202 void TracingMessageHandler::OnTracingControllerInitialized(
203 const base::ListValue* args) {
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
206 // Send the client info to the tracingController
208 scoped_ptr<base::DictionaryValue> dict(new base::DictionaryValue());
209 dict->SetString("version", GetContentClient()->GetProduct());
211 dict->SetString("command_line",
212 CommandLine::ForCurrentProcess()->GetCommandLineString());
214 web_ui()->CallJavascriptFunction("tracingController.onClientInfoUpdate",
215 *dict);
219 void TracingMessageHandler::OnBeginRequestBufferPercentFull(
220 const base::ListValue* list) {
221 TraceController::GetInstance()->GetTraceBufferPercentFullAsync(this);
224 // A callback used for asynchronously reading a file to a string. Calls the
225 // TaskProxy callback when reading is complete.
226 void ReadTraceFileCallback(TaskProxy* proxy, const base::FilePath& path) {
227 std::string file_contents;
228 if (!file_util::ReadFileToString(path, &file_contents))
229 return;
231 // We need to escape the file contents, because it will go into a javascript
232 // quoted string in TracingMessageHandler::LoadTraceFileComplete. We need to
233 // escape control characters (to have well-formed javascript statements), as
234 // well as \ and ' (the only special characters in a ''-quoted string).
235 // Do the escaping on this thread, it may take a little while for big files
236 // and we don't want to block the UI during that time. Also do the UTF-16
237 // conversion here.
238 // Note: we're using UTF-16 because we'll need to cut the string into slices
239 // to give to Javascript, and it's easier to cut than UTF-8 (since JS strings
240 // are arrays of 16-bit values, UCS-2 really, whereas we can't cut inside of a
241 // multibyte UTF-8 codepoint).
242 size_t size = file_contents.size();
243 std::string escaped_contents;
244 escaped_contents.reserve(size);
245 for (size_t i = 0; i < size; ++i) {
246 char c = file_contents[i];
247 if (c < ' ') {
248 escaped_contents += base::StringPrintf("\\u%04x", c);
249 continue;
251 if (c == '\\' || c == '\'')
252 escaped_contents.push_back('\\');
253 escaped_contents.push_back(c);
255 file_contents.clear();
257 scoped_ptr<string16> contents16(new string16);
258 UTF8ToUTF16(escaped_contents).swap(*contents16);
260 BrowserThread::PostTask(
261 BrowserThread::UI, FROM_HERE,
262 base::Bind(&TaskProxy::LoadTraceFileCompleteProxy, proxy,
263 contents16.release(),
264 path));
267 // A callback used for asynchronously writing a file from a string. Calls the
268 // TaskProxy callback when writing is complete.
269 void WriteTraceFileCallback(TaskProxy* proxy,
270 const base::FilePath& path,
271 std::string* contents) {
272 if (!file_util::WriteFile(path, contents->c_str(), contents->size()))
273 return;
275 BrowserThread::PostTask(
276 BrowserThread::UI, FROM_HERE,
277 base::Bind(&TaskProxy::SaveTraceFileCompleteProxy, proxy));
280 void TracingMessageHandler::FileSelected(
281 const base::FilePath& path, int index, void* params) {
282 if (select_trace_file_dialog_type_ ==
283 ui::SelectFileDialog::SELECT_OPEN_FILE) {
284 BrowserThread::PostTask(
285 BrowserThread::FILE, FROM_HERE,
286 base::Bind(&ReadTraceFileCallback,
287 make_scoped_refptr(new TaskProxy(AsWeakPtr())), path));
288 } else {
289 BrowserThread::PostTask(
290 BrowserThread::FILE, FROM_HERE,
291 base::Bind(&WriteTraceFileCallback,
292 make_scoped_refptr(new TaskProxy(AsWeakPtr())), path,
293 trace_data_to_save_.release()));
296 select_trace_file_dialog_ = NULL;
299 void TracingMessageHandler::FileSelectionCanceled(void* params) {
300 select_trace_file_dialog_ = NULL;
301 if (select_trace_file_dialog_type_ ==
302 ui::SelectFileDialog::SELECT_OPEN_FILE) {
303 web_ui()->CallJavascriptFunction(
304 "tracingController.onLoadTraceFileCanceled");
305 } else {
306 web_ui()->CallJavascriptFunction(
307 "tracingController.onSaveTraceFileCanceled");
311 void TracingMessageHandler::OnLoadTraceFile(const base::ListValue* list) {
312 // Only allow a single dialog at a time.
313 if (select_trace_file_dialog_.get())
314 return;
315 select_trace_file_dialog_type_ = ui::SelectFileDialog::SELECT_OPEN_FILE;
316 select_trace_file_dialog_ = ui::SelectFileDialog::Create(
317 this,
318 GetContentClient()->browser()->CreateSelectFilePolicy(
319 web_ui()->GetWebContents()));
320 select_trace_file_dialog_->SelectFile(
321 ui::SelectFileDialog::SELECT_OPEN_FILE,
322 string16(),
323 base::FilePath(),
324 NULL,
326 base::FilePath::StringType(),
327 web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(),
328 NULL);
331 void TracingMessageHandler::LoadTraceFileComplete(string16* contents,
332 const base::FilePath& path) {
333 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
335 // We need to pass contents to tracingController.onLoadTraceFileComplete, but
336 // that may be arbitrarily big, and IPCs messages are limited in size. So we
337 // need to cut it into pieces and rebuild the string in Javascript.
338 // |contents| has already been escaped in ReadTraceFileCallback.
339 // IPC::Channel::kMaximumMessageSize is in bytes, and we need to account for
340 // overhead.
341 const size_t kMaxSize = IPC::Channel::kMaximumMessageSize / 2 - 128;
342 string16 first_prefix = UTF8ToUTF16("window.traceData = '");
343 string16 prefix = UTF8ToUTF16("window.traceData += '");
344 string16 suffix = UTF8ToUTF16("';");
346 RenderViewHost* rvh = web_ui()->GetWebContents()->GetRenderViewHost();
347 for (size_t i = 0; i < contents->size(); i += kMaxSize) {
348 string16 javascript = i == 0 ? first_prefix : prefix;
349 javascript += contents->substr(i, kMaxSize) + suffix;
350 rvh->ExecuteJavascriptInWebFrame(string16(), javascript);
353 // The CallJavascriptFunction is not used because we need to pass
354 // the first param |window.traceData| through as an un-quoted string.
355 rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(
356 "tracingController.onLoadTraceFileComplete(window.traceData," +
357 base::GetDoubleQuotedJson(path.value()) + ");" +
358 "delete window.traceData;"));
361 void TracingMessageHandler::OnSaveTraceFile(const base::ListValue* list) {
362 // Only allow a single dialog at a time.
363 if (select_trace_file_dialog_.get())
364 return;
366 DCHECK(list->GetSize() == 1);
368 std::string* trace_data = new std::string();
369 bool ok = list->GetString(0, trace_data);
370 DCHECK(ok);
371 trace_data_to_save_.reset(trace_data);
373 select_trace_file_dialog_type_ = ui::SelectFileDialog::SELECT_SAVEAS_FILE;
374 select_trace_file_dialog_ = ui::SelectFileDialog::Create(
375 this,
376 GetContentClient()->browser()->CreateSelectFilePolicy(
377 web_ui()->GetWebContents()));
378 select_trace_file_dialog_->SelectFile(
379 ui::SelectFileDialog::SELECT_SAVEAS_FILE,
380 string16(),
381 base::FilePath(),
382 NULL,
384 base::FilePath::StringType(),
385 web_ui()->GetWebContents()->GetView()->GetTopLevelNativeWindow(),
386 NULL);
389 void TracingMessageHandler::SaveTraceFileComplete() {
390 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
391 web_ui()->CallJavascriptFunction("tracingController.onSaveTraceFileComplete");
394 void TracingMessageHandler::OnBeginTracing(const base::ListValue* args) {
395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
396 DCHECK_GE(args->GetSize(), (size_t) 2);
397 DCHECK_LE(args->GetSize(), (size_t) 3);
399 bool system_tracing_requested = false;
400 bool ok = args->GetBoolean(0, &system_tracing_requested);
401 DCHECK(ok);
403 std::string chrome_categories;
404 ok = args->GetString(1, &chrome_categories);
405 DCHECK(ok);
407 base::debug::TraceLog::Options options =
408 base::debug::TraceLog::RECORD_UNTIL_FULL;
409 if (args->GetSize() >= 3) {
410 std::string options_;
411 ok = args->GetString(2, &options_);
412 DCHECK(ok);
413 options = base::debug::TraceLog::TraceOptionsFromString(options_);
416 trace_enabled_ = true;
417 // TODO(jbates) This may fail, but that's OK for current use cases.
418 // Ex: Multiple about:gpu traces can not trace simultaneously.
419 // TODO(nduca) send feedback to javascript about whether or not BeginTracing
420 // was successful.
421 TraceController::GetInstance()->BeginTracing(this, chrome_categories,
422 options);
424 if (system_tracing_requested) {
425 #if defined(OS_CHROMEOS)
426 DCHECK(!system_trace_in_progress_);
427 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
428 StartSystemTracing();
429 // TODO(sleffler) async, could wait for completion
430 system_trace_in_progress_ = true;
431 #endif
435 void TracingMessageHandler::OnEndTracingAsync(const base::ListValue* list) {
436 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
438 // This is really us beginning to end tracing, rather than tracing being truly
439 // over. When this function yields, we expect to get some number of
440 // OnTraceDataCollected callbacks, which will append data to window.traceData.
441 // To set up for this, set window.traceData to the empty string.
442 web_ui()->GetWebContents()->GetRenderViewHost()->
443 ExecuteJavascriptInWebFrame(string16(),
444 UTF8ToUTF16("window.traceData = '';"));
446 // TODO(nduca): fix javascript code to make sure trace_enabled_ is always true
447 // here. triggered a false condition by just clicking stop
448 // trace a few times when it was going slow, and maybe switching
449 // between tabs.
450 if (trace_enabled_ &&
451 !TraceController::GetInstance()->EndTracingAsync(this)) {
452 // Set to false now, since it turns out we never were the trace subscriber.
453 OnEndTracingComplete();
457 void TracingMessageHandler::OnEndTracingComplete() {
458 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
459 trace_enabled_ = false;
460 if (system_trace_in_progress_) {
461 // Disable system tracing now that the local trace has shutdown.
462 // This must be done last because we potentially need to push event
463 // records into the system event log for synchronizing system event
464 // timestamps with chrome event timestamps--and since the system event
465 // log is a ring-buffer (on linux) adding them at the end is the only
466 // way we're confident we'll have them in the final result.
467 system_trace_in_progress_ = false;
468 #if defined(OS_CHROMEOS)
469 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->
470 RequestStopSystemTracing(
471 base::Bind(&TracingMessageHandler::OnEndSystemTracingAck,
472 base::Unretained(this)));
473 return;
474 #endif
477 RenderViewHost* rvh = web_ui()->GetWebContents()->GetRenderViewHost();
478 rvh->ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(
479 "tracingController.onEndTracingComplete(window.traceData);"
480 "delete window.traceData;"));
483 void TracingMessageHandler::OnEndSystemTracingAck(
484 const scoped_refptr<base::RefCountedString>& events_str_ptr) {
485 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
487 web_ui()->CallJavascriptFunction(
488 "tracingController.onSystemTraceDataCollected",
489 *scoped_ptr<base::Value>(new base::StringValue(events_str_ptr->data())));
490 DCHECK(!system_trace_in_progress_);
492 OnEndTracingComplete();
495 void TracingMessageHandler::OnTraceDataCollected(
496 const scoped_refptr<base::RefCountedString>& trace_fragment) {
497 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
499 std::string javascript;
500 javascript.reserve(trace_fragment->size() * 2);
501 javascript.append("window.traceData += \"");
502 base::JsonDoubleQuote(trace_fragment->data(), false, &javascript);
504 // Intentionally append a , to the traceData. This technically causes all
505 // traceData that we pass back to JS to end with a comma, but that is actually
506 // something the JS side strips away anyway
507 javascript.append(",\";");
509 web_ui()->GetWebContents()->GetRenderViewHost()->
510 ExecuteJavascriptInWebFrame(string16(), UTF8ToUTF16(javascript));
513 void TracingMessageHandler::OnTraceBufferPercentFullReply(float percent_full) {
514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
515 web_ui()->CallJavascriptFunction(
516 "tracingController.onRequestBufferPercentFullComplete",
517 *scoped_ptr<base::Value>(new base::FundamentalValue(percent_full)));
520 void TracingMessageHandler::OnGetKnownCategories(const base::ListValue* list) {
521 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
522 if (!TraceController::GetInstance()->GetKnownCategoryGroupsAsync(this)) {
523 std::set<std::string> ret;
524 OnKnownCategoriesCollected(ret);
528 void TracingMessageHandler::OnKnownCategoriesCollected(
529 const std::set<std::string>& known_categories) {
530 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
532 scoped_ptr<base::ListValue> categories(new base::ListValue());
533 for (std::set<std::string>::const_iterator iter = known_categories.begin();
534 iter != known_categories.end();
535 ++iter) {
536 categories->AppendString(*iter);
539 web_ui()->CallJavascriptFunction(
540 "tracingController.onKnownCategoriesCollected", *categories);
543 } // namespace
546 ////////////////////////////////////////////////////////////////////////////////
548 // TracingUI
550 ////////////////////////////////////////////////////////////////////////////////
552 TracingUI::TracingUI(WebUI* web_ui) : WebUIController(web_ui) {
553 web_ui->AddMessageHandler(new TracingMessageHandler());
555 // Set up the chrome://tracing/ source.
556 BrowserContext* browser_context =
557 web_ui->GetWebContents()->GetBrowserContext();
558 WebUIDataSource::Add(browser_context, CreateTracingHTMLSource());
561 } // namespace content