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