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 <set> 8#include <string> 9#include <vector> 10 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/format_macros.h" 17#include "base/json/json_reader.h" 18#include "base/json/json_writer.h" 19#include "base/memory/scoped_ptr.h" 20#include "base/strings/string_number_conversions.h" 21#include "base/strings/string_split.h" 22#include "base/strings/string_util.h" 23#include "base/strings/stringprintf.h" 24#include "base/values.h" 25#include "content/browser/tracing/grit/tracing_resources.h" 26#include "content/browser/tracing/trace_uploader.h" 27#include "content/browser/tracing/tracing_controller_impl.h" 28#include "content/public/browser/browser_context.h" 29#include "content/public/browser/browser_thread.h" 30#include "content/public/browser/tracing_controller.h" 31#include "content/public/browser/web_contents.h" 32#include "content/public/browser/web_ui.h" 33#include "content/public/browser/web_ui_data_source.h" 34#include "content/public/common/content_client.h" 35#include "content/public/common/content_switches.h" 36#include "content/public/common/url_constants.h" 37 38namespace content { 39namespace { 40 41const char kUploadURL[] = "https://clients2.google.com/cr/staging_report"; 42 43void OnGotCategories(const WebUIDataSource::GotDataCallback& callback, 44 const std::set<std::string>& categorySet) { 45 scoped_ptr<base::ListValue> category_list(new base::ListValue()); 46 for (std::set<std::string>::const_iterator it = categorySet.begin(); 47 it != categorySet.end(); it++) { 48 category_list->AppendString(*it); 49 } 50 51 base::RefCountedString* res = new base::RefCountedString(); 52 base::JSONWriter::Write(category_list.get(), &res->data()); 53 callback.Run(res); 54} 55 56bool GetTracingOptions(const std::string& data64, 57 base::debug::CategoryFilter* category_filter, 58 base::debug::TraceOptions* tracing_options) { 59 std::string data; 60 if (!base::Base64Decode(data64, &data)) { 61 LOG(ERROR) << "Options were not base64 encoded."; 62 return false; 63 } 64 65 scoped_ptr<base::Value> optionsRaw(base::JSONReader::Read(data)); 66 if (!optionsRaw) { 67 LOG(ERROR) << "Options were not valid JSON"; 68 return false; 69 } 70 base::DictionaryValue* options; 71 if (!optionsRaw->GetAsDictionary(&options)) { 72 LOG(ERROR) << "Options must be dict"; 73 return false; 74 } 75 76 if (!category_filter) { 77 LOG(ERROR) << "category_filter can't be passed as NULL"; 78 return false; 79 } 80 81 if (!tracing_options) { 82 LOG(ERROR) << "tracing_options can't be passed as NULL"; 83 return false; 84 } 85 86 bool options_ok = true; 87 std::string category_filter_string; 88 options_ok &= options->GetString("categoryFilter", &category_filter_string); 89 *category_filter = base::debug::CategoryFilter(category_filter_string); 90 91 options_ok &= options->GetBoolean("useSystemTracing", 92 &tracing_options->enable_systrace); 93 options_ok &= 94 options->GetBoolean("useSampling", &tracing_options->enable_sampling); 95 96 bool use_continuous_tracing; 97 options_ok &= 98 options->GetBoolean("useContinuousTracing", &use_continuous_tracing); 99 100 if (use_continuous_tracing) 101 tracing_options->record_mode = base::debug::RECORD_CONTINUOUSLY; 102 else 103 tracing_options->record_mode = base::debug::RECORD_UNTIL_FULL; 104 105 if (!options_ok) { 106 LOG(ERROR) << "Malformed options"; 107 return false; 108 } 109 return true; 110} 111 112void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback); 113 114bool BeginRecording(const std::string& data64, 115 const WebUIDataSource::GotDataCallback& callback) { 116 base::debug::CategoryFilter category_filter(""); 117 base::debug::TraceOptions tracing_options; 118 if (!GetTracingOptions(data64, &category_filter, &tracing_options)) 119 return false; 120 121 return TracingController::GetInstance()->EnableRecording( 122 category_filter, 123 tracing_options, 124 base::Bind(&OnRecordingEnabledAck, callback)); 125} 126 127void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) { 128 base::RefCountedString* res = new base::RefCountedString(); 129 callback.Run(res); 130} 131 132void OnTraceBufferPercentFullResult( 133 const WebUIDataSource::GotDataCallback& callback, float result) { 134 std::string str = base::DoubleToString(result); 135 callback.Run(base::RefCountedString::TakeString(&str)); 136} 137 138void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback); 139 140bool EnableMonitoring(const std::string& data64, 141 const WebUIDataSource::GotDataCallback& callback) { 142 base::debug::TraceOptions tracing_options; 143 base::debug::CategoryFilter category_filter(""); 144 if (!GetTracingOptions(data64, &category_filter, &tracing_options)) 145 return false; 146 147 return TracingController::GetInstance()->EnableMonitoring( 148 category_filter, 149 tracing_options, 150 base::Bind(OnMonitoringEnabledAck, callback)); 151} 152 153void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback) { 154 base::RefCountedString* res = new base::RefCountedString(); 155 callback.Run(res); 156} 157 158void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback& callback) { 159 base::RefCountedString* res = new base::RefCountedString(); 160 callback.Run(res); 161} 162 163void GetMonitoringStatus(const WebUIDataSource::GotDataCallback& callback) { 164 bool is_monitoring; 165 base::debug::CategoryFilter category_filter(""); 166 base::debug::TraceOptions options; 167 TracingController::GetInstance()->GetMonitoringStatus( 168 &is_monitoring, &category_filter, &options); 169 170 scoped_ptr<base::DictionaryValue> 171 monitoring_options(new base::DictionaryValue()); 172 monitoring_options->SetBoolean("isMonitoring", is_monitoring); 173 monitoring_options->SetString("categoryFilter", category_filter.ToString()); 174 monitoring_options->SetBoolean("useSystemTracing", options.enable_systrace); 175 monitoring_options->SetBoolean( 176 "useContinuousTracing", 177 options.record_mode == base::debug::RECORD_CONTINUOUSLY); 178 monitoring_options->SetBoolean("useSampling", options.enable_sampling); 179 180 std::string monitoring_options_json; 181 base::JSONWriter::Write(monitoring_options.get(), &monitoring_options_json); 182 183 base::RefCountedString* monitoring_options_base64 = 184 new base::RefCountedString(); 185 base::Base64Encode(monitoring_options_json, 186 &monitoring_options_base64->data()); 187 callback.Run(monitoring_options_base64); 188} 189 190void TracingCallbackWrapper(const WebUIDataSource::GotDataCallback& callback, 191 base::RefCountedString* data) { 192 callback.Run(data); 193} 194 195bool OnBeginJSONRequest(const std::string& path, 196 const WebUIDataSource::GotDataCallback& callback) { 197 if (path == "json/categories") { 198 return TracingController::GetInstance()->GetCategories( 199 base::Bind(OnGotCategories, callback)); 200 } 201 202 const char* beginRecordingPath = "json/begin_recording?"; 203 if (StartsWithASCII(path, beginRecordingPath, true)) { 204 std::string data = path.substr(strlen(beginRecordingPath)); 205 return BeginRecording(data, callback); 206 } 207 if (path == "json/get_buffer_percent_full") { 208 return TracingController::GetInstance()->GetTraceBufferPercentFull( 209 base::Bind(OnTraceBufferPercentFullResult, callback)); 210 } 211 if (path == "json/end_recording") { 212 return TracingController::GetInstance()->DisableRecording( 213 TracingControllerImpl::CreateStringSink( 214 base::Bind(TracingCallbackWrapper, callback))); 215 } 216 217 const char* enableMonitoringPath = "json/begin_monitoring?"; 218 if (path.find(enableMonitoringPath) == 0) { 219 std::string data = path.substr(strlen(enableMonitoringPath)); 220 return EnableMonitoring(data, callback); 221 } 222 if (path == "json/end_monitoring") { 223 return TracingController::GetInstance()->DisableMonitoring( 224 base::Bind(OnMonitoringDisabled, callback)); 225 } 226 if (path == "json/capture_monitoring") { 227 TracingController::GetInstance()->CaptureMonitoringSnapshot( 228 TracingControllerImpl::CreateStringSink( 229 base::Bind(TracingCallbackWrapper, callback))); 230 return true; 231 } 232 if (path == "json/get_monitoring_status") { 233 GetMonitoringStatus(callback); 234 return true; 235 } 236 237 LOG(ERROR) << "Unhandled request to " << path; 238 return false; 239} 240 241bool OnTracingRequest(const std::string& path, 242 const WebUIDataSource::GotDataCallback& callback) { 243 if (StartsWithASCII(path, "json/", true)) { 244 if (!OnBeginJSONRequest(path, callback)) { 245 std::string error("##ERROR##"); 246 callback.Run(base::RefCountedString::TakeString(&error)); 247 } 248 return true; 249 } 250 return false; 251} 252 253} // namespace 254 255 256//////////////////////////////////////////////////////////////////////////////// 257// 258// TracingUI 259// 260//////////////////////////////////////////////////////////////////////////////// 261 262TracingUI::TracingUI(WebUI* web_ui) 263 : WebUIController(web_ui), 264 weak_factory_(this) { 265 web_ui->RegisterMessageCallback( 266 "doUpload", 267 base::Bind(&TracingUI::DoUpload, base::Unretained(this))); 268 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 291void TracingUI::DoUpload(const base::ListValue* args) { 292 const base::CommandLine& command_line = 293 *base::CommandLine::ForCurrentProcess(); 294 std::string upload_url = kUploadURL; 295 if (command_line.HasSwitch(switches::kTraceUploadURL)) { 296 upload_url = 297 command_line.GetSwitchValueASCII(switches::kTraceUploadURL); 298 } 299 if (!GURL(upload_url).is_valid()) { 300 upload_url.clear(); 301 } 302 303 if (upload_url.empty()) { 304 web_ui()->CallJavascriptFunction("onUploadError", 305 base::StringValue("Upload URL empty or invalid")); 306 return; 307 } 308 309 std::string file_contents; 310 if (!args || args->empty() || !args->GetString(0, &file_contents)) { 311 web_ui()->CallJavascriptFunction("onUploadError", 312 base::StringValue("Missing data")); 313 return; 314 } 315 316 TraceUploader::UploadProgressCallback progress_callback = 317 base::Bind(&TracingUI::OnTraceUploadProgress, 318 weak_factory_.GetWeakPtr()); 319 TraceUploader::UploadDoneCallback done_callback = 320 base::Bind(&TracingUI::OnTraceUploadComplete, 321 weak_factory_.GetWeakPtr()); 322 323#if defined(OS_WIN) 324 const char product[] = "Chrome"; 325#elif defined(OS_MACOSX) 326 const char product[] = "Chrome_Mac"; 327#elif defined(OS_LINUX) 328 const char product[] = "Chrome_Linux"; 329#elif defined(OS_ANDROID) 330 const char product[] = "Chrome_Android"; 331#elif defined(OS_CHROMEOS) 332 const char product[] = "Chrome_ChromeOS"; 333#else 334#error Platform not supported. 335#endif 336 337 // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out 338 // the part before the "/". 339 std::vector<std::string> product_components; 340 base::SplitString(content::GetContentClient()->GetProduct(), '/', 341 &product_components); 342 DCHECK_EQ(2U, product_components.size()); 343 std::string version; 344 if (product_components.size() == 2U) { 345 version = product_components[1]; 346 } else { 347 version = "unknown"; 348 } 349 350 BrowserContext* browser_context = 351 web_ui()->GetWebContents()->GetBrowserContext(); 352 TraceUploader* uploader = new TraceUploader( 353 product, version, upload_url, browser_context->GetRequestContext()); 354 355 BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind( 356 &TraceUploader::DoUpload, 357 base::Unretained(uploader), 358 file_contents, 359 progress_callback, 360 done_callback)); 361 // TODO(mmandlis): Add support for stopping the upload in progress. 362} 363 364void TracingUI::OnTraceUploadProgress(int64 current, int64 total) { 365 DCHECK(current <= total); 366 int percent = (current / total) * 100; 367 web_ui()->CallJavascriptFunction( 368 "onUploadProgress", 369 base::FundamentalValue(percent), 370 base::StringValue(base::StringPrintf("%" PRId64, current)), 371 base::StringValue(base::StringPrintf("%" PRId64, total))); 372} 373 374void TracingUI::OnTraceUploadComplete(bool success, 375 const std::string& report_id, 376 const std::string& error_message) { 377 if (success) { 378 web_ui()->CallJavascriptFunction("onUploadComplete", 379 base::StringValue(report_id)); 380 } else { 381 web_ui()->CallJavascriptFunction("onUploadError", 382 base::StringValue(error_message)); 383 } 384} 385 386} // namespace content 387