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