// 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/devtools/devtools_tracing_handler.h"
#include <cmath>
#include "base/bind.h"
#include "base/debug/trace_event_impl.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/values.h"
#include "content/browser/devtools/devtools_http_handler_impl.h"
#include "content/browser/devtools/devtools_protocol_constants.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/tracing_controller.h"
namespace content {
namespace {
const char kRecordUntilFull[] = "record-until-full";
const char kRecordContinuously[] = "record-continuously";
const char kRecordAsMuchAsPossible[] = "record-as-much-as-possible";
const char kEnableSampling[] = "enable-sampling";
class DevToolsTraceSinkProxy : public TracingController::TraceDataSink {
public:
explicit DevToolsTraceSinkProxy(base::WeakPtr<DevToolsTracingHandler> handler)
: tracing_handler_(handler) {}
virtual void AddTraceChunk(const std::string& chunk) OVERRIDE {
if (DevToolsTracingHandler* h = tracing_handler_.get())
h->OnTraceDataCollected(chunk);
}
virtual void Close() OVERRIDE {
if (DevToolsTracingHandler* h = tracing_handler_.get())
h->OnTraceComplete();
}
private:
virtual ~DevToolsTraceSinkProxy() {}
base::WeakPtr<DevToolsTracingHandler> tracing_handler_;
};
} // namespace
const char* DevToolsTracingHandler::kDefaultCategories =
"-*,disabled-by-default-devtools.timeline*";
const double DevToolsTracingHandler::kDefaultReportingInterval = 1000.0;
const double DevToolsTracingHandler::kMinimumReportingInterval = 250.0;
DevToolsTracingHandler::DevToolsTracingHandler(
DevToolsTracingHandler::Target target)
: target_(target), is_recording_(false), weak_factory_(this) {
RegisterCommandHandler(devtools::Tracing::start::kName,
base::Bind(&DevToolsTracingHandler::OnStart,
base::Unretained(this)));
RegisterCommandHandler(devtools::Tracing::end::kName,
base::Bind(&DevToolsTracingHandler::OnEnd,
base::Unretained(this)));
RegisterCommandHandler(devtools::Tracing::getCategories::kName,
base::Bind(&DevToolsTracingHandler::OnGetCategories,
base::Unretained(this)));
}
DevToolsTracingHandler::~DevToolsTracingHandler() {
}
void DevToolsTracingHandler::OnTraceDataCollected(
const std::string& trace_fragment) {
// Hand-craft protocol notification message so we can substitute JSON
// that we already got as string as a bare object, not a quoted string.
std::string message =
base::StringPrintf("{ \"method\": \"%s\", \"params\": { \"%s\": [",
devtools::Tracing::dataCollected::kName,
devtools::Tracing::dataCollected::kParamValue);
const size_t messageSuffixSize = 10;
message.reserve(message.size() + trace_fragment.size() + messageSuffixSize);
message += trace_fragment;
message += "] } }", SendRawMessage(message);
}
void DevToolsTracingHandler::OnTraceComplete() {
SendNotification(devtools::Tracing::tracingComplete::kName, NULL);
}
base::debug::TraceOptions DevToolsTracingHandler::TraceOptionsFromString(
const std::string& options) {
std::vector<std::string> split;
std::vector<std::string>::iterator iter;
base::debug::TraceOptions ret;
base::SplitString(options, ',', &split);
for (iter = split.begin(); iter != split.end(); ++iter) {
if (*iter == kRecordUntilFull) {
ret.record_mode = base::debug::RECORD_UNTIL_FULL;
} else if (*iter == kRecordContinuously) {
ret.record_mode = base::debug::RECORD_CONTINUOUSLY;
} else if (*iter == kRecordAsMuchAsPossible) {
ret.record_mode = base::debug::RECORD_AS_MUCH_AS_POSSIBLE;
} else if (*iter == kEnableSampling) {
ret.enable_sampling = true;
}
}
return ret;
}
scoped_refptr<DevToolsProtocol::Response>
DevToolsTracingHandler::OnStart(
scoped_refptr<DevToolsProtocol::Command> command) {
if (is_recording_) {
return command->InternalErrorResponse("Tracing is already started");
}
is_recording_ = true;
std::string categories;
base::debug::TraceOptions options;
double usage_reporting_interval = 0.0;
base::DictionaryValue* params = command->params();
if (params) {
params->GetString(devtools::Tracing::start::kParamCategories, &categories);
std::string options_param;
if (params->GetString(devtools::Tracing::start::kParamOptions,
&options_param)) {
options = TraceOptionsFromString(options_param);
}
params->GetDouble(
devtools::Tracing::start::kParamBufferUsageReportingInterval,
&usage_reporting_interval);
}
SetupTimer(usage_reporting_interval);
// If inspected target is a render process Tracing.start will be handled by
// tracing agent in the renderer.
if (target_ == Renderer) {
TracingController::GetInstance()->EnableRecording(
base::debug::CategoryFilter(categories),
options,
TracingController::EnableRecordingDoneCallback());
return NULL;
}
TracingController::GetInstance()->EnableRecording(
base::debug::CategoryFilter(categories),
options,
base::Bind(&DevToolsTracingHandler::OnRecordingEnabled,
weak_factory_.GetWeakPtr(),
command));
return command->AsyncResponsePromise();
}
void DevToolsTracingHandler::SetupTimer(double usage_reporting_interval) {
if (usage_reporting_interval == 0) return;
if (usage_reporting_interval < kMinimumReportingInterval)
usage_reporting_interval = kMinimumReportingInterval;
base::TimeDelta interval = base::TimeDelta::FromMilliseconds(
std::ceil(usage_reporting_interval));
buffer_usage_poll_timer_.reset(new base::Timer(
FROM_HERE,
interval,
base::Bind(
base::IgnoreResult(&TracingController::GetTraceBufferPercentFull),
base::Unretained(TracingController::GetInstance()),
base::Bind(&DevToolsTracingHandler::OnBufferUsage,
weak_factory_.GetWeakPtr())),
true));
buffer_usage_poll_timer_->Reset();
}
void DevToolsTracingHandler::OnRecordingEnabled(
scoped_refptr<DevToolsProtocol::Command> command) {
SendAsyncResponse(command->SuccessResponse(NULL));
}
void DevToolsTracingHandler::OnBufferUsage(float usage) {
base::DictionaryValue* params = new base::DictionaryValue();
params->SetDouble(devtools::Tracing::bufferUsage::kParamValue, usage);
SendNotification(devtools::Tracing::bufferUsage::kName, params);
}
scoped_refptr<DevToolsProtocol::Response>
DevToolsTracingHandler::OnEnd(
scoped_refptr<DevToolsProtocol::Command> command) {
if (!is_recording_) {
return command->InternalErrorResponse("Tracing is not started");
}
DisableRecording(false);
// If inspected target is a render process Tracing.end will be handled by
// tracing agent in the renderer.
if (target_ == Renderer)
return NULL;
return command->SuccessResponse(NULL);
}
void DevToolsTracingHandler::DisableRecording(bool abort) {
is_recording_ = false;
buffer_usage_poll_timer_.reset();
TracingController::GetInstance()->DisableRecording(
abort ? NULL : new DevToolsTraceSinkProxy(weak_factory_.GetWeakPtr()));
}
void DevToolsTracingHandler::OnClientDetached() {
if (is_recording_)
DisableRecording(true);
}
scoped_refptr<DevToolsProtocol::Response>
DevToolsTracingHandler::OnGetCategories(
scoped_refptr<DevToolsProtocol::Command> command) {
TracingController::GetInstance()->GetCategories(
base::Bind(&DevToolsTracingHandler::OnCategoriesReceived,
weak_factory_.GetWeakPtr(),
command));
return command->AsyncResponsePromise();
}
void DevToolsTracingHandler::OnCategoriesReceived(
scoped_refptr<DevToolsProtocol::Command> command,
const std::set<std::string>& category_set) {
base::DictionaryValue* response = new base::DictionaryValue;
base::ListValue* category_list = new base::ListValue;
for (std::set<std::string>::const_iterator it = category_set.begin();
it != category_set.end(); ++it) {
category_list->AppendString(*it);
}
response->Set(devtools::Tracing::getCategories::kResponseCategories,
category_list);
SendAsyncResponse(command->SuccessResponse(response));
}
} // namespace content