// 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 #include "base/bind.h" #include "base/format_macros.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 "content/browser/devtools/devtools_io_context.h" namespace content { namespace devtools { namespace tracing { using Response = DevToolsProtocolClient::Response; namespace { const double kMinimumReportingInterval = 250.0; class DevToolsTraceSinkProxy : public TracingController::TraceDataSink { public: explicit DevToolsTraceSinkProxy(base::WeakPtr 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 tracing_handler_; }; class DevToolsStreamTraceSink : public TracingController::TraceDataSink { public: explicit DevToolsStreamTraceSink( base::WeakPtr handler, const scoped_refptr& stream) : stream_(stream), tracing_handler_(handler), first_chunk_(true) {} void AddTraceChunk(const std::string& chunk) override { // FIXME: change interface to pass chunks as refcounted strings. scoped_refptr ref_counted_chunk = new base::RefCountedString(); std::string prefix = first_chunk_ ? "[" : ","; ref_counted_chunk->data() = prefix + chunk; first_chunk_ = false; stream_->Append(ref_counted_chunk); } void Close() override { if (TracingHandler* h = tracing_handler_.get()) { std::string suffix = "]"; stream_->Append(base::RefCountedString::TakeString(&suffix)); h->OnTraceToStreamComplete(stream_->handle()); } } private: ~DevToolsStreamTraceSink() override {} scoped_refptr stream_; base::WeakPtr tracing_handler_; bool first_chunk_; }; } // namespace TracingHandler::TracingHandler(TracingHandler::Target target, DevToolsIOContext* io_context) : target_(target), io_context_(io_context), did_initiate_recording_(false), return_as_stream_(false), weak_factory_(this) {} TracingHandler::~TracingHandler() { } void TracingHandler::SetClient(scoped_ptr client) { client_.swap(client); } void TracingHandler::Detached() { if (IsRecording()) DisableRecording(scoped_refptr()); } 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 += "] } }"; client_->SendRawMessage(message); } void TracingHandler::OnTraceComplete() { client_->TracingComplete(TracingCompleteParams::Create()); } void TracingHandler::OnTraceToStreamComplete(const std::string& stream_handle) { client_->TracingComplete( TracingCompleteParams::Create()->set_stream(stream_handle)); } Response TracingHandler::Start(DevToolsCommandId command_id, const std::string* categories, const std::string* options, const double* buffer_usage_reporting_interval) { return Start(command_id, categories, options, buffer_usage_reporting_interval, nullptr); } Response TracingHandler::Start(DevToolsCommandId command_id, const std::string* categories, const std::string* options, const double* buffer_usage_reporting_interval, const std::string* transfer_mode) { if (IsRecording()) return Response::InternalError("Tracing is already started"); did_initiate_recording_ = true; return_as_stream_ = transfer_mode && *transfer_mode == start::kTransferModeReturnAsStream; base::trace_event::TraceConfig trace_config( categories ? *categories : std::string(), options ? *options : std::string()); if (buffer_usage_reporting_interval) SetupTimer(*buffer_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( trace_config, TracingController::EnableRecordingDoneCallback()); return Response::FallThrough(); } TracingController::GetInstance()->EnableRecording( trace_config, base::Bind(&TracingHandler::OnRecordingEnabled, weak_factory_.GetWeakPtr(), command_id)); return Response::OK(); } Response TracingHandler::End(DevToolsCommandId command_id) { if (!IsRecording()) return Response::InternalError("Tracing is not started"); scoped_refptr proxy; if (return_as_stream_) { proxy = new DevToolsStreamTraceSink( weak_factory_.GetWeakPtr(), io_context_->CreateTempFileBackedStream()); } else { proxy = new DevToolsTraceSinkProxy(weak_factory_.GetWeakPtr()); } DisableRecording(proxy); // If inspected target is a render process Tracing.end will be handled by // tracing agent in the renderer. return target_ == Renderer ? Response::FallThrough() : Response::OK(); } Response TracingHandler::GetCategories(DevToolsCommandId command_id) { TracingController::GetInstance()->GetCategories( base::Bind(&TracingHandler::OnCategoriesReceived, weak_factory_.GetWeakPtr(), command_id)); return Response::OK(); } void TracingHandler::OnRecordingEnabled(DevToolsCommandId command_id) { client_->SendStartResponse(command_id, StartResponse::Create()); } 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. client_->BufferUsage(BufferUsageParams::Create() ->set_value(percent_full) ->set_percent_full(percent_full) ->set_event_count(approximate_event_count)); } void TracingHandler::OnCategoriesReceived( DevToolsCommandId command_id, const std::set& category_set) { std::vector categories; for (const std::string& category : category_set) categories.push_back(category); client_->SendGetCategoriesResponse(command_id, GetCategoriesResponse::Create()->set_categories(categories)); } Response TracingHandler::RequestMemoryDump(DevToolsCommandId command_id) { if (!IsRecording()) return Response::InternalError("Tracing is not started"); base::trace_event::MemoryDumpArgs dump_args = { base::trace_event::MemoryDumpArgs::LevelOfDetail::HIGH}; base::trace_event::MemoryDumpManager::GetInstance()->RequestGlobalDump( base::trace_event::MemoryDumpType::EXPLICITLY_TRIGGERED, dump_args, base::Bind(&TracingHandler::OnMemoryDumpFinished, weak_factory_.GetWeakPtr(), command_id)); return Response::OK(); } void TracingHandler::OnMemoryDumpFinished(DevToolsCommandId command_id, uint64 dump_guid, bool success) { client_->SendRequestMemoryDumpResponse( command_id, RequestMemoryDumpResponse::Create() ->set_dump_guid(base::StringPrintf("0x%" PRIx64, dump_guid)) ->set_success(success)); } 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::DisableRecording( const scoped_refptr& trace_data_sink) { buffer_usage_poll_timer_.reset(); TracingController::GetInstance()->DisableRecording(trace_data_sink); did_initiate_recording_ = false; } bool TracingHandler::IsRecording() const { return TracingController::GetInstance()->IsRecording(); } } // namespace tracing } // namespace devtools } // namespace content