blob: bf3cb1d7c8b17445d7d29d7996acc38558b3af9a [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// 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 <algorithm>
#include <cmath>
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include "base/format_macros.h"
#include "base/functional/bind.h"
#include "base/json/json_writer.h"
#include "base/memory/ref_counted_memory.h"
#include "base/numerics/safe_conversions.h"
#include "base/strings/strcat.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/traced_value.h"
#include "base/trace_event/tracing_agent.h"
#include "base/values.h"
#include "build/build_config.h"
#include "components/tracing/common/trace_startup_config.h"
#include "content/browser/devtools/devtools_agent_host_impl.h"
#include "content/browser/devtools/devtools_io_context.h"
#include "content/browser/devtools/devtools_stream_file.h"
#include "content/browser/devtools/devtools_traceable_screenshot.h"
#include "content/browser/devtools/devtools_video_consumer.h"
#include "content/browser/devtools/tracing_process_set_monitor.h"
#include "content/browser/gpu/gpu_process_host.h"
#include "content/browser/renderer_host/frame_tree.h"
#include "content/browser/renderer_host/frame_tree_node.h"
#include "content/browser/renderer_host/navigation_request.h"
#include "content/browser/renderer_host/render_frame_host_impl.h"
#include "content/browser/renderer_host/render_widget_host_impl.h"
#include "content/browser/tracing/tracing_controller_impl.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/tracing_service.h"
#include "content/public/browser/web_contents.h"
#include "services/resource_coordinator/public/cpp/memory_instrumentation/memory_instrumentation.h"
#include "services/tracing/public/cpp/perfetto/perfetto_config.h"
#include "services/tracing/public/cpp/perfetto/perfetto_session.h"
#include "services/tracing/public/cpp/perfetto/trace_packet_tokenizer.h"
#include "services/tracing/public/cpp/tracing_features.h"
#include "services/tracing/public/mojom/constants.mojom-forward.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "third_party/inspector_protocol/crdtp/json.h"
#if BUILDFLAG(IS_ANDROID)
#include "content/browser/renderer_host/compositor_impl_android.h"
#endif
namespace content::protocol {
namespace {
const double kMinimumReportingInterval = 250.0;
const char kRecordModeParam[] = "record_mode";
const char kTraceBufferSizeInKb[] = "trace_buffer_size_in_kb";
#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
const char kTrackEventDataSourceName[] = "track_event";
#endif
// Frames need to be at least 1x1, otherwise nothing would be captured.
constexpr gfx::Size kMinFrameSize = gfx::Size(1, 1);
// Frames do not need to be greater than 500x500 for tracing.
constexpr gfx::Size kMaxFrameSize = gfx::Size(500, 500);
// 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;
}
base::Value ConvertDictKeyStyle(const base::Value& value) {
const base::Value::Dict* dict = value.GetIfDict();
if (dict) {
base::Value::Dict out;
for (auto kv : *dict) {
out.Set(ConvertFromCamelCase(kv.first, '_'),
ConvertDictKeyStyle(kv.second));
}
return base::Value(std::move(out));
}
const base::Value::List* list = value.GetIfList();
if (list) {
base::Value::List out;
for (const auto& v : *list) {
out.Append(ConvertDictKeyStyle(v));
}
return base::Value(std::move(out));
}
return value.Clone();
}
class DevToolsTraceEndpointProxy : public TracingController::TraceDataEndpoint {
public:
explicit DevToolsTraceEndpointProxy(base::WeakPtr<TracingHandler> handler)
: tracing_handler_(handler) {}
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
if (TracingHandler* h = tracing_handler_.get())
h->OnTraceDataCollected(std::move(chunk));
}
void ReceivedTraceFinalContents() override {
if (TracingHandler* h = tracing_handler_.get())
h->OnTraceComplete();
}
private:
~DevToolsTraceEndpointProxy() override = default;
base::WeakPtr<TracingHandler> tracing_handler_;
};
class DevToolsStreamEndpoint : public TracingController::TraceDataEndpoint {
public:
explicit DevToolsStreamEndpoint(
base::WeakPtr<TracingHandler> handler,
const scoped_refptr<DevToolsStreamFile>& stream)
: stream_(stream), tracing_handler_(handler) {}
// CompressedStringEndpoint calls these methods on a background thread.
void ReceiveTraceChunk(std::unique_ptr<std::string> chunk) override {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&DevToolsStreamEndpoint::ReceiveTraceChunk,
this, std::move(chunk)));
return;
}
stream_->Append(std::move(chunk));
}
void ReceivedTraceFinalContents() override {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&DevToolsStreamEndpoint::ReceivedTraceFinalContents,
this));
return;
}
if (TracingHandler* h = tracing_handler_.get())
h->OnTraceToStreamComplete(stream_->handle());
}
private:
~DevToolsStreamEndpoint() override = default;
scoped_refptr<DevToolsStreamFile> stream_;
base::WeakPtr<TracingHandler> tracing_handler_;
};
std::string GetProcessHostHex(RenderProcessHost* host) {
return base::StringPrintf("0x%" PRIxPTR, reinterpret_cast<uintptr_t>(host));
}
void SendProcessReadyInBrowserEvent(const base::UnguessableToken& frame_token,
RenderProcessHost* host) {
auto data = std::make_unique<base::trace_event::TracedValue>();
data->SetString("frame", frame_token.ToString());
data->SetString("processPseudoId", GetProcessHostHex(host));
data->SetInteger("processId", static_cast<int>(host->GetProcess().Pid()));
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"ProcessReadyInBrowser", TRACE_EVENT_SCOPE_THREAD,
"data", std::move(data));
}
void FillFrameData(base::trace_event::TracedValue* data,
RenderFrameHostImpl* frame_host) {
CHECK(frame_host);
GURL::Replacements strip_fragment;
strip_fragment.ClearRef();
std::string url = frame_host->GetLastCommittedURL()
.ReplaceComponents(strip_fragment)
.spec();
data->SetString("frame", frame_host->devtools_frame_token().ToString());
data->SetString("url", url);
data->SetString("name", frame_host->GetFrameName());
if (frame_host->GetParent()) {
data->SetString(
"parent", frame_host->GetParent()->GetDevToolsFrameToken().ToString());
}
RenderProcessHost* process_host = frame_host->GetProcess();
const base::Process& process_handle = process_host->GetProcess();
if (!process_handle.IsValid()) {
data->SetString("processPseudoId", GetProcessHostHex(process_host));
frame_host->GetProcess()->PostTaskWhenProcessIsReady(
base::BindOnce(&SendProcessReadyInBrowserEvent,
frame_host->devtools_frame_token(), process_host));
} else {
// Cast process id to int to be compatible with tracing.
data->SetInteger("processId", static_cast<int>(process_handle.Pid()));
}
}
absl::optional<base::trace_event::MemoryDumpLevelOfDetail>
StringToMemoryDumpLevelOfDetail(const std::string& str) {
if (str == Tracing::MemoryDumpLevelOfDetailEnum::Detailed)
return {base::trace_event::MemoryDumpLevelOfDetail::DETAILED};
if (str == Tracing::MemoryDumpLevelOfDetailEnum::Background)
return {base::trace_event::MemoryDumpLevelOfDetail::BACKGROUND};
if (str == Tracing::MemoryDumpLevelOfDetailEnum::Light)
return {base::trace_event::MemoryDumpLevelOfDetail::LIGHT};
return {};
}
void AddPidsToProcessFilter(
const std::unordered_set<base::ProcessId>& included_process_ids,
perfetto::TraceConfig& trace_config) {
#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
const std::string kDataSourceName = kTrackEventDataSourceName;
#else
const std::string kDataSourceName = tracing::mojom::kTraceEventDataSourceName;
#endif
for (auto& data_source : *(trace_config.mutable_data_sources())) {
auto* source_config = data_source.mutable_config();
if (source_config->name() == kDataSourceName) {
for (auto& enabled_pid : included_process_ids) {
*data_source.add_producer_name_filter() = base::StrCat(
{tracing::mojom::kPerfettoProducerNamePrefix,
base::NumberToString(static_cast<uint32_t>(enabled_pid))});
}
break;
}
}
}
bool IsChromeDataSource(const std::string& data_source_name) {
return base::StartsWith(data_source_name, "org.chromium.") ||
data_source_name == "track_event";
}
absl::optional<perfetto::BackendType> GetBackendTypeFromParameters(
const std::string& tracing_backend,
perfetto::TraceConfig& perfetto_config) {
if (tracing_backend == Tracing::TracingBackendEnum::Chrome)
return perfetto::BackendType::kCustomBackend;
if (tracing_backend == Tracing::TracingBackendEnum::System)
return perfetto::BackendType::kSystemBackend;
if (tracing_backend == Tracing::TracingBackendEnum::Auto) {
// Use the Chrome backend by default, unless there are non-Chrome data
// sources specified in the config.
for (auto& data_source : *(perfetto_config.mutable_data_sources())) {
auto* source_config = data_source.mutable_config();
if (!IsChromeDataSource(source_config->name()))
return perfetto::BackendType::kSystemBackend;
}
return perfetto::BackendType::kCustomBackend;
}
return absl::nullopt;
}
// Perfetto SDK build expects track_event data source to be configured via
// track_event_config. But some devtools users (e.g. Perfetto UI) send
// a chrome_config instead. We build a track_event_config based on the
// chrome_config if no other track_event data sources have been configured.
void ConvertToTrackEventConfigIfNeeded(perfetto::TraceConfig& trace_config) {
#if BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
for (const auto& data_source : trace_config.data_sources()) {
if (!data_source.config().track_event_config_raw().empty()) {
return;
}
}
for (auto& data_source : *trace_config.mutable_data_sources()) {
if (data_source.config().name() ==
tracing::mojom::kTraceEventDataSourceName &&
data_source.config().has_chrome_config()) {
data_source.mutable_config()->set_name(kTrackEventDataSourceName);
base::trace_event::TraceConfig base_config(
data_source.config().chrome_config().trace_config());
bool privacy_filtering_enabled =
data_source.config().chrome_config().privacy_filtering_enabled();
data_source.mutable_config()->set_track_event_config_raw(
base_config.ToPerfettoTrackEventConfigRaw(privacy_filtering_enabled));
return;
}
}
#endif // BUILDFLAG(USE_PERFETTO_CLIENT_LIBRARY)
}
// We currently don't support concurrent tracing sessions, but are planning to.
// For the time being, we're using this flag as a workaround to prevent devtools
// users from accidentally starting two concurrent sessions.
// TODO(eseckler): Remove once we add support for concurrent sessions to the
// perfetto backend.
static bool g_any_agent_tracing = false;
} // namespace
class TracingHandler::PerfettoTracingSession {
public:
PerfettoTracingSession(bool use_proto, perfetto::BackendType backend_type)
: use_proto_format_(use_proto), backend_type_(backend_type) {}
~PerfettoTracingSession() { g_any_agent_tracing = false; }
void EnableTracing(const perfetto::TraceConfig& perfetto_config,
base::OnceCallback<void(const std::string& /*error_msg*/)>
start_callback) {
DCHECK(!tracing_session_);
DCHECK(!tracing_active_);
g_any_agent_tracing = true;
tracing_active_ = true;
start_callback_ = std::move(start_callback);
#if DCHECK_IS_ON()
last_perfetto_config_ = perfetto_config;
for (auto& data_source : *(last_perfetto_config_.mutable_data_sources())) {
data_source.clear_producer_name_filter();
}
#endif
tracing_session_ = perfetto::Tracing::NewTrace(backend_type_);
tracing_session_->Setup(perfetto_config);
auto weak_ptr = weak_factory_.GetWeakPtr();
tracing_session_->SetOnStartCallback([weak_ptr] {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&PerfettoTracingSession::OnTracingSessionStarted,
weak_ptr));
});
tracing_session_->SetOnErrorCallback(
[weak_ptr](perfetto::TracingError error) {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&PerfettoTracingSession::OnTracingSessionFailed,
weak_ptr, error.message));
});
tracing_session_->Start();
}
void AdoptStartupTracingSession(
const perfetto::TraceConfig& perfetto_config) {
// Start a perfetto tracing session, which will claim startup tracing data.
DCHECK(!TracingController::GetInstance()->IsTracing());
waiting_for_startup_tracing_enabled_ = true;
EnableTracing(
perfetto_config,
base::BindOnce(&PerfettoTracingSession::OnStartupTracingEnabled,
base::Unretained(this)));
}
void ChangeTraceConfig(const perfetto::TraceConfig& perfetto_config) {
if (!tracing_session_)
return;
#if DCHECK_IS_ON()
// Ensure that the process filter is the only thing that gets changed
// in a configuration during a tracing session.
perfetto::TraceConfig config_without_filters = perfetto_config;
for (auto& data_source : *(config_without_filters.mutable_data_sources())) {
data_source.clear_producer_name_filter();
}
DCHECK(config_without_filters == last_perfetto_config_);
last_perfetto_config_ = std::move(config_without_filters);
#endif
tracing_session_->ChangeTraceConfig(perfetto_config);
}
void DisableTracing(
scoped_refptr<TracingController::TraceDataEndpoint> endpoint) {
DCHECK(endpoint);
if (waiting_for_startup_tracing_enabled_) {
pending_disable_tracing_task_ =
base::BindOnce(&PerfettoTracingSession::DisableTracing,
base::Unretained(this), std::move(endpoint));
return;
}
endpoint_ = endpoint;
tracing_active_ = false;
if (!tracing_session_) {
endpoint_->ReceivedTraceFinalContents();
return;
}
auto weak_ptr = weak_factory_.GetWeakPtr();
tracing_session_->SetOnStopCallback([weak_ptr] {
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&PerfettoTracingSession::OnTracingSessionStopped,
weak_ptr));
});
tracing_session_->Stop();
}
void GetBufferUsage(base::OnceCallback<void(bool success,
float percent_full,
size_t approximate_event_count)>
on_buffer_usage_callback) {
DCHECK(on_buffer_usage_callback);
if (!tracing_session_) {
std::move(on_buffer_usage_callback).Run(false, 0.0f, 0);
return;
}
if (on_buffer_usage_callback_) {
// We only support one concurrent buffer usage request.
std::move(on_buffer_usage_callback).Run(false, 0.0f, 0);
return;
}
on_buffer_usage_callback_ = std::move(on_buffer_usage_callback);
auto weak_ptr = weak_factory_.GetWeakPtr();
tracing_session_->GetTraceStats(
[weak_ptr](perfetto::TracingSession::GetTraceStatsCallbackArgs args) {
tracing::ReadTraceStats(
args,
base::BindOnce(&PerfettoTracingSession::OnBufferUsage, weak_ptr),
GetUIThreadTaskRunner({}));
});
}
bool HasTracingFailed() { return tracing_active_ && !tracing_session_; }
bool HasDataLossOccurred() { return data_loss_; }
private:
void OnStartupTracingEnabled(const std::string& error_msg) {
DCHECK(error_msg.empty());
waiting_for_startup_tracing_enabled_ = false;
if (pending_disable_tracing_task_)
std::move(pending_disable_tracing_task_).Run();
}
void OnTracingSessionStarted() {
if (start_callback_)
std::move(start_callback_).Run(/*error_msg=*/std::string());
}
void OnTracingSessionFailed(std::string error_msg) {
tracing_session_.reset();
if (start_callback_)
std::move(start_callback_).Run(error_msg);
if (pending_disable_tracing_task_) {
// Will call endpoint_->ReceivedTraceFinalContents() and delete |this|.
std::move(pending_disable_tracing_task_).Run();
} else if (endpoint_) {
// Will delete |this|.
endpoint_->ReceivedTraceFinalContents();
}
}
void OnTracingSessionStopped() {
DCHECK(tracing_session_);
auto weak_ptr = weak_factory_.GetWeakPtr();
if (use_proto_format_) {
tracing_session_->ReadTrace(
[weak_ptr](perfetto::TracingSession::ReadTraceCallbackArgs args) {
tracing::ReadTraceAsProtobuf(
args,
base::BindOnce(&PerfettoTracingSession::OnTraceData, weak_ptr),
base::BindOnce(&PerfettoTracingSession::OnTraceDataComplete,
weak_ptr),
GetUIThreadTaskRunner({}));
});
} else {
// Ref-counted because it is used within the lambda running on the
// perfetto SDK's thread.
auto tokenizer = base::MakeRefCounted<
base::RefCountedData<std::unique_ptr<tracing::TracePacketTokenizer>>>(
std::make_unique<tracing::TracePacketTokenizer>());
tracing_session_->ReadTrace(
[weak_ptr,
tokenizer](perfetto::TracingSession::ReadTraceCallbackArgs args) {
tracing::ReadTraceAsJson(
args, tokenizer,
base::BindOnce(&PerfettoTracingSession::OnTraceData, weak_ptr),
base::BindOnce(&PerfettoTracingSession::OnTraceDataComplete,
weak_ptr),
GetUIThreadTaskRunner({}));
});
}
}
void OnBufferUsage(bool success, float percent_full, bool data_loss) {
data_loss_ |= data_loss;
if (on_buffer_usage_callback_) {
std::move(on_buffer_usage_callback_).Run(success, percent_full, 0);
}
}
void OnTraceData(std::unique_ptr<std::string> data) {
endpoint_->ReceiveTraceChunk(std::move(data));
}
void OnTraceDataComplete() {
// Request stats to check if data loss occurred.
GetBufferUsage(base::BindOnce(&PerfettoTracingSession::OnFinalBufferUsage,
weak_factory_.GetWeakPtr()));
}
void OnFinalBufferUsage(bool success,
float percent_full,
size_t approximate_event_count) {
// Will delete |this|.
endpoint_->ReceivedTraceFinalContents();
}
std::unique_ptr<perfetto::TracingSession> tracing_session_;
const bool use_proto_format_;
perfetto::BackendType backend_type_ =
perfetto::BackendType::kUnspecifiedBackend;
base::OnceCallback<void(const std::string&)> start_callback_;
base::OnceCallback<
void(bool success, float percent_full, size_t approximate_event_count)>
on_buffer_usage_callback_;
base::OnceClosure pending_disable_tracing_task_;
bool waiting_for_startup_tracing_enabled_ = false;
scoped_refptr<TracingController::TraceDataEndpoint> endpoint_;
bool tracing_active_ = false;
bool data_loss_ = false;
#if DCHECK_IS_ON()
perfetto::TraceConfig last_perfetto_config_;
#endif
base::WeakPtrFactory<PerfettoTracingSession> weak_factory_{this};
};
TracingHandler::TracingHandler(DevToolsAgentHostImpl* host,
DevToolsIOContext* io_context,
DevToolsSession* root_session)
: DevToolsDomainHandler(Tracing::Metainfo::domainName),
io_context_(io_context),
host_(host),
session_for_process_filter_(root_session),
did_initiate_recording_(false),
return_as_stream_(false),
gzip_compression_(false),
buffer_usage_reporting_interval_(0) {
video_consumer_ = std::make_unique<DevToolsVideoConsumer>(base::BindRepeating(
&TracingHandler::OnFrameFromVideoConsumer, base::Unretained(this)));
}
TracingHandler::~TracingHandler() = default;
// static
std::vector<TracingHandler*> TracingHandler::ForAgentHost(
DevToolsAgentHostImpl* host) {
return host->HandlersByName<TracingHandler>(Tracing::Metainfo::domainName);
}
void TracingHandler::SetRenderer(int process_host_id,
RenderFrameHostImpl* frame_host) {
if (!frame_host) {
return;
}
video_consumer_->SetFrameSinkId(
frame_host->GetRenderWidgetHost()->GetFrameSinkId());
}
void TracingHandler::Wire(UberDispatcher* dispatcher) {
frontend_ = std::make_unique<Tracing::Frontend>(dispatcher->channel());
Tracing::Dispatcher::wire(dispatcher, this);
}
Response TracingHandler::Disable() {
if (session_)
StopTracing(nullptr);
return Response::Success();
}
namespace {
class TracingNotification : public crdtp::Serializable {
public:
explicit TracingNotification(std::string json) : json_(std::move(json)) {}
void AppendSerialized(std::vector<uint8_t>* out) const override {
crdtp::Status status =
crdtp::json::ConvertJSONToCBOR(crdtp::SpanFrom(json_), out);
DCHECK(status.ok()) << status.ToASCIIString();
}
private:
std::string json_;
};
} // namespace
void TracingHandler::OnTraceDataCollected(
std::unique_ptr<std::string> trace_fragment) {
const std::string valid_trace_fragment =
UpdateTraceDataBuffer(*trace_fragment);
if (valid_trace_fragment.empty())
return;
// 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() + valid_trace_fragment.size() +
messageSuffixSize - trace_data_buffer_state_.offset);
message.append(valid_trace_fragment.c_str() +
trace_data_buffer_state_.offset);
message += "] } }";
frontend_->sendRawNotification(
std::make_unique<TracingNotification>(std::move(message)));
}
void TracingHandler::OnTraceComplete() {
if (!trace_data_buffer_state_.data.empty())
OnTraceDataCollected(std::make_unique<std::string>(""));
DCHECK(trace_data_buffer_state_.data.empty());
DCHECK_EQ(0u, trace_data_buffer_state_.pos);
DCHECK_EQ(0, trace_data_buffer_state_.open_braces);
DCHECK(!trace_data_buffer_state_.in_string);
DCHECK(!trace_data_buffer_state_.slashed);
bool data_loss = session_->HasDataLossOccurred();
process_set_monitor_.reset();
session_.reset();
frontend_->TracingComplete(data_loss);
}
std::string TracingHandler::UpdateTraceDataBuffer(
const std::string& trace_fragment) {
size_t end = 0;
size_t last_open = 0;
TraceDataBufferState& state = trace_data_buffer_state_;
state.offset = 0;
bool update_offset = state.open_braces == 0;
for (; state.pos < trace_fragment.size(); ++state.pos) {
char c = trace_fragment[state.pos];
switch (c) {
case '{':
if (!state.in_string && !state.slashed) {
state.open_braces++;
if (state.open_braces == 1) {
last_open = state.data.size() + state.pos;
if (update_offset) {
state.offset = last_open;
update_offset = false;
}
}
}
break;
case '}':
if (!state.in_string && !state.slashed) {
DCHECK_GT(state.open_braces, 0);
state.open_braces--;
if (state.open_braces == 0)
end = state.data.size() + state.pos + 1;
}
break;
case '"':
if (!state.slashed)
state.in_string = !state.in_string;
break;
case 'u':
if (state.slashed)
state.pos += 4;
break;
}
if (state.in_string && c == '\\') {
state.slashed = !state.slashed;
} else {
state.slashed = false;
}
}
// Next starting position is usually 0 except when we are in the middle of
// processing a unicode character, i.e. \uxxxx.
state.pos -= trace_fragment.size();
std::string complete_str = state.data + trace_fragment;
state.data = complete_str.substr(std::max(end, last_open));
complete_str.resize(end);
return complete_str;
}
void TracingHandler::OnTraceToStreamComplete(const std::string& stream_handle) {
bool data_loss = session_->HasDataLossOccurred();
process_set_monitor_.reset();
session_.reset();
std::string stream_format = (proto_format_ ? Tracing::StreamFormatEnum::Proto
: Tracing::StreamFormatEnum::Json);
std::string stream_compression =
(gzip_compression_ ? Tracing::StreamCompressionEnum::Gzip
: Tracing::StreamCompressionEnum::None);
frontend_->TracingComplete(data_loss, stream_handle, stream_format,
stream_compression);
}
void TracingHandler::Start(Maybe<std::string> categories,
Maybe<std::string> options,
Maybe<double> buffer_usage_reporting_interval,
Maybe<std::string> transfer_mode,
Maybe<std::string> transfer_format,
Maybe<std::string> transfer_compression,
Maybe<Tracing::TraceConfig> config,
Maybe<Binary> perfetto_config,
Maybe<std::string> tracing_backend,
std::unique_ptr<StartCallback> callback) {
bool return_as_stream = transfer_mode.fromMaybe("") ==
Tracing::Start::TransferModeEnum::ReturnAsStream;
bool gzip_compression = transfer_compression.fromMaybe("") ==
Tracing::StreamCompressionEnum::Gzip;
bool proto_format =
transfer_format.fromMaybe("") == Tracing::StreamFormatEnum::Proto;
perfetto::TraceConfig trace_config;
if (perfetto_config.isJust()) {
bool parsed = trace_config.ParseFromArray(
perfetto_config.fromJust().data(), perfetto_config.fromJust().size());
if (!parsed) {
callback->sendFailure(Response::InvalidParams(
"Couldn't parse the supplied perfettoConfig."));
return;
}
if (!trace_config.data_sources_size()) {
callback->sendFailure(Response::InvalidParams(
"Supplied perfettoConfig doesn't have any data sources specified"));
return;
}
// Default to proto format for perfettoConfig, except if it specifies
// convert_to_legacy_json in the data source config.
proto_format = true;
for (const auto& data_source : trace_config.data_sources()) {
if (data_source.config().has_chrome_config() &&
data_source.config().chrome_config().convert_to_legacy_json()) {
proto_format = false;
break;
}
}
ConvertToTrackEventConfigIfNeeded(trace_config);
} else {
base::trace_event::TraceConfig browser_config =
base::trace_event::TraceConfig();
if (config.isJust()) {
base::Value::Dict dict;
CHECK(crdtp::ConvertProtocolValue(*config.fromJust(), &dict));
browser_config =
GetTraceConfigFromDevToolsConfig(base::Value(std::move(dict)));
} else if (categories.isJust() || options.isJust()) {
browser_config = base::trace_event::TraceConfig(categories.fromMaybe(""),
options.fromMaybe(""));
}
trace_config = CreatePerfettoConfiguration(browser_config, return_as_stream,
proto_format);
}
absl::optional<perfetto::BackendType> backend = GetBackendTypeFromParameters(
tracing_backend.fromMaybe(Tracing::TracingBackendEnum::Auto),
trace_config);
if (!backend) {
callback->sendFailure(Response::InvalidParams(
"Unsupported value for tracing_backend parameter."));
return;
}
// Check if we should adopt the startup tracing session. Only the first
// Tracing.start() sent to the browser endpoint can adopt it.
// TODO(crbug.com/1183735): Add tests for system-controlled startup traces.
AttemptAdoptStartupSession(return_as_stream, gzip_compression, proto_format,
*backend);
if (IsTracing()) {
callback->sendFailure(Response::ServerError(
"Tracing has already been started (possibly in another tab)."));
return;
}
if (did_initiate_recording_) {
callback->sendFailure(Response::ServerError(
"Starting trace recording is already in progress"));
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;
}
if (proto_format && !return_as_stream) {
callback->sendFailure(Response::InvalidParams(
"Proto format is only supported when using stream transfer mode."));
return;
}
return_as_stream_ = return_as_stream;
gzip_compression_ = gzip_compression;
proto_format_ = proto_format;
buffer_usage_reporting_interval_ =
buffer_usage_reporting_interval.fromMaybe(0);
did_initiate_recording_ = true;
trace_config_ = std::move(trace_config);
if (session_for_process_filter_) {
process_set_monitor_ = TracingProcessSetMonitor::Start(
*session_for_process_filter_,
base::BindRepeating(&TracingHandler::AddProcessToFilter,
base::Unretained(this)));
std::unordered_set<base::ProcessId> pids = process_set_monitor_->GetPids();
base::ProcessId browser_pid = base::Process::Current().Pid();
pids.insert(browser_pid);
if (auto* gpu_process_host =
GpuProcessHost::Get(GPU_PROCESS_KIND_SANDBOXED,
/* force_create */ false)) {
base::ProcessId gpu_pid = gpu_process_host->process_id();
if (gpu_pid != base::kNullProcessId) {
pids.insert(gpu_pid);
}
}
AddPidsToProcessFilter(pids, trace_config_);
}
session_ = std::make_unique<PerfettoTracingSession>(proto_format_, *backend);
session_->EnableTracing(
trace_config_,
base::BindOnce(&TracingHandler::OnRecordingEnabled,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
perfetto::TraceConfig TracingHandler::CreatePerfettoConfiguration(
const base::trace_event::TraceConfig& browser_config,
bool return_as_stream,
bool proto_format) {
return tracing::GetDefaultPerfettoConfig(
browser_config,
/*privacy_filtering_enabled=*/false,
/*convert_to_legacy_json=*/!proto_format,
perfetto::protos::gen::ChromeConfig::USER_INITIATED,
/*json_agent_label_filter*/
(proto_format || return_as_stream)
? ""
: tracing::mojom::kChromeTraceEventLabel);
}
void TracingHandler::AddProcessToFilter(base::ProcessId pid) {
CHECK(did_initiate_recording_);
CHECK(session_);
AddPidsToProcessFilter({pid}, trace_config_);
session_->ChangeTraceConfig(trace_config_);
}
void TracingHandler::AttemptAdoptStartupSession(
bool return_as_stream,
bool gzip_compression,
bool proto_format,
perfetto::BackendType tracing_backend) {
// Only adopt startup session for browser-level sessions.
if (session_for_process_filter_) {
return;
}
auto* startup_config = tracing::TraceStartupConfig::GetInstance();
if (!startup_config->AttemptAdoptBySessionOwner(
tracing::TraceStartupConfig::SessionOwner::kDevToolsTracingHandler)) {
return;
}
return_as_stream_ = return_as_stream;
gzip_compression_ = gzip_compression;
proto_format_ = proto_format;
base::trace_event::TraceConfig browser_config =
tracing::TraceStartupConfig::GetInstance()->GetTraceConfig();
perfetto::TraceConfig perfetto_config = CreatePerfettoConfiguration(
browser_config, return_as_stream_, proto_format_);
session_ =
std::make_unique<PerfettoTracingSession>(proto_format_, tracing_backend);
session_->AdoptStartupTracingSession(perfetto_config);
}
Response TracingHandler::End() {
if (!session_) {
did_initiate_recording_ = false;
return Response::ServerError("Tracing is not started");
}
if (session_->HasTracingFailed())
return Response::ServerError("Tracing failed");
scoped_refptr<TracingController::TraceDataEndpoint> endpoint;
if (return_as_stream_) {
endpoint = new DevToolsStreamEndpoint(
weak_factory_.GetWeakPtr(),
DevToolsStreamFile::Create(
io_context_, gzip_compression_ || proto_format_ /* binary */));
if (gzip_compression_) {
endpoint = TracingControllerImpl::CreateCompressedStringEndpoint(
endpoint, true /* compress_with_background_priority */);
}
} else {
// Reset the trace data buffer state.
trace_data_buffer_state_ = TracingHandler::TraceDataBufferState();
endpoint = new DevToolsTraceEndpointProxy(weak_factory_.GetWeakPtr());
}
StopTracing(endpoint);
return Response::Success();
}
void TracingHandler::GetCategories(
std::unique_ptr<GetCategoriesCallback> callback) {
// TODO(eseckler): Support this via the perfetto service too.
TracingController::GetInstance()->GetCategories(
base::BindOnce(&TracingHandler::OnCategoriesReceived,
weak_factory_.GetWeakPtr(), std::move(callback)));
}
void TracingHandler::OnRecordingEnabled(std::unique_ptr<StartCallback> callback,
const std::string& error_msg) {
if (!error_msg.empty()) {
callback->sendFailure(Response::ServerError(error_msg));
return;
}
if (!did_initiate_recording_) {
callback->sendFailure(Response::ServerError(
"Tracing was stopped before start has been completed."));
return;
}
EmitFrameTree();
callback->sendSuccess();
SetupTimer(buffer_usage_reporting_interval_);
bool screenshot_enabled;
TRACE_EVENT_CATEGORY_GROUP_ENABLED(
TRACE_DISABLED_BY_DEFAULT("devtools.screenshot"), &screenshot_enabled);
if (screenshot_enabled) {
// Reset number of screenshots received, each time tracing begins.
number_of_screenshots_from_video_consumer_ = 0;
video_consumer_->SetMinAndMaxFrameSize(kMinFrameSize, kMaxFrameSize);
video_consumer_->StartCapture();
}
}
void TracingHandler::OnBufferUsage(bool success,
float percent_full,
size_t approximate_event_count) {
if (!did_initiate_recording_)
return;
if (!success)
return;
// TODO(crbug426117): remove set_value once all clients have switched to
// the new interface of the event.
frontend_->BufferUsage(percent_full, approximate_event_count, percent_full);
}
void TracingHandler::OnCategoriesReceived(
std::unique_ptr<GetCategoriesCallback> callback,
const std::set<std::string>& category_set) {
auto categories = std::make_unique<protocol::Array<std::string>>(
category_set.begin(), category_set.end());
callback->sendSuccess(std::move(categories));
}
void TracingHandler::RequestMemoryDump(
Maybe<bool> deterministic,
Maybe<std::string> level_of_detail,
std::unique_ptr<RequestMemoryDumpCallback> callback) {
if (!IsTracing()) {
callback->sendFailure(Response::ServerError("Tracing is not started"));
return;
}
absl::optional<base::trace_event::MemoryDumpLevelOfDetail> memory_detail =
StringToMemoryDumpLevelOfDetail(level_of_detail.fromMaybe(
Tracing::MemoryDumpLevelOfDetailEnum::Detailed));
if (!memory_detail) {
callback->sendFailure(
Response::ServerError("Invalid levelOfDetail specified."));
return;
}
auto determinism = deterministic.fromMaybe(false)
? base::trace_event::MemoryDumpDeterminism::FORCE_GC
: base::trace_event::MemoryDumpDeterminism::NONE;
auto on_memory_dump_finished =
base::BindOnce(&TracingHandler::OnMemoryDumpFinished,
weak_factory_.GetWeakPtr(), std::move(callback));
memory_instrumentation::MemoryInstrumentation::GetInstance()
->RequestGlobalDumpAndAppendToTrace(
base::trace_event::MemoryDumpType::EXPLICITLY_TRIGGERED,
*memory_detail, determinism, std::move(on_memory_dump_finished));
}
void TracingHandler::OnMemoryDumpFinished(
std::unique_ptr<RequestMemoryDumpCallback> callback,
bool success,
uint64_t dump_id) {
callback->sendSuccess(base::StringPrintf("0x%" PRIx64, dump_id), success);
}
void TracingHandler::OnFrameFromVideoConsumer(
scoped_refptr<media::VideoFrame> frame) {
const SkBitmap skbitmap = DevToolsVideoConsumer::GetSkBitmapFromFrame(frame);
base::TimeTicks reference_time = *frame->metadata().reference_time;
TRACE_EVENT_OBJECT_SNAPSHOT_WITH_ID_AND_TIMESTAMP(
TRACE_DISABLED_BY_DEFAULT("devtools.screenshot"), "Screenshot", 1,
reference_time, std::make_unique<DevToolsTraceableScreenshot>(skbitmap));
++number_of_screenshots_from_video_consumer_;
DCHECK(video_consumer_);
if (number_of_screenshots_from_video_consumer_ >=
DevToolsTraceableScreenshot::kMaximumNumberOfScreenshots) {
video_consumer_->StopCapture();
}
}
Response TracingHandler::RecordClockSyncMarker(const std::string& sync_id) {
if (!IsTracing())
return Response::ServerError("Tracing is not started");
TRACE_EVENT_CLOCK_SYNC_RECEIVER(sync_id);
return Response::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::Milliseconds(std::ceil(usage_reporting_interval));
buffer_usage_poll_timer_ = std::make_unique<base::RepeatingTimer>();
buffer_usage_poll_timer_->Start(
FROM_HERE, interval,
base::BindRepeating(&TracingHandler::UpdateBufferUsage,
weak_factory_.GetWeakPtr()));
}
void TracingHandler::UpdateBufferUsage() {
session_->GetBufferUsage(base::BindOnce(&TracingHandler::OnBufferUsage,
weak_factory_.GetWeakPtr()));
}
void TracingHandler::StopTracing(
const scoped_refptr<TracingController::TraceDataEndpoint>& endpoint) {
DCHECK(session_);
buffer_usage_poll_timer_.reset();
process_set_monitor_.reset();
if (endpoint) {
// Will delete |session_|.
session_->DisableTracing(std::move(endpoint));
} else {
session_.reset();
}
did_initiate_recording_ = false;
video_consumer_->StopCapture();
}
bool TracingHandler::IsTracing() const {
return TracingController::GetInstance()->IsTracing() || g_any_agent_tracing;
}
void TracingHandler::EmitFrameTree() {
auto data = std::make_unique<base::trace_event::TracedValue>();
if (WebContents* wc = host_ ? host_->GetWebContents() : nullptr) {
auto* frame_host =
static_cast<RenderFrameHostImpl*>(wc->GetPrimaryMainFrame());
CHECK(frame_host);
data->SetInteger("frameTreeNodeId",
frame_host->frame_tree_node()->frame_tree_node_id());
data->SetBoolean("persistentIds", true);
data->BeginArray("frames");
wc->ForEachRenderFrameHost([&data](RenderFrameHost* rfh) {
data->BeginDictionary();
FillFrameData(data.get(), static_cast<RenderFrameHostImpl*>(rfh));
data->EndDictionary();
});
data->EndArray();
}
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"TracingStartedInBrowser", TRACE_EVENT_SCOPE_THREAD,
"data", std::move(data));
}
void TracingHandler::WillInitiatePrerender(FrameTreeNode* frame_tree_node) {
if (!did_initiate_recording_) {
return;
}
auto data = std::make_unique<base::trace_event::TracedValue>();
FillFrameData(data.get(), frame_tree_node->current_frame_host());
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"FrameCommittedInBrowser", TRACE_EVENT_SCOPE_THREAD,
"data", std::move(data));
}
void TracingHandler::ReadyToCommitNavigation(
NavigationRequest* navigation_request) {
if (!did_initiate_recording_)
return;
auto data = std::make_unique<base::trace_event::TracedValue>();
FillFrameData(data.get(), navigation_request->GetRenderFrameHost());
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"FrameCommittedInBrowser", TRACE_EVENT_SCOPE_THREAD,
"data", std::move(data));
}
void TracingHandler::FrameDeleted(int frame_tree_node_id) {
if (!did_initiate_recording_)
return;
FrameTreeNode* node = FrameTreeNode::GloballyFindByID(frame_tree_node_id);
if (!node->current_frame_host()) {
// This might happen on prerendering activation when the prerender tree is
// shutting how and the RFH is migrated to a different frame tree.
return;
}
auto data = std::make_unique<base::trace_event::TracedValue>();
data->SetString(
"frame", node->current_frame_host()->devtools_frame_token().ToString());
TRACE_EVENT_INSTANT1(TRACE_DISABLED_BY_DEFAULT("devtools.timeline"),
"FrameDeletedInBrowser", TRACE_EVENT_SCOPE_THREAD,
"data", std::move(data));
}
// static
bool TracingHandler::IsStartupTracingActive() {
return ::tracing::TraceStartupConfig::GetInstance()->IsEnabled();
}
// static
base::trace_event::TraceConfig TracingHandler::GetTraceConfigFromDevToolsConfig(
const base::Value& devtools_config) {
base::Value config = ConvertDictKeyStyle(devtools_config);
base::Value::Dict& config_dict = config.GetDict();
if (std::string* mode = config_dict.FindString(kRecordModeParam)) {
config_dict.Set(kRecordModeParam, ConvertFromCamelCase(*mode, '-'));
}
if (absl::optional<double> buffer_size =
config_dict.FindDouble(kTraceBufferSizeInKb)) {
config_dict.Set(
kTraceBufferSizeInKb,
static_cast<int>(base::saturated_cast<size_t>(buffer_size.value())));
}
return base::trace_event::TraceConfig(config_dict);
}
} // namespace content::protocol