blob: 03b1eb0ec123447d26ceb485f0a5b8cd32fdb17b [file] [log] [blame]
// 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 <stddef.h>
#include <memory>
#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/format_macros.h"
#include "base/json/json_reader.h"
#include "base/json/json_writer.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/trace_event/trace_event.h"
#include "base/values.h"
#include "content/browser/tracing/grit/tracing_resources.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/content_browser_client.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/trace_uploader.h"
#include "content/public/browser/tracing_controller.h"
#include "content/public/browser/tracing_delegate.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/url_constants.h"
namespace content {
namespace {
void OnGotCategories(const WebUIDataSource::GotDataCallback& callback,
const std::set<std::string>& categorySet) {
base::ListValue category_list;
for (std::set<std::string>::const_iterator it = categorySet.begin();
it != categorySet.end(); it++) {
category_list.AppendString(*it);
}
scoped_refptr<base::RefCountedString> res(new base::RefCountedString());
base::JSONWriter::Write(category_list, &res->data());
callback.Run(res);
}
void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback);
bool BeginRecording(const std::string& data64,
const WebUIDataSource::GotDataCallback& callback) {
base::trace_event::TraceConfig trace_config("", "");
if (!TracingUI::GetTracingOptions(data64, &trace_config))
return false;
return TracingController::GetInstance()->StartTracing(
trace_config,
base::Bind(&OnRecordingEnabledAck, callback));
}
void OnRecordingEnabledAck(const WebUIDataSource::GotDataCallback& callback) {
callback.Run(
scoped_refptr<base::RefCountedMemory>(new base::RefCountedString()));
}
void OnTraceBufferUsageResult(const WebUIDataSource::GotDataCallback& callback,
float percent_full,
size_t approximate_event_count) {
std::string str = base::DoubleToString(percent_full);
callback.Run(base::RefCountedString::TakeString(&str));
}
void OnTraceBufferStatusResult(const WebUIDataSource::GotDataCallback& callback,
float percent_full,
size_t approximate_event_count) {
base::DictionaryValue status;
status.SetDouble("percentFull", percent_full);
status.SetInteger("approximateEventCount", approximate_event_count);
std::string status_json;
base::JSONWriter::Write(status, &status_json);
base::RefCountedString* status_base64 = new base::RefCountedString();
base::Base64Encode(status_json, &status_base64->data());
callback.Run(status_base64);
}
void TracingCallbackWrapperBase64(
const WebUIDataSource::GotDataCallback& callback,
std::unique_ptr<const base::DictionaryValue> metadata,
base::RefCountedString* data) {
base::RefCountedString* data_base64 = new base::RefCountedString();
base::Base64Encode(data->data(), &data_base64->data());
callback.Run(data_base64);
}
bool OnBeginJSONRequest(const std::string& path,
const WebUIDataSource::GotDataCallback& callback) {
if (path == "json/categories") {
return TracingController::GetInstance()->GetCategories(
base::Bind(OnGotCategories, callback));
}
const char kBeginRecordingPath[] = "json/begin_recording?";
if (base::StartsWith(path, kBeginRecordingPath,
base::CompareCase::SENSITIVE)) {
std::string data = path.substr(strlen(kBeginRecordingPath));
return BeginRecording(data, callback);
}
if (path == "json/get_buffer_percent_full") {
return TracingController::GetInstance()->GetTraceBufferUsage(
base::Bind(OnTraceBufferUsageResult, callback));
}
if (path == "json/get_buffer_status") {
return TracingController::GetInstance()->GetTraceBufferUsage(
base::Bind(OnTraceBufferStatusResult, callback));
}
if (path == "json/end_recording_compressed") {
if (!TracingController::GetInstance()->IsTracing())
return false;
scoped_refptr<TracingControllerImpl::TraceDataSink> data_sink =
TracingControllerImpl::CreateCompressedStringSink(
TracingControllerImpl::CreateCallbackEndpoint(
base::Bind(TracingCallbackWrapperBase64, callback)),
false /* compress_with_background_priority */);
return TracingController::GetInstance()->StopTracing(data_sink);
}
LOG(ERROR) << "Unhandled request to " << path;
return false;
}
bool OnTracingRequest(const std::string& path,
const WebUIDataSource::GotDataCallback& callback) {
if (base::StartsWith(path, "json/", base::CompareCase::SENSITIVE)) {
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),
delegate_(GetContentClient()->browser()->GetTracingDelegate()),
weak_factory_(this) {
web_ui->RegisterMessageCallback(
"doUpload",
base::Bind(&TracingUI::DoUpload, base::Unretained(this)));
web_ui->RegisterMessageCallback(
"doUploadBase64",
base::Bind(&TracingUI::DoUploadBase64Encoded, base::Unretained(this)));
// Set up the chrome://tracing/ source.
BrowserContext* browser_context =
web_ui->GetWebContents()->GetBrowserContext();
WebUIDataSource* source = WebUIDataSource::Create(kChromeUITracingHost);
source->UseGzip({"json/begin_recording", "json/categories",
"json/end_recording_compressed",
"json/get_buffer_percent_full", "json/get_buffer_status"});
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::DoUploadBase64Encoded(const base::ListValue* args) {
std::string file_contents_base64;
if (!args || args->empty() || !args->GetString(0, &file_contents_base64)) {
web_ui()->CallJavascriptFunctionUnsafe("onUploadError",
base::Value("Missing data"));
return;
}
std::string file_contents;
base::Base64Decode(file_contents_base64, &file_contents);
// doUploadBase64 is used to upload binary data which is assumed to already
// be compressed.
DoUploadInternal(file_contents, TraceUploader::UNCOMPRESSED_UPLOAD);
}
void TracingUI::DoUpload(const base::ListValue* args) {
std::string file_contents;
if (!args || args->empty() || !args->GetString(0, &file_contents)) {
web_ui()->CallJavascriptFunctionUnsafe("onUploadError",
base::Value("Missing data"));
return;
}
DoUploadInternal(file_contents, TraceUploader::COMPRESSED_UPLOAD);
}
void TracingUI::DoUploadInternal(const std::string& file_contents,
TraceUploader::UploadMode upload_mode) {
if (!delegate_) {
web_ui()->CallJavascriptFunctionUnsafe("onUploadError",
base::Value("Not implemented"));
return;
}
if (trace_uploader_) {
web_ui()->CallJavascriptFunctionUnsafe("onUploadError",
base::Value("Upload in progress"));
return;
}
TraceUploader::UploadProgressCallback progress_callback =
base::Bind(&TracingUI::OnTraceUploadProgress,
weak_factory_.GetWeakPtr());
TraceUploader::UploadDoneCallback done_callback =
base::Bind(&TracingUI::OnTraceUploadComplete,
weak_factory_.GetWeakPtr());
trace_uploader_ = delegate_->GetTraceUploader(
BrowserContext::GetDefaultStoragePartition(
web_ui()->GetWebContents()->GetBrowserContext())->
GetURLRequestContext());
DCHECK(trace_uploader_);
trace_uploader_->DoUpload(file_contents, upload_mode, nullptr,
progress_callback, done_callback);
// TODO(mmandlis): Add support for stopping the upload in progress.
}
// static
bool TracingUI::GetTracingOptions(
const std::string& data64,
base::trace_event::TraceConfig* trace_config) {
std::string data;
if (!base::Base64Decode(data64, &data)) {
LOG(ERROR) << "Options were not base64 encoded.";
return false;
}
std::unique_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;
}
if (!trace_config) {
LOG(ERROR) << "trace_config can't be passed as NULL";
return false;
}
// New style options dictionary.
if (options->HasKey("included_categories")) {
*trace_config = base::trace_event::TraceConfig(*options);
return true;
}
bool options_ok = true;
std::string category_filter_string;
options_ok &= options->GetString("categoryFilter", &category_filter_string);
std::string record_mode;
options_ok &= options->GetString("tracingRecordMode", &record_mode);
*trace_config =
base::trace_event::TraceConfig(category_filter_string, record_mode);
bool enable_systrace;
options_ok &= options->GetBoolean("useSystemTracing", &enable_systrace);
if (enable_systrace)
trace_config->EnableSystrace();
if (!options_ok) {
LOG(ERROR) << "Malformed options";
return false;
}
return true;
}
void TracingUI::OnTraceUploadProgress(int64_t current, int64_t total) {
DCHECK(current <= total);
int percent = (current / total) * 100;
web_ui()->CallJavascriptFunctionUnsafe(
"onUploadProgress", base::Value(percent),
base::Value(base::StringPrintf("%" PRId64, current)),
base::Value(base::StringPrintf("%" PRId64, total)));
}
void TracingUI::OnTraceUploadComplete(bool success,
const std::string& feedback) {
if (success) {
web_ui()->CallJavascriptFunctionUnsafe("onUploadComplete",
base::Value(feedback));
} else {
web_ui()->CallJavascriptFunctionUnsafe("onUploadError",
base::Value(feedback));
}
trace_uploader_.reset();
}
} // namespace content