blob: ea5c2d44be4ed9bdd4684102a3afc8487b7437c7 [file] [log] [blame]
// Copyright 2014 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/protocol/tracing_handler.h"
#include <cmath>
#include "base/bind.h"
#include "base/format_macros.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted_memory.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/trace_event_impl.h"
#include "base/trace_event/tracing_agent.h"
#include "components/tracing/browser/trace_config_file.h"
#include "content/browser/devtools/devtools_io_context.h"
#include "content/browser/tracing/tracing_controller_impl.h"
namespace content {
namespace protocol {
namespace {
const double kMinimumReportingInterval = 250.0;
const char kRecordModeParam[] = "record_mode";
// Convert from camel case to separator + lowercase.
std::string ConvertFromCamelCase(const std::string& in_str, char separator) {
std::string out_str;
out_str.reserve(in_str.size());
for (const char& c : in_str) {
if (isupper(c)) {
out_str.push_back(separator);
out_str.push_back(tolower(c));
} else {
out_str.push_back(c);
}
}
return out_str;
}
std::unique_ptr<base::Value> ConvertDictKeyStyle(const base::Value& value) {
const base::DictionaryValue* dict = nullptr;
if (value.GetAsDictionary(&dict)) {
std::unique_ptr<base::DictionaryValue> out_dict(
new base::DictionaryValue());
for (base::DictionaryValue::Iterator it(*dict); !it.IsAtEnd();
it.Advance()) {
out_dict->Set(ConvertFromCamelCase(it.key(), '_'),
ConvertDictKeyStyle(it.value()));
}
return std::move(out_dict);
}
const base::ListValue* list = nullptr;
if (value.GetAsList(&list)) {
std::unique_ptr<base::ListValue> out_list(new base::ListValue());
for (const auto& value : *list)
out_list->Append(ConvertDictKeyStyle(*value));
return std::move(out_list);
}
return value.CreateDeepCopy();
}
class DevToolsTraceSinkProxy : public TracingController::TraceDataSink {
public:
explicit DevToolsTraceSinkProxy(base::WeakPtr<TracingHandler> handler)
: tracing_handler_(handler) {}
void AddTraceChunk(const std::string& chunk) override {
if (TracingHandler* h = tracing_handler_.get())
h->OnTraceDataCollected(chunk);
}
void Close() override {
if (TracingHandler* h = tracing_handler_.get())
h->OnTraceComplete();
}
private:
~DevToolsTraceSinkProxy() override {}
base::WeakPtr<TracingHandler> tracing_handler_;
};
class DevToolsStreamEndpoint : public TraceDataEndpoint {
public:
explicit DevToolsStreamEndpoint(
base::WeakPtr<TracingHandler> handler,
const scoped_refptr<DevToolsIOContext::Stream>& stream)
: stream_(stream), tracing_handler_(handler) {}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
stream_->Append(std::move(chunk));
}
void ReceiveTraceFinalContents(
std::unique_ptr<const base::DictionaryValue> metadata) override {
if (TracingHandler* h = tracing_handler_.get())
h->OnTraceToStreamComplete(stream_->handle());
}
private:
~DevToolsStreamEndpoint() override {}
scoped_refptr<DevToolsIOContext::Stream> stream_;
base::WeakPtr<TracingHandler> tracing_handler_;
};
} // namespace
TracingHandler::TracingHandler(TracingHandler::Target target,
int frame_tree_node_id,
DevToolsIOContext* io_context)
: target_(target),
io_context_(io_context),
frame_tree_node_id_(frame_tree_node_id),
did_initiate_recording_(false),
return_as_stream_(false),
weak_factory_(this) {}
TracingHandler::~TracingHandler() {
}
void TracingHandler::Wire(UberDispatcher* dispatcher) {
frontend_.reset(new Tracing::Frontend(dispatcher->channel()));
Tracing::Dispatcher::wire(dispatcher, this);
}
Response TracingHandler::Disable() {
if (did_initiate_recording_)
StopTracing(scoped_refptr<TracingController::TraceDataSink>());
return Response::OK();
}
void TracingHandler::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(
"{ \"method\": \"Tracing.dataCollected\", \"params\": { \"value\": [");
const size_t messageSuffixSize = 10;
message.reserve(message.size() + trace_fragment.size() + messageSuffixSize);
message += trace_fragment;
message += "] } }";
frontend_->sendRawNotification(message);
}
void TracingHandler::OnTraceComplete() {
frontend_->TracingComplete();
}
void TracingHandler::OnTraceToStreamComplete(const std::string& stream_handle) {
frontend_->TracingComplete(stream_handle);
}
void TracingHandler::Start(Maybe<std::string> categories,
Maybe<std::string> options,
Maybe<double> buffer_usage_reporting_interval,
Maybe<std::string> transfer_mode,
Maybe<Tracing::TraceConfig> config,
std::unique_ptr<StartCallback> callback) {
bool return_as_stream = transfer_mode.fromMaybe("") ==
Tracing::Start::TransferModeEnum::ReturnAsStream;
if (IsTracing()) {
if (!did_initiate_recording_ && IsStartupTracingActive()) {
// If tracing is already running because it was initiated by startup
// tracing, honor the transfer mode update, as that's the only way
// for the client to communicate it.
return_as_stream_ = return_as_stream;
}
callback->sendFailure(Response::Error("Tracing is already started"));
return;
}
if (config.isJust() && (categories.isJust() || options.isJust())) {
callback->sendFailure(Response::InvalidParams(
"Either trace config (preferred), or categories+options should be "
"specified, but not both."));
return;
}
did_initiate_recording_ = true;
return_as_stream_ = return_as_stream;
if (buffer_usage_reporting_interval.isJust())
SetupTimer(buffer_usage_reporting_interval.fromJust());
base::trace_event::TraceConfig trace_config;
if (config.isJust()) {
std::unique_ptr<base::Value> value =
protocol::toBaseValue(config.fromJust()->toValue().get(), 1000);
if (value && value->IsType(base::Value::TYPE_DICTIONARY)) {
trace_config = GetTraceConfigFromDevToolsConfig(
*static_cast<base::DictionaryValue*>(value.get()));
}
} else if (categories.isJust() || options.isJust()) {
trace_config = base::trace_event::TraceConfig(
categories.fromMaybe(""), options.fromMaybe(""));
}
// If inspected target is a render process Tracing.start will be handled by
// tracing agent in the renderer.
if (target_ == Renderer)
callback->fallThrough();
TracingController::GetInstance()->StartTracing(
trace_config,
base::Bind(&TracingHandler::OnRecordingEnabled,
weak_factory_.GetWeakPtr(),
base::Passed(std::move(callback))));
}
void TracingHandler::End(std::unique_ptr<EndCallback> callback) {
// Startup tracing triggered by --trace-config-file is a special case, where
// tracing is started automatically upon browser startup and can be stopped
// via DevTools.
if (!did_initiate_recording_ && !IsStartupTracingActive()) {
callback->sendFailure(Response::Error("Tracing is not started"));
return;
}
scoped_refptr<TracingController::TraceDataSink> sink;
if (return_as_stream_) {
sink = TracingControllerImpl::CreateJSONSink(new DevToolsStreamEndpoint(
weak_factory_.GetWeakPtr(), io_context_->CreateTempFileBackedStream()));
} else {
sink = new DevToolsTraceSinkProxy(weak_factory_.GetWeakPtr());
}
StopTracing(sink);
// If inspected target is a render process Tracing.end will be handled by
// tracing agent in the renderer.
if (target_ == Renderer)
callback->fallThrough();
else
callback->sendSuccess();
}
void TracingHandler::GetCategories(
std::unique_ptr<GetCategoriesCallback> callback) {
TracingController::GetInstance()->GetCategories(
base::Bind(&TracingHandler::OnCategoriesReceived,
weak_factory_.GetWeakPtr(),
base::Passed(std::move(callback))));
}
void TracingHandler::OnRecordingEnabled(
std::unique_ptr<StartCallback> callback) {
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"TracingStartedInBrowser", TRACE_EVENT_SCOPE_THREAD,
"frameTreeNodeId", frame_tree_node_id_);
if (target_ != Renderer)
callback->sendSuccess();
}
void TracingHandler::OnBufferUsage(float percent_full,
size_t approximate_event_count) {
// TODO(crbug426117): remove set_value once all clients have switched to
// the new interface of the event.
frontend_->BufferUsage(percent_full, percent_full, approximate_event_count);
}
void TracingHandler::OnCategoriesReceived(
std::unique_ptr<GetCategoriesCallback> callback,
const std::set<std::string>& category_set) {
std::unique_ptr<protocol::Array<std::string>> categories =
protocol::Array<std::string>::create();
for (const std::string& category : category_set)
categories->addItem(category);
callback->sendSuccess(std::move(categories));
}
void TracingHandler::RequestMemoryDump(
std::unique_ptr<RequestMemoryDumpCallback> callback) {
if (!IsTracing()) {
callback->sendFailure(Response::Error("Tracing is not started"));
return;
}
base::trace_event::MemoryDumpManager::GetInstance()->RequestGlobalDump(
base::trace_event::MemoryDumpType::EXPLICITLY_TRIGGERED,
base::trace_event::MemoryDumpLevelOfDetail::DETAILED,
base::Bind(&TracingHandler::OnMemoryDumpFinished,
weak_factory_.GetWeakPtr(),
base::Passed(std::move(callback))));
}
void TracingHandler::OnMemoryDumpFinished(
std::unique_ptr<RequestMemoryDumpCallback> callback,
uint64_t dump_guid,
bool success) {
callback->sendSuccess(base::StringPrintf("0x%" PRIx64, dump_guid), success);
}
Response TracingHandler::RecordClockSyncMarker(const std::string& sync_id) {
if (!IsTracing())
return Response::Error("Tracing is not started");
TracingControllerImpl::GetInstance()->RecordClockSyncMarker(
sync_id,
base::trace_event::TracingAgent::RecordClockSyncMarkerCallback());
return Response::OK();
}
void TracingHandler::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::GetTraceBufferUsage),
base::Unretained(TracingController::GetInstance()),
base::Bind(&TracingHandler::OnBufferUsage,
weak_factory_.GetWeakPtr())),
true));
buffer_usage_poll_timer_->Reset();
}
void TracingHandler::StopTracing(
const scoped_refptr<TracingController::TraceDataSink>& trace_data_sink) {
buffer_usage_poll_timer_.reset();
TracingController::GetInstance()->StopTracing(trace_data_sink);
did_initiate_recording_ = false;
}
bool TracingHandler::IsTracing() const {
return TracingController::GetInstance()->IsTracing();
}
bool TracingHandler::IsStartupTracingActive() {
return ::tracing::TraceConfigFile::GetInstance()->IsEnabled() &&
TracingController::GetInstance()->IsTracing();
}
// static
base::trace_event::TraceConfig TracingHandler::GetTraceConfigFromDevToolsConfig(
const base::DictionaryValue& devtools_config) {
std::unique_ptr<base::Value> value = ConvertDictKeyStyle(devtools_config);
DCHECK(value && value->IsType(base::Value::TYPE_DICTIONARY));
std::unique_ptr<base::DictionaryValue> tracing_dict(
static_cast<base::DictionaryValue*>(value.release()));
std::string mode;
if (tracing_dict->GetString(kRecordModeParam, &mode))
tracing_dict->SetString(kRecordModeParam, ConvertFromCamelCase(mode, '-'));
return base::trace_event::TraceConfig(*tracing_dict);
}
} // namespace tracing
} // namespace protocol