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 38 namespace content { 39 namespace { 40 41 const char kUploadURL[] = "https://clients2.google.com/cr/staging_report"; 42 43 void 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 56 bool 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 112 void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback); 113 114 bool 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 127 void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) { 128 base::RefCountedString* res = new base::RefCountedString(); 129 callback.Run(res); 130 } 131 132 void OnTraceBufferPercentFullResult( 133 const WebUIDataSource::GotDataCallback& callback, float result) { 134 std::string str = base::DoubleToString(result); 135 callback.Run(base::RefCountedString::TakeString(&str)); 136 } 137 138 void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback); 139 140 bool 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 153 void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback) { 154 base::RefCountedString* res = new base::RefCountedString(); 155 callback.Run(res); 156 } 157 158 void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback& callback) { 159 base::RefCountedString* res = new base::RefCountedString(); 160 callback.Run(res); 161 } 162 163 void 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 190 void TracingCallbackWrapper(const WebUIDataSource::GotDataCallback& callback, 191 base::RefCountedString* data) { 192 callback.Run(data); 193 } 194 195 bool 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 241 bool 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 262 TracingUI::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 282 TracingUI::~TracingUI() { 283 TracingControllerImpl::GetInstance()->UnregisterTracingUI(this); 284 } 285 286 void TracingUI::OnMonitoringStateChanged(bool is_monitoring) { 287 web_ui()->CallJavascriptFunction( 288 "onMonitoringStateChanged", base::FundamentalValue(is_monitoring)); 289 } 290 291 void 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 364 void 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 374 void 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