blob: 6599c9cf39274dff575ce4c78e203d899d8e398b [file] [log] [blame]
// Copyright 2018 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 "services/tracing/perfetto/json_trace_exporter.h"
#include "base/json/string_escape.h"
#include "base/logging.h"
#include "base/format_macros.h"
#include "base/strings/stringprintf.h"
#include "base/trace_event/trace_event.h"
#include "services/tracing/public/mojom/perfetto_service.mojom.h"
#include "third_party/perfetto/include/perfetto/tracing/core/trace_config.h"
#include "third_party/perfetto/include/perfetto/tracing/core/trace_packet.h"
#include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_event.pbzero.h"
#include "third_party/perfetto/protos/perfetto/trace/chrome/chrome_trace_packet.pb.h"
using TraceEvent = base::trace_event::TraceEvent;
namespace {
void OutputJSONFromTraceEventProto(
const perfetto::protos::ChromeTraceEvent& event,
std::string* out) {
char phase = static_cast<char>(event.phase());
base::StringAppendF(out,
"{\"pid\":%i,\"tid\":%i,\"ts\":%" PRId64
",\"ph\":\"%c\",\"cat\":\"%s\",\"name\":",
event.process_id(), event.thread_id(), event.timestamp(),
phase, event.category_group_name().c_str());
base::EscapeJSONString(event.name(), true, out);
if (event.has_duration()) {
base::StringAppendF(out, ",\"dur\":%" PRId64, event.duration());
}
if (event.has_thread_duration()) {
base::StringAppendF(out, ",\"tdur\":%" PRId64, event.thread_duration());
}
if (event.has_thread_timestamp()) {
base::StringAppendF(out, ",\"tts\":%" PRId64, event.thread_timestamp());
}
// Output async tts marker field if flag is set.
if (event.flags() & TRACE_EVENT_FLAG_ASYNC_TTS) {
base::StringAppendF(out, ", \"use_async_tts\":1");
}
// If id_ is set, print it out as a hex string so we don't loose any
// bits (it might be a 64-bit pointer).
unsigned int id_flags =
event.flags() & (TRACE_EVENT_FLAG_HAS_ID | TRACE_EVENT_FLAG_HAS_LOCAL_ID |
TRACE_EVENT_FLAG_HAS_GLOBAL_ID);
if (id_flags) {
if (event.has_scope()) {
base::StringAppendF(out, ",\"scope\":\"%s\"", event.scope().c_str());
}
DCHECK(event.has_id());
switch (id_flags) {
case TRACE_EVENT_FLAG_HAS_ID:
base::StringAppendF(out, ",\"id\":\"0x%" PRIx64 "\"",
static_cast<uint64_t>(event.id()));
break;
case TRACE_EVENT_FLAG_HAS_LOCAL_ID:
base::StringAppendF(out, ",\"id2\":{\"local\":\"0x%" PRIx64 "\"}",
static_cast<uint64_t>(event.id()));
break;
case TRACE_EVENT_FLAG_HAS_GLOBAL_ID:
base::StringAppendF(out, ",\"id2\":{\"global\":\"0x%" PRIx64 "\"}",
static_cast<uint64_t>(event.id()));
break;
default:
NOTREACHED() << "More than one of the ID flags are set";
break;
}
}
if (event.flags() & TRACE_EVENT_FLAG_BIND_TO_ENCLOSING)
base::StringAppendF(out, ",\"bp\":\"e\"");
if (event.has_bind_id()) {
base::StringAppendF(out, ",\"bind_id\":\"0x%" PRIx64 "\"",
static_cast<uint64_t>(event.bind_id()));
}
if (event.flags() & TRACE_EVENT_FLAG_FLOW_IN)
base::StringAppendF(out, ",\"flow_in\":true");
if (event.flags() & TRACE_EVENT_FLAG_FLOW_OUT)
base::StringAppendF(out, ",\"flow_out\":true");
// Instant events also output their scope.
if (phase == TRACE_EVENT_PHASE_INSTANT) {
char scope = '?';
switch (event.flags() & TRACE_EVENT_FLAG_SCOPE_MASK) {
case TRACE_EVENT_SCOPE_GLOBAL:
scope = TRACE_EVENT_SCOPE_NAME_GLOBAL;
break;
case TRACE_EVENT_SCOPE_PROCESS:
scope = TRACE_EVENT_SCOPE_NAME_PROCESS;
break;
case TRACE_EVENT_SCOPE_THREAD:
scope = TRACE_EVENT_SCOPE_NAME_THREAD;
break;
}
base::StringAppendF(out, ",\"s\":\"%c\"", scope);
}
*out += ",\"args\":{";
for (int i = 0; i < event.args_size(); ++i) {
auto& arg = event.args(i);
if (i > 0) {
*out += ",";
}
*out += "\"";
*out += arg.name();
*out += "\":";
TraceEvent::TraceValue value;
if (arg.has_bool_value()) {
value.as_bool = arg.bool_value();
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_BOOL, value, out);
continue;
}
if (arg.has_uint_value()) {
value.as_uint = arg.uint_value();
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_UINT, value, out);
continue;
}
if (arg.has_int_value()) {
value.as_int = arg.int_value();
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_INT, value, out);
continue;
}
if (arg.has_double_value()) {
value.as_double = arg.double_value();
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_DOUBLE, value, out);
continue;
}
if (arg.has_pointer_value()) {
value.as_pointer = reinterpret_cast<void*>(arg.pointer_value());
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_POINTER, value, out);
continue;
}
if (arg.has_string_value()) {
std::string str = arg.string_value();
value.as_string = &str[0];
TraceEvent::AppendValueAsJSON(TRACE_VALUE_TYPE_STRING, value, out);
continue;
}
if (arg.has_json_value()) {
*out += arg.json_value();
continue;
}
}
*out += "}}";
}
} // namespace
namespace tracing {
JSONTraceExporter::JSONTraceExporter(const std::string& config,
perfetto::TracingService* service)
: config_(config) {
consumer_endpoint_ = service->ConnectConsumer(this);
}
JSONTraceExporter::~JSONTraceExporter() = default;
void JSONTraceExporter::OnConnect() {
// Start tracing.
perfetto::TraceConfig trace_config;
trace_config.add_buffers()->set_size_kb(4096 * 100);
auto* ds_config = trace_config.add_data_sources()->mutable_config();
ds_config->set_name(mojom::kTraceEventDataSourceName);
ds_config->set_target_buffer(0);
auto* chrome_config = ds_config->mutable_chrome_config();
chrome_config->set_trace_config(config_);
consumer_endpoint_->EnableTracing(trace_config);
}
void JSONTraceExporter::OnDisconnect() {}
void JSONTraceExporter::StopAndFlush(OnTraceEventJSONCallback callback) {
DCHECK(!json_callback_ && callback);
json_callback_ = callback;
consumer_endpoint_->DisableTracing();
consumer_endpoint_->ReadBuffers();
}
void JSONTraceExporter::OnTraceData(std::vector<perfetto::TracePacket> packets,
bool has_more) {
DCHECK(json_callback_);
DCHECK(!packets.empty() || !has_more);
std::string out;
if (!has_output_json_preamble_) {
out = "{\"traceEvents\":[";
has_output_json_preamble_ = true;
}
for (auto& encoded_packet : packets) {
perfetto::protos::ChromeTracePacket packet;
bool decoded = encoded_packet.Decode(&packet);
DCHECK(decoded);
if (!packet.has_chrome_events()) {
continue;
}
const perfetto::protos::ChromeEventBundle& bundle = packet.chrome_events();
for (const perfetto::protos::ChromeTraceEvent& event :
bundle.trace_events()) {
if (has_output_first_event_) {
out += ",";
} else {
has_output_first_event_ = true;
}
OutputJSONFromTraceEventProto(event, &out);
}
}
if (!has_more) {
out += "]}";
}
json_callback_.Run(out, has_more);
}
} // namespace tracing