| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/tracing/tracing_ui.h" |
| |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/base64.h" |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/command_line.h" |
| #include "base/file_util.h" |
| #include "base/format_macros.h" |
| #include "base/json/json_reader.h" |
| #include "base/json/json_writer.h" |
| #include "base/memory/scoped_ptr.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_split.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "content/browser/tracing/grit/tracing_resources.h" |
| #include "content/browser/tracing/trace_uploader.h" |
| #include "content/browser/tracing/tracing_controller_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/tracing_controller.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_ui.h" |
| #include "content/public/browser/web_ui_data_source.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/content_switches.h" |
| #include "content/public/common/url_constants.h" |
| |
| namespace content { |
| namespace { |
| |
| const char kUploadURL[] = "https://clients2.google.com/cr/staging_report"; |
| |
| void OnGotCategories(const WebUIDataSource::GotDataCallback& callback, |
| const std::set<std::string>& categorySet) { |
| scoped_ptr<base::ListValue> category_list(new base::ListValue()); |
| for (std::set<std::string>::const_iterator it = categorySet.begin(); |
| it != categorySet.end(); it++) { |
| category_list->AppendString(*it); |
| } |
| |
| base::RefCountedString* res = new base::RefCountedString(); |
| base::JSONWriter::Write(category_list.get(), &res->data()); |
| callback.Run(res); |
| } |
| |
| bool GetTracingOptions(const std::string& data64, |
| std::string* category_filter_string, |
| int* tracing_options) { |
| std::string data; |
| if (!base::Base64Decode(data64, &data)) { |
| LOG(ERROR) << "Options were not base64 encoded."; |
| return false; |
| } |
| |
| scoped_ptr<base::Value> optionsRaw(base::JSONReader::Read(data)); |
| if (!optionsRaw) { |
| LOG(ERROR) << "Options were not valid JSON"; |
| return false; |
| } |
| base::DictionaryValue* options; |
| if (!optionsRaw->GetAsDictionary(&options)) { |
| LOG(ERROR) << "Options must be dict"; |
| return false; |
| } |
| |
| bool use_system_tracing; |
| bool use_continuous_tracing; |
| bool use_sampling; |
| |
| bool options_ok = true; |
| options_ok &= options->GetString("categoryFilter", category_filter_string); |
| options_ok &= options->GetBoolean("useSystemTracing", &use_system_tracing); |
| options_ok &= options->GetBoolean("useContinuousTracing", |
| &use_continuous_tracing); |
| options_ok &= options->GetBoolean("useSampling", &use_sampling); |
| if (!options_ok) { |
| LOG(ERROR) << "Malformed options"; |
| return false; |
| } |
| |
| *tracing_options = 0; |
| if (use_system_tracing) |
| *tracing_options |= TracingController::ENABLE_SYSTRACE; |
| if (use_sampling) |
| *tracing_options |= TracingController::ENABLE_SAMPLING; |
| if (use_continuous_tracing) |
| *tracing_options |= TracingController::RECORD_CONTINUOUSLY; |
| return true; |
| } |
| |
| void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback); |
| |
| bool BeginRecording(const std::string& data64, |
| const WebUIDataSource::GotDataCallback& callback) { |
| std::string category_filter_string; |
| int tracing_options = 0; |
| if (!GetTracingOptions(data64, &category_filter_string, &tracing_options)) |
| return false; |
| |
| return TracingController::GetInstance()->EnableRecording( |
| category_filter_string, |
| static_cast<TracingController::Options>(tracing_options), |
| base::Bind(&OnRecordingEnabledAck, callback)); |
| } |
| |
| void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) { |
| base::RefCountedString* res = new base::RefCountedString(); |
| callback.Run(res); |
| } |
| |
| void OnTraceBufferPercentFullResult( |
| const WebUIDataSource::GotDataCallback& callback, float result) { |
| std::string str = base::DoubleToString(result); |
| callback.Run(base::RefCountedString::TakeString(&str)); |
| } |
| |
| void ReadRecordingResult(const WebUIDataSource::GotDataCallback& callback, |
| const base::FilePath& path) { |
| std::string tmp; |
| if (!base::ReadFileToString(path, &tmp)) |
| LOG(ERROR) << "Failed to read file " << path.value(); |
| base::DeleteFile(path, false); |
| callback.Run(base::RefCountedString::TakeString(&tmp)); |
| } |
| |
| void BeginReadingRecordingResult( |
| const WebUIDataSource::GotDataCallback& callback, |
| const base::FilePath& path) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(ReadRecordingResult, callback, path)); |
| } |
| |
| void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback); |
| |
| bool EnableMonitoring(const std::string& data64, |
| const WebUIDataSource::GotDataCallback& callback) { |
| std::string category_filter_string; |
| int tracing_options = 0; |
| if (!GetTracingOptions(data64, &category_filter_string, &tracing_options)) |
| return false; |
| |
| return TracingController::GetInstance()->EnableMonitoring( |
| category_filter_string, |
| static_cast<TracingController::Options>(tracing_options), |
| base::Bind(OnMonitoringEnabledAck, callback)); |
| } |
| |
| void OnMonitoringEnabledAck(const WebUIDataSource::GotDataCallback& callback) { |
| base::RefCountedString* res = new base::RefCountedString(); |
| callback.Run(res); |
| } |
| |
| void OnMonitoringDisabled(const WebUIDataSource::GotDataCallback& callback) { |
| base::RefCountedString* res = new base::RefCountedString(); |
| callback.Run(res); |
| } |
| |
| void GetMonitoringStatus(const WebUIDataSource::GotDataCallback& callback) { |
| bool is_monitoring; |
| std::string category_filter; |
| TracingController::Options options; |
| TracingController::GetInstance()->GetMonitoringStatus( |
| &is_monitoring, &category_filter, &options); |
| |
| scoped_ptr<base::DictionaryValue> |
| monitoring_options(new base::DictionaryValue()); |
| monitoring_options->SetBoolean("isMonitoring", is_monitoring); |
| monitoring_options->SetString("categoryFilter", category_filter); |
| monitoring_options->SetBoolean("useSystemTracing", |
| (options & TracingController::ENABLE_SYSTRACE) != 0); |
| monitoring_options->SetBoolean("useContinuousTracing", |
| (options & TracingController::RECORD_CONTINUOUSLY) != 0); |
| monitoring_options->SetBoolean("useSampling", |
| (options & TracingController::ENABLE_SAMPLING) != 0); |
| |
| std::string monitoring_options_json; |
| base::JSONWriter::Write(monitoring_options.get(), &monitoring_options_json); |
| |
| base::RefCountedString* monitoring_options_base64 = |
| new base::RefCountedString(); |
| base::Base64Encode(monitoring_options_json, |
| &monitoring_options_base64->data()); |
| callback.Run(monitoring_options_base64); |
| } |
| |
| void ReadMonitoringSnapshot(const WebUIDataSource::GotDataCallback& callback, |
| const base::FilePath& path) { |
| std::string tmp; |
| if (!base::ReadFileToString(path, &tmp)) |
| LOG(ERROR) << "Failed to read file " << path.value(); |
| base::DeleteFile(path, false); |
| callback.Run(base::RefCountedString::TakeString(&tmp)); |
| } |
| |
| void OnMonitoringSnapshotCaptured( |
| const WebUIDataSource::GotDataCallback& callback, |
| const base::FilePath& path) { |
| BrowserThread::PostTask( |
| BrowserThread::FILE, FROM_HERE, |
| base::Bind(ReadMonitoringSnapshot, callback, path)); |
| } |
| |
| bool OnBeginJSONRequest(const std::string& path, |
| const WebUIDataSource::GotDataCallback& callback) { |
| if (path == "json/categories") { |
| return TracingController::GetInstance()->GetCategories( |
| base::Bind(OnGotCategories, callback)); |
| } |
| |
| const char* beginRecordingPath = "json/begin_recording?"; |
| if (StartsWithASCII(path, beginRecordingPath, true)) { |
| std::string data = path.substr(strlen(beginRecordingPath)); |
| return BeginRecording(data, callback); |
| } |
| if (path == "json/get_buffer_percent_full") { |
| return TracingController::GetInstance()->GetTraceBufferPercentFull( |
| base::Bind(OnTraceBufferPercentFullResult, callback)); |
| } |
| if (path == "json/end_recording") { |
| return TracingController::GetInstance()->DisableRecording( |
| base::FilePath(), base::Bind(BeginReadingRecordingResult, callback)); |
| } |
| |
| const char* enableMonitoringPath = "json/begin_monitoring?"; |
| if (path.find(enableMonitoringPath) == 0) { |
| std::string data = path.substr(strlen(enableMonitoringPath)); |
| return EnableMonitoring(data, callback); |
| } |
| if (path == "json/end_monitoring") { |
| return TracingController::GetInstance()->DisableMonitoring( |
| base::Bind(OnMonitoringDisabled, callback)); |
| } |
| if (path == "json/capture_monitoring") { |
| TracingController::GetInstance()->CaptureMonitoringSnapshot( |
| base::FilePath(), base::Bind(OnMonitoringSnapshotCaptured, callback)); |
| return true; |
| } |
| if (path == "json/get_monitoring_status") { |
| GetMonitoringStatus(callback); |
| return true; |
| } |
| |
| LOG(ERROR) << "Unhandled request to " << path; |
| return false; |
| } |
| |
| bool OnTracingRequest(const std::string& path, |
| const WebUIDataSource::GotDataCallback& callback) { |
| if (StartsWithASCII(path, "json/", true)) { |
| if (!OnBeginJSONRequest(path, callback)) { |
| std::string error("##ERROR##"); |
| callback.Run(base::RefCountedString::TakeString(&error)); |
| } |
| return true; |
| } |
| return false; |
| } |
| |
| } // namespace |
| |
| |
| //////////////////////////////////////////////////////////////////////////////// |
| // |
| // TracingUI |
| // |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| TracingUI::TracingUI(WebUI* web_ui) |
| : WebUIController(web_ui), |
| weak_factory_(this) { |
| web_ui->RegisterMessageCallback( |
| "doUpload", |
| base::Bind(&TracingUI::DoUpload, base::Unretained(this))); |
| |
| // Set up the chrome://tracing/ source. |
| BrowserContext* browser_context = |
| web_ui->GetWebContents()->GetBrowserContext(); |
| |
| WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost); |
| source->SetJsonPath("strings.js"); |
| source->SetDefaultResource(IDR_TRACING_HTML); |
| source->AddResourcePath("tracing.js", IDR_TRACING_JS); |
| source->SetRequestFilter(base::Bind(OnTracingRequest)); |
| WebUIDataSource::Add(browser_context, source); |
| TracingControllerImpl::GetInstance()->RegisterTracingUI(this); |
| } |
| |
| TracingUI::~TracingUI() { |
| TracingControllerImpl::GetInstance()->UnregisterTracingUI(this); |
| } |
| |
| void TracingUI::OnMonitoringStateChanged(bool is_monitoring) { |
| web_ui()->CallJavascriptFunction( |
| "onMonitoringStateChanged", base::FundamentalValue(is_monitoring)); |
| } |
| |
| void TracingUI::DoUpload(const base::ListValue* args) { |
| const CommandLine& command_line = *CommandLine::ForCurrentProcess(); |
| std::string upload_url = kUploadURL; |
| if (command_line.HasSwitch(switches::kTraceUploadURL)) { |
| upload_url = |
| command_line.GetSwitchValueASCII(switches::kTraceUploadURL); |
| } |
| if (!GURL(upload_url).is_valid()) { |
| upload_url.clear(); |
| } |
| |
| if (upload_url.empty()) { |
| web_ui()->CallJavascriptFunction("onUploadError", |
| base::StringValue("Upload URL empty or invalid")); |
| return; |
| } |
| |
| std::string file_contents; |
| if (!args || args->empty() || !args->GetString(0, &file_contents)) { |
| web_ui()->CallJavascriptFunction("onUploadError", |
| base::StringValue("Missing data")); |
| return; |
| } |
| |
| TraceUploader::UploadProgressCallback progress_callback = |
| base::Bind(&TracingUI::OnTraceUploadProgress, |
| weak_factory_.GetWeakPtr()); |
| TraceUploader::UploadDoneCallback done_callback = |
| base::Bind(&TracingUI::OnTraceUploadComplete, |
| weak_factory_.GetWeakPtr()); |
| |
| #if defined(OS_WIN) |
| const char product[] = "Chrome"; |
| #elif defined(OS_MACOSX) |
| const char product[] = "Chrome_Mac"; |
| #elif defined(OS_LINUX) |
| const char product[] = "Chrome_Linux"; |
| #elif defined(OS_ANDROID) |
| const char product[] = "Chrome_Android"; |
| #elif defined(OS_CHROMEOS) |
| const char product[] = "Chrome_ChromeOS"; |
| #else |
| #error Platform not supported. |
| #endif |
| |
| // GetProduct() returns a string like "Chrome/aa.bb.cc.dd", split out |
| // the part before the "/". |
| std::vector<std::string> product_components; |
| base::SplitString(content::GetContentClient()->GetProduct(), '/', |
| &product_components); |
| DCHECK_EQ(2U, product_components.size()); |
| std::string version; |
| if (product_components.size() == 2U) { |
| version = product_components[1]; |
| } else { |
| version = "unknown"; |
| } |
| |
| BrowserContext* browser_context = |
| web_ui()->GetWebContents()->GetBrowserContext(); |
| TraceUploader* uploader = new TraceUploader( |
| product, version, upload_url, browser_context->GetRequestContext()); |
| |
| BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE, base::Bind( |
| &TraceUploader::DoUpload, |
| base::Unretained(uploader), |
| file_contents, |
| progress_callback, |
| done_callback)); |
| // TODO(mmandlis): Add support for stopping the upload in progress. |
| } |
| |
| void TracingUI::OnTraceUploadProgress(int64 current, int64 total) { |
| DCHECK(current <= total); |
| int percent = (current / total) * 100; |
| web_ui()->CallJavascriptFunction( |
| "onUploadProgress", |
| base::FundamentalValue(percent), |
| base::StringValue(base::StringPrintf("%" PRId64, current)), |
| base::StringValue(base::StringPrintf("%" PRId64, total))); |
| } |
| |
| void TracingUI::OnTraceUploadComplete(bool success, |
| const std::string& report_id, |
| const std::string& error_message) { |
| if (success) { |
| web_ui()->CallJavascriptFunction("onUploadComplete", |
| base::StringValue(report_id)); |
| } else { |
| web_ui()->CallJavascriptFunction("onUploadError", |
| base::StringValue(error_message)); |
| } |
| } |
| |
| } // namespace content |