blob: 04e3a5d1c1818d6ed821898d25a998d230ca8503 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// For bazel build.
#include "perfetto/base/build_config.h"
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
#include "perfetto/ext/trace_processor/export_json.h"
#include "src/trace_processor/export_json.h"
#include <inttypes.h>
#include <json/reader.h>
#include <json/value.h>
#include <json/writer.h>
#include <stdio.h>
#include <cstring>
#include <deque>
#include <limits>
#include "perfetto/ext/base/string_splitter.h"
#include "src/trace_processor/metadata.h"
#include "src/trace_processor/trace_processor_context.h"
#include "src/trace_processor/trace_processor_storage_impl.h"
#include "src/trace_processor/trace_storage.h"
namespace perfetto {
namespace trace_processor {
namespace json {
namespace {
using IndexMap = perfetto::trace_processor::TraceStorage::Stats::IndexMap;
const char kLegacyEventArgsKey[] = "legacy_event";
const char kLegacyEventOriginalTidKey[] = "original_tid";
const char kLegacyEventCategoryKey[] = "category";
const char kLegacyEventNameKey[] = "name";
const char kLegacyEventPhaseKey[] = "phase";
const char kLegacyEventDurationNsKey[] = "duration_ns";
const char kLegacyEventThreadTimestampNsKey[] = "thread_timestamp_ns";
const char kLegacyEventThreadDurationNsKey[] = "thread_duration_ns";
const char kLegacyEventThreadInstructionCountKey[] = "thread_instruction_count";
const char kLegacyEventThreadInstructionDeltaKey[] = "thread_instruction_delta";
const char kLegacyEventUseAsyncTtsKey[] = "use_async_tts";
const char kLegacyEventUnscopedIdKey[] = "unscoped_id";
const char kLegacyEventGlobalIdKey[] = "global_id";
const char kLegacyEventLocalIdKey[] = "local_id";
const char kLegacyEventIdScopeKey[] = "id_scope";
const char kLegacyEventBindIdKey[] = "bind_id";
const char kLegacyEventBindToEnclosingKey[] = "bind_to_enclosing";
const char kLegacyEventFlowDirectionKey[] = "flow_direction";
const char kFlowDirectionValueIn[] = "in";
const char kFlowDirectionValueOut[] = "out";
const char kFlowDirectionValueInout[] = "inout";
const char kStrippedArgument[] = "__stripped__";
const char* GetNonNullString(const TraceStorage* storage, StringId id) {
return id == kNullStringId ? "" : storage->GetString(id).c_str();
}
class FileWriter : public OutputWriter {
public:
FileWriter(FILE* file) : file_(file) {}
~FileWriter() override { fflush(file_); }
util::Status AppendString(const std::string& s) override {
size_t written =
fwrite(s.data(), sizeof(std::string::value_type), s.size(), file_);
if (written != s.size())
return util::ErrStatus("Error writing to file: %d", ferror(file_));
return util::OkStatus();
}
private:
FILE* file_;
};
class TraceFormatWriter {
public:
TraceFormatWriter(OutputWriter* output,
ArgumentFilterPredicate argument_filter,
MetadataFilterPredicate metadata_filter,
LabelFilterPredicate label_filter)
: output_(output),
argument_filter_(argument_filter),
metadata_filter_(metadata_filter),
label_filter_(label_filter),
first_event_(true) {
WriteHeader();
}
~TraceFormatWriter() { WriteFooter(); }
void WriteCommonEvent(const Json::Value& event) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
// Pop end events with smaller or equal timestamps.
PopEndEvents(event["ts"].asInt64());
DoWriteEvent(event);
}
void PushEndEvent(const Json::Value& event) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
// Pop any end events that end before the new one.
PopEndEvents(event["ts"].asInt64() - 1);
// Catapult doesn't handle out-of-order begin/end events well, especially
// when their timestamps are the same, but their order is incorrect. Since
// our events are sorted by begin timestamp, we only have to reorder end
// events. We do this by buffering them into a stack, so that both begin &
// end events of potential child events have been emitted before we emit the
// end of a parent event.
end_events_.push_back(event);
}
void WriteMetadataEvent(const char* metadata_type,
const char* metadata_value,
uint32_t tid,
uint32_t pid) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
if (!first_event_)
output_->AppendString(",\n");
Json::FastWriter writer;
writer.omitEndingLineFeed();
Json::Value value;
value["ph"] = "M";
value["cat"] = "__metadata";
value["ts"] = 0;
value["name"] = metadata_type;
value["tid"] = static_cast<int32_t>(tid);
value["pid"] = static_cast<int32_t>(pid);
Json::Value args;
args["name"] = metadata_value;
value["args"] = args;
output_->AppendString(writer.write(value));
first_event_ = false;
}
void MergeMetadata(const Json::Value& value) {
for (const auto& member : value.getMemberNames()) {
metadata_[member] = value[member];
}
}
void AppendTelemetryMetadataString(const char* key, const char* value) {
metadata_["telemetry"][key].append(value);
}
void AppendTelemetryMetadataInt(const char* key, int64_t value) {
metadata_["telemetry"][key].append(Json::Int64(value));
}
void AppendTelemetryMetadataBool(const char* key, bool value) {
metadata_["telemetry"][key].append(value);
}
void SetTelemetryMetadataTimestamp(const char* key, int64_t value) {
metadata_["telemetry"][key] = value / 1000.0;
}
void SetStats(const char* key, int64_t value) {
metadata_["trace_processor_stats"][key] = Json::Int64(value);
}
void SetStats(const char* key, const IndexMap& indexed_values) {
constexpr const char* kBufferStatsPrefix = "traced_buf_";
// Stats for the same buffer should be grouped together in the JSON.
if (strncmp(kBufferStatsPrefix, key, strlen(kBufferStatsPrefix)) == 0) {
for (const auto& value : indexed_values) {
metadata_["trace_processor_stats"]["traced_buf"][value.first]
[key + strlen(kBufferStatsPrefix)] = Json::Int64(value.second);
}
return;
}
// Other indexed value stats are exported as array under their key.
for (const auto& value : indexed_values) {
metadata_["trace_processor_stats"][key][value.first] =
Json::Int64(value.second);
}
}
void AddSystemTraceData(const std::string& data) {
system_trace_data_ += data;
}
void AddUserTraceData(const std::string& data) {
if (user_trace_data_.empty())
user_trace_data_ = "[";
user_trace_data_ += data;
}
private:
void WriteHeader() {
if (!label_filter_)
output_->AppendString("{\"traceEvents\":[\n");
}
void WriteFooter() {
PopEndEvents(std::numeric_limits<int64_t>::max());
// Filter metadata entries.
if (metadata_filter_) {
for (const auto& member : metadata_.getMemberNames()) {
if (!metadata_filter_(member.c_str()))
metadata_[member] = kStrippedArgument;
}
}
Json::FastWriter writer;
writer.omitEndingLineFeed();
if ((!label_filter_ || label_filter_("traceEvents")) &&
!user_trace_data_.empty()) {
user_trace_data_ += "]";
Json::Reader reader;
Json::Value result;
if (reader.parse(user_trace_data_, result)) {
for (const auto& event : result) {
WriteCommonEvent(event);
}
} else {
PERFETTO_DLOG(
"can't parse legacy user json trace export, skipping. data: %s",
user_trace_data_.c_str());
}
}
if (!label_filter_)
output_->AppendString("]");
if ((!label_filter_ || label_filter_("systemTraceEvents")) &&
!system_trace_data_.empty()) {
output_->AppendString(",\"systemTraceEvents\":\n");
output_->AppendString(writer.write(Json::Value(system_trace_data_)));
}
if ((!label_filter_ || label_filter_("metadata")) && !metadata_.empty()) {
output_->AppendString(",\"metadata\":\n");
output_->AppendString(writer.write(metadata_));
}
if (!label_filter_)
output_->AppendString("}");
}
void DoWriteEvent(const Json::Value& event) {
if (!first_event_)
output_->AppendString(",\n");
Json::FastWriter writer;
writer.omitEndingLineFeed();
ArgumentNameFilterPredicate argument_name_filter;
bool strip_args =
argument_filter_ &&
!argument_filter_(event["cat"].asCString(), event["name"].asCString(),
&argument_name_filter);
if ((strip_args || argument_name_filter) && event.isMember("args")) {
Json::Value event_copy = event;
if (strip_args) {
event_copy["args"] = kStrippedArgument;
} else {
auto& args = event_copy["args"];
for (const auto& member : event["args"].getMemberNames()) {
if (!argument_name_filter(member.c_str()))
args[member] = kStrippedArgument;
}
}
output_->AppendString(writer.write(event_copy));
} else {
output_->AppendString(writer.write(event));
}
first_event_ = false;
}
void PopEndEvents(int64_t max_ts) {
while (!end_events_.empty()) {
int64_t ts = end_events_.back()["ts"].asInt64();
if (ts > max_ts)
break;
DoWriteEvent(end_events_.back());
end_events_.pop_back();
}
}
OutputWriter* output_;
ArgumentFilterPredicate argument_filter_;
MetadataFilterPredicate metadata_filter_;
LabelFilterPredicate label_filter_;
bool first_event_;
Json::Value metadata_;
std::string system_trace_data_;
std::string user_trace_data_;
std::deque<Json::Value> end_events_;
};
std::string PrintUint64(uint64_t x) {
char hex_str[19];
sprintf(hex_str, "0x%" PRIx64, x);
return hex_str;
}
class ArgsBuilder {
public:
explicit ArgsBuilder(const TraceStorage* storage)
: storage_(storage),
empty_value_(Json::objectValue),
nan_value_(Json::StaticString("NaN")),
inf_value_(Json::StaticString("Infinity")),
neg_inf_value_(Json::StaticString("-Infinity")) {
const auto& arg_table = storage->arg_table();
uint32_t count = arg_table.row_count();
if (count == 0) {
args_sets_.resize(1, empty_value_);
return;
}
args_sets_.resize(arg_table.arg_set_id()[count - 1] + 1, empty_value_);
for (uint32_t i = 0; i < count; ++i) {
ArgSetId set_id = arg_table.arg_set_id()[i];
const char* key = GetNonNullString(storage_, arg_table.key()[i]);
Variadic value = storage_->GetArgValue(i);
AppendArg(set_id, key, VariadicToJson(value));
}
PostprocessArgs();
}
const Json::Value& GetArgs(ArgSetId set_id) const {
// If |set_id| was empty and added to the storage last, it may not be in
// args_sets_.
if (set_id > args_sets_.size())
return empty_value_;
return args_sets_[set_id];
}
private:
Json::Value VariadicToJson(Variadic variadic) {
switch (variadic.type) {
case Variadic::kInt:
return Json::Int64(variadic.int_value);
case Variadic::kUint:
return Json::UInt64(variadic.uint_value);
case Variadic::kString:
return GetNonNullString(storage_, variadic.string_value);
case Variadic::kReal:
if (std::isnan(variadic.real_value)) {
return nan_value_;
} else if (std::isinf(variadic.real_value) && variadic.real_value > 0) {
return inf_value_;
} else if (std::isinf(variadic.real_value) && variadic.real_value < 0) {
return neg_inf_value_;
} else {
return variadic.real_value;
}
case Variadic::kPointer:
return PrintUint64(variadic.pointer_value);
case Variadic::kBool:
return variadic.bool_value;
case Variadic::kJson:
Json::Reader reader;
Json::Value result;
reader.parse(GetNonNullString(storage_, variadic.json_value), result);
return result;
}
PERFETTO_FATAL("Not reached"); // For gcc.
}
void AppendArg(ArgSetId set_id,
const std::string& key,
const Json::Value& value) {
Json::Value* target = &args_sets_[set_id];
for (base::StringSplitter parts(key, '.'); parts.Next();) {
if (PERFETTO_UNLIKELY(!target->isNull() && !target->isObject())) {
PERFETTO_DLOG("Malformed arguments. Can't append %s to %s.",
key.c_str(), args_sets_[set_id].toStyledString().c_str());
return;
}
std::string key_part = parts.cur_token();
size_t bracketpos = key_part.find('[');
if (bracketpos == key_part.npos) { // A single item
target = &(*target)[key_part];
} else { // A list item
target = &(*target)[key_part.substr(0, bracketpos)];
while (bracketpos != key_part.npos) {
std::string index = key_part.substr(
bracketpos + 1, key_part.find(']', bracketpos) - bracketpos - 1);
if (PERFETTO_UNLIKELY(!target->isNull() && !target->isArray())) {
PERFETTO_DLOG("Malformed arguments. Can't append %s to %s.",
key.c_str(),
args_sets_[set_id].toStyledString().c_str());
return;
}
target = &(*target)[stoi(index)];
bracketpos = key_part.find('[', bracketpos + 1);
}
}
}
*target = value;
}
void PostprocessArgs() {
for (Json::Value& args : args_sets_) {
// Move all fields from "debug" key to upper level.
if (args.isMember("debug")) {
Json::Value debug = args["debug"];
args.removeMember("debug");
for (const auto& member : debug.getMemberNames()) {
args[member] = debug[member];
}
}
// Rename source fields.
if (args.isMember("task")) {
if (args["task"].isMember("posted_from")) {
Json::Value posted_from = args["task"]["posted_from"];
args["task"].removeMember("posted_from");
if (posted_from.isMember("function_name")) {
args["src_func"] = posted_from["function_name"];
args["src_file"] = posted_from["file_name"];
} else if (posted_from.isMember("file_name")) {
args["src"] = posted_from["file_name"];
}
}
if (args["task"].empty())
args.removeMember("task");
}
}
}
const TraceStorage* storage_;
std::vector<Json::Value> args_sets_;
const Json::Value empty_value_;
const Json::Value nan_value_;
const Json::Value inf_value_;
const Json::Value neg_inf_value_;
};
void ConvertLegacyFlowEventArgs(const Json::Value& legacy_args,
Json::Value* event) {
if (legacy_args.isMember(kLegacyEventBindIdKey)) {
(*event)["bind_id"] =
PrintUint64(legacy_args[kLegacyEventBindIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventBindToEnclosingKey))
(*event)["bp"] = "e";
if (legacy_args.isMember(kLegacyEventFlowDirectionKey)) {
const char* val = legacy_args[kLegacyEventFlowDirectionKey].asCString();
if (strcmp(val, kFlowDirectionValueIn) == 0) {
(*event)["flow_in"] = true;
} else if (strcmp(val, kFlowDirectionValueOut) == 0) {
(*event)["flow_out"] = true;
} else {
PERFETTO_DCHECK(strcmp(val, kFlowDirectionValueInout) == 0);
(*event)["flow_in"] = true;
(*event)["flow_out"] = true;
}
}
}
util::Status ExportThreadNames(const TraceStorage* storage,
TraceFormatWriter* writer) {
const auto& thread_table = storage->thread_table();
const auto& process_table = storage->process_table();
for (UniqueTid i = 1; i < thread_table.row_count(); ++i) {
auto opt_name = thread_table.name()[i];
if (!opt_name.is_null()) {
const char* thread_name = GetNonNullString(storage, opt_name);
auto opt_upid = thread_table.upid()[i];
uint32_t pid = opt_upid ? process_table.pid()[*opt_upid] : 0;
writer->WriteMetadataEvent("thread_name", thread_name,
thread_table.tid()[i], pid);
}
}
return util::OkStatus();
}
util::Status ExportProcessNames(const TraceStorage* storage,
TraceFormatWriter* writer) {
const auto& process_table = storage->process_table();
for (UniquePid i = 1; i < process_table.row_count(); ++i) {
auto opt_name = process_table.name()[i];
if (!opt_name.is_null()) {
const char* process_name = GetNonNullString(storage, opt_name);
writer->WriteMetadataEvent("process_name", process_name, 0,
process_table.pid()[i]);
}
}
return util::OkStatus();
}
util::Status ExportSlices(const TraceStorage* storage,
const ArgsBuilder& args_builder,
TraceFormatWriter* writer) {
const auto& slices = storage->slice_table();
for (uint32_t i = 0; i < slices.row_count(); ++i) {
Json::Value event;
event["ts"] = Json::Int64(slices.ts()[i] / 1000);
event["cat"] = GetNonNullString(storage, slices.category()[i]);
event["name"] = GetNonNullString(storage, slices.name()[i]);
event["pid"] = 0;
event["tid"] = 0;
int32_t legacy_tid = 0;
event["args"] =
args_builder.GetArgs(slices.arg_set_id()[i]); // Makes a copy.
if (event["args"].isMember(kLegacyEventArgsKey)) {
ConvertLegacyFlowEventArgs(event["args"][kLegacyEventArgsKey], &event);
if (event["args"][kLegacyEventArgsKey].isMember(
kLegacyEventOriginalTidKey)) {
legacy_tid = static_cast<int32_t>(
event["args"][kLegacyEventArgsKey][kLegacyEventOriginalTidKey]
.asInt());
}
event["args"].removeMember(kLegacyEventArgsKey);
}
// To prevent duplicate export of slices, only export slices on descriptor
// or chrome tracks (i.e. TrackEvent slices). Slices on other tracks may
// also be present as raw events and handled by trace_to_text. Only add more
// track types here if they are not already covered by trace_to_text.
uint32_t track_id = slices.track_id()[i];
const auto& track_table = storage->track_table();
uint32_t track_row = *track_table.id().IndexOf(TrackId{track_id});
auto track_args_id = track_table.source_arg_set_id()[track_row];
if (!track_args_id)
continue;
const auto& track_args = args_builder.GetArgs(*track_args_id);
bool legacy_chrome_track = track_args["source"].asString() == "chrome";
if (!track_args.isMember("source") ||
(!legacy_chrome_track &&
track_args["source"].asString() != "descriptor")) {
continue;
}
const auto& thread_table = storage->thread_table();
const auto& process_table = storage->process_table();
const auto& thread_track = storage->thread_track_table();
const auto& process_track = storage->process_track_table();
const auto& thread_slices = storage->thread_slices();
const auto& virtual_track_slices = storage->virtual_track_slices();
int64_t duration_ns = slices.dur()[i];
int64_t thread_ts_ns = 0;
int64_t thread_duration_ns = 0;
int64_t thread_instruction_count = 0;
int64_t thread_instruction_delta = 0;
base::Optional<uint32_t> thread_slice_row =
thread_slices.FindRowForSliceId(i);
if (thread_slice_row) {
thread_ts_ns = thread_slices.thread_timestamp_ns()[*thread_slice_row];
thread_duration_ns =
thread_slices.thread_duration_ns()[*thread_slice_row];
thread_instruction_count =
thread_slices.thread_instruction_counts()[*thread_slice_row];
thread_instruction_delta =
thread_slices.thread_instruction_deltas()[*thread_slice_row];
} else {
base::Optional<uint32_t> vtrack_slice_row =
virtual_track_slices.FindRowForSliceId(i);
if (vtrack_slice_row) {
thread_ts_ns =
virtual_track_slices.thread_timestamp_ns()[*vtrack_slice_row];
thread_duration_ns =
virtual_track_slices.thread_duration_ns()[*vtrack_slice_row];
thread_instruction_count =
virtual_track_slices.thread_instruction_counts()[*vtrack_slice_row];
thread_instruction_delta =
virtual_track_slices.thread_instruction_deltas()[*vtrack_slice_row];
}
}
auto opt_thread_track_row = thread_track.id().IndexOf(TrackId{track_id});
if (opt_thread_track_row) {
// Synchronous (thread) slice or instant event.
UniqueTid utid = thread_track.utid()[*opt_thread_track_row];
event["tid"] = static_cast<int32_t>(thread_table.tid()[utid]);
auto opt_upid = thread_table.upid()[utid];
if (opt_upid) {
event["pid"] = static_cast<int32_t>(process_table.pid()[*opt_upid]);
}
if (duration_ns == 0) {
// Use "I" instead of "i" phase for backwards-compat with old consumers.
event["ph"] = "I";
if (thread_ts_ns > 0) {
event["tts"] = Json::Int64(thread_ts_ns / 1000);
}
if (thread_instruction_count > 0) {
event["ticount"] = Json::Int64(thread_instruction_count);
}
event["s"] = "t";
} else {
if (duration_ns > 0) {
event["ph"] = "X";
event["dur"] = Json::Int64(duration_ns / 1000);
} else {
// If the slice didn't finish, the duration may be negative. Only
// write a begin event without end event in this case.
event["ph"] = "B";
}
if (thread_ts_ns > 0) {
event["tts"] = Json::Int64(thread_ts_ns / 1000);
// Only write thread duration for completed events.
if (duration_ns > 0)
event["tdur"] = Json::Int64(thread_duration_ns / 1000);
}
if (thread_instruction_count > 0) {
event["ticount"] = Json::Int64(thread_instruction_count);
// Only write thread instruction delta for completed events.
if (duration_ns > 0)
event["tidelta"] = Json::Int64(thread_instruction_delta);
}
}
writer->WriteCommonEvent(event);
} else if (!legacy_chrome_track ||
(legacy_chrome_track && track_args.isMember("source_id"))) {
// Async event slice.
auto opt_process_row = process_track.id().IndexOf(TrackId{track_id});
if (legacy_chrome_track) {
// Legacy async tracks are always process-associated.
PERFETTO_DCHECK(opt_process_row);
uint32_t upid = process_track.upid()[*opt_process_row];
event["pid"] = static_cast<int32_t>(process_table.pid()[upid]);
event["tid"] = legacy_tid
? legacy_tid
: static_cast<int32_t>(process_table.pid()[upid]);
// Preserve original event IDs for legacy tracks. This is so that e.g.
// memory dump IDs show up correctly in the JSON trace.
PERFETTO_DCHECK(track_args.isMember("source_id"));
PERFETTO_DCHECK(track_args.isMember("source_id_is_process_scoped"));
PERFETTO_DCHECK(track_args.isMember("source_scope"));
uint64_t source_id =
static_cast<uint64_t>(track_args["source_id"].asInt64());
std::string source_scope = track_args["source_scope"].asString();
if (!source_scope.empty())
event["scope"] = source_scope;
bool source_id_is_process_scoped =
track_args["source_id_is_process_scoped"].asBool();
if (source_id_is_process_scoped) {
event["id2"]["local"] = PrintUint64(source_id);
} else {
// Some legacy importers don't understand "id2" fields, so we use the
// "usually" global "id" field instead. This works as long as the
// event phase is not in {'N', 'D', 'O', '(', ')'}, see
// "LOCAL_ID_PHASES" in catapult.
event["id"] = PrintUint64(source_id);
}
} else {
if (opt_process_row) {
uint32_t upid = process_track.upid()[*opt_process_row];
event["id2"]["local"] = PrintUint64(track_id);
event["pid"] = static_cast<int32_t>(process_table.pid()[upid]);
event["tid"] = legacy_tid
? legacy_tid
: static_cast<int32_t>(process_table.pid()[upid]);
} else {
// Some legacy importers don't understand "id2" fields, so we use the
// "usually" global "id" field instead. This works as long as the
// event phase is not in {'N', 'D', 'O', '(', ')'}, see
// "LOCAL_ID_PHASES" in catapult.
event["id"] = PrintUint64(track_id);
}
}
if (thread_ts_ns > 0) {
event["tts"] = Json::Int64(thread_ts_ns / 1000);
event["use_async_tts"] = Json::Int(1);
}
if (thread_instruction_count > 0) {
event["ticount"] = Json::Int64(thread_instruction_count);
event["use_async_tts"] = Json::Int(1);
}
if (duration_ns == 0) { // Instant async event.
event["ph"] = "n";
writer->WriteCommonEvent(event);
} else { // Async start and end.
event["ph"] = "b";
writer->WriteCommonEvent(event);
// If the slice didn't finish, the duration may be negative. Don't
// write the end event in this case.
if (duration_ns > 0) {
event["ph"] = "e";
event["ts"] = Json::Int64((slices.ts()[i] + duration_ns) / 1000);
if (thread_ts_ns > 0) {
event["tts"] =
Json::Int64((thread_ts_ns + thread_duration_ns) / 1000);
}
if (thread_instruction_count > 0) {
event["ticount"] = Json::Int64(
(thread_instruction_count + thread_instruction_delta));
}
event["args"].clear();
writer->PushEndEvent(event);
}
}
} else {
// Global or process-scoped instant event.
PERFETTO_DCHECK(duration_ns == 0);
// Use "I" instead of "i" phase for backwards-compat with old consumers.
event["ph"] = "I";
auto opt_process_row = process_track.id().IndexOf(TrackId{track_id});
if (opt_process_row.has_value()) {
uint32_t upid = process_track.upid()[*opt_process_row];
event["pid"] = static_cast<int32_t>(process_table.pid()[upid]);
event["tid"] = legacy_tid
? legacy_tid
: static_cast<int32_t>(process_table.pid()[upid]);
event["s"] = "p";
} else {
event["s"] = "g";
}
writer->WriteCommonEvent(event);
}
}
return util::OkStatus();
}
Json::Value ConvertLegacyRawEventToJson(const TraceStorage* storage,
const ArgsBuilder& args_builder,
uint32_t index) {
const auto& thread_table = storage->thread_table();
const auto& process_table = storage->process_table();
const auto& events = storage->raw_events();
Json::Value event;
event["ts"] = Json::Int64(events.timestamps()[index] / 1000);
UniqueTid utid = static_cast<UniqueTid>(events.utids()[index]);
event["tid"] = static_cast<int32_t>(thread_table.tid()[utid]);
event["pid"] = 0;
auto opt_upid = thread_table.upid()[utid];
if (opt_upid)
event["pid"] = static_cast<int32_t>(process_table.pid()[*opt_upid]);
// Raw legacy events store all other params in the arg set. Make a copy of
// the converted args here, parse, and then remove the legacy params.
event["args"] = args_builder.GetArgs(events.arg_set_ids()[index]);
const Json::Value& legacy_args = event["args"][kLegacyEventArgsKey];
PERFETTO_DCHECK(legacy_args.isMember(kLegacyEventCategoryKey));
event["cat"] = legacy_args[kLegacyEventCategoryKey];
PERFETTO_DCHECK(legacy_args.isMember(kLegacyEventNameKey));
event["name"] = legacy_args[kLegacyEventNameKey];
PERFETTO_DCHECK(legacy_args.isMember(kLegacyEventPhaseKey));
event["ph"] = legacy_args[kLegacyEventPhaseKey];
// Object snapshot events are supposed to have a mandatory "snapshot" arg,
// which may be removed in trace processor if it is empty.
if (legacy_args[kLegacyEventPhaseKey] == "O" &&
!event["args"].isMember("snapshot")) {
event["args"]["snapshot"] = Json::Value(Json::objectValue);
}
if (legacy_args.isMember(kLegacyEventDurationNsKey))
event["dur"] = legacy_args[kLegacyEventDurationNsKey].asInt64() / 1000;
if (legacy_args.isMember(kLegacyEventThreadTimestampNsKey)) {
event["tts"] =
legacy_args[kLegacyEventThreadTimestampNsKey].asInt64() / 1000;
}
if (legacy_args.isMember(kLegacyEventThreadDurationNsKey)) {
event["tdur"] =
legacy_args[kLegacyEventThreadDurationNsKey].asInt64() / 1000;
}
if (legacy_args.isMember(kLegacyEventThreadInstructionCountKey))
event["ticount"] = legacy_args[kLegacyEventThreadInstructionCountKey];
if (legacy_args.isMember(kLegacyEventThreadInstructionDeltaKey))
event["tidelta"] = legacy_args[kLegacyEventThreadInstructionDeltaKey];
if (legacy_args.isMember(kLegacyEventUseAsyncTtsKey))
event["use_async_tts"] = legacy_args[kLegacyEventUseAsyncTtsKey];
if (legacy_args.isMember(kLegacyEventUnscopedIdKey)) {
event["id"] =
PrintUint64(legacy_args[kLegacyEventUnscopedIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventGlobalIdKey)) {
event["id2"]["global"] =
PrintUint64(legacy_args[kLegacyEventGlobalIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventLocalIdKey)) {
event["id2"]["local"] =
PrintUint64(legacy_args[kLegacyEventLocalIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventIdScopeKey))
event["scope"] = legacy_args[kLegacyEventIdScopeKey];
ConvertLegacyFlowEventArgs(legacy_args, &event);
event["args"].removeMember(kLegacyEventArgsKey);
return event;
}
util::Status ExportRawEvents(const TraceStorage* storage,
const ArgsBuilder& args_builder,
TraceFormatWriter* writer) {
base::Optional<StringId> raw_legacy_event_key_id =
storage->string_pool().GetId("track_event.legacy_event");
base::Optional<StringId> raw_legacy_system_trace_event_id =
storage->string_pool().GetId("chrome_event.legacy_system_trace");
base::Optional<StringId> raw_legacy_user_trace_event_id =
storage->string_pool().GetId("chrome_event.legacy_user_trace");
base::Optional<StringId> raw_chrome_metadata_event_id =
storage->string_pool().GetId("chrome_event.metadata");
const auto& events = storage->raw_events();
for (uint32_t i = 0; i < events.raw_event_count(); ++i) {
if (raw_legacy_event_key_id &&
events.name_ids()[i] == *raw_legacy_event_key_id) {
Json::Value event = ConvertLegacyRawEventToJson(storage, args_builder, i);
writer->WriteCommonEvent(event);
} else if (raw_legacy_system_trace_event_id &&
events.name_ids()[i] == *raw_legacy_system_trace_event_id) {
Json::Value args = args_builder.GetArgs(events.arg_set_ids()[i]);
PERFETTO_DCHECK(args.isMember("data"));
writer->AddSystemTraceData(args["data"].asString());
} else if (raw_legacy_user_trace_event_id &&
events.name_ids()[i] == *raw_legacy_user_trace_event_id) {
Json::Value args = args_builder.GetArgs(events.arg_set_ids()[i]);
PERFETTO_DCHECK(args.isMember("data"));
writer->AddUserTraceData(args["data"].asString());
} else if (raw_chrome_metadata_event_id &&
events.name_ids()[i] == *raw_chrome_metadata_event_id) {
Json::Value args = args_builder.GetArgs(events.arg_set_ids()[i]);
writer->MergeMetadata(args);
}
}
return util::OkStatus();
}
util::Status ExportCpuProfileSamples(const TraceStorage* storage,
TraceFormatWriter* writer) {
const auto& thread_table = storage->thread_table();
const auto& process_table = storage->process_table();
const tables::CpuProfileStackSampleTable& samples =
storage->cpu_profile_stack_sample_table();
for (uint32_t i = 0; i < samples.row_count(); ++i) {
Json::Value event;
event["ts"] = Json::Int64(samples.ts()[i] / 1000);
UniqueTid utid = static_cast<UniqueTid>(samples.utid()[i]);
event["tid"] = static_cast<int32_t>(thread_table.tid()[utid]);
auto opt_upid = thread_table.upid()[utid];
if (opt_upid)
event["pid"] = static_cast<int32_t>(process_table.pid()[*opt_upid]);
event["ph"] = "n";
event["cat"] = "disabled_by_default-cpu_profiler";
event["name"] = "StackCpuSampling";
event["s"] = "t";
// Add a dummy thread timestamp to this event to match the format of instant
// events. Useful in the UI to view args of a selected group of samples.
event["tts"] = Json::Int64(1);
// "n"-phase events are nestable async events which get tied together with
// their id, so we need to give each one a unique ID as we only
// want the samples to show up on their own track in the trace-viewer but
// not nested together.
static size_t g_id_counter = 0;
event["id"] = PrintUint64(++g_id_counter);
std::vector<std::string> callstack;
const auto& callsites = storage->stack_profile_callsite_table();
int64_t maybe_callsite_id = samples.callsite_id()[i];
PERFETTO_DCHECK(maybe_callsite_id >= 0 &&
maybe_callsite_id < callsites.row_count());
while (maybe_callsite_id >= 0) {
uint32_t callsite_id = static_cast<uint32_t>(maybe_callsite_id);
const auto& frames = storage->stack_profile_frame_table();
PERFETTO_DCHECK(callsites.frame_id()[callsite_id] >= 0 &&
callsites.frame_id()[callsite_id] < frames.row_count());
uint32_t frame_id =
static_cast<uint32_t>(callsites.frame_id()[callsite_id]);
const auto& mappings = storage->stack_profile_mapping_table();
PERFETTO_DCHECK(frames.mapping()[frame_id] >= 0 &&
frames.mapping()[frame_id] < mappings.row_count());
uint32_t mapping_id = static_cast<uint32_t>(frames.mapping()[frame_id]);
NullTermStringView symbol_name;
auto opt_symbol_set_id = frames.symbol_set_id()[frame_id];
if (opt_symbol_set_id) {
symbol_name = storage->GetString(
storage->symbol_table().name()[*opt_symbol_set_id]);
}
char frame_entry[1024];
snprintf(
frame_entry, sizeof(frame_entry), "%s - %s [%s]\n",
(symbol_name.empty()
? PrintUint64(static_cast<uint64_t>(frames.rel_pc()[frame_id]))
.c_str()
: symbol_name.c_str()),
GetNonNullString(storage, mappings.name()[mapping_id]),
GetNonNullString(storage, mappings.build_id()[mapping_id]));
callstack.emplace_back(frame_entry);
maybe_callsite_id = callsites.parent_id()[callsite_id];
}
std::string merged_callstack;
for (auto entry = callstack.rbegin(); entry != callstack.rend(); ++entry) {
merged_callstack += *entry;
}
event["args"]["frames"] = merged_callstack;
// TODO(oysteine): Used for backwards compatibility with the memlog
// pipeline, should remove once we've switched to looking directly at the
// tid.
event["args"]["thread_id"] = thread_table.tid()[utid];
writer->WriteCommonEvent(event);
}
return util::OkStatus();
}
util::Status ExportMetadata(const TraceStorage* storage,
TraceFormatWriter* writer) {
const auto& trace_metadata = storage->metadata_table();
const auto& keys = trace_metadata.name();
const auto& int_values = trace_metadata.int_value();
const auto& str_values = trace_metadata.str_value();
// Create a mapping from key string ids to keys.
std::unordered_map<StringId, metadata::KeyIDs> key_map;
for (uint32_t i = 0; i < metadata::kNumKeys; ++i) {
auto id = *storage->string_pool().GetId(metadata::kNames[i]);
key_map[id] = static_cast<metadata::KeyIDs>(i);
}
for (uint32_t pos = 0; pos < trace_metadata.row_count(); pos++) {
// Cast away from enum type, as otherwise -Wswitch-enum will demand an
// exhaustive list of cases, even if there's a default case.
metadata::KeyIDs key = key_map[keys[pos]];
switch (static_cast<size_t>(key)) {
case metadata::benchmark_description:
writer->AppendTelemetryMetadataString(
"benchmarkDescriptions",
GetNonNullString(storage, str_values[pos]));
break;
case metadata::benchmark_name:
writer->AppendTelemetryMetadataString(
"benchmarks", GetNonNullString(storage, str_values[pos]));
break;
case metadata::benchmark_start_time_us:
writer->SetTelemetryMetadataTimestamp("benchmarkStart",
*int_values[pos]);
break;
case metadata::benchmark_had_failures:
writer->AppendTelemetryMetadataBool("hadFailures", *int_values[pos]);
break;
case metadata::benchmark_label:
writer->AppendTelemetryMetadataString(
"labels", GetNonNullString(storage, str_values[pos]));
break;
case metadata::benchmark_story_name:
writer->AppendTelemetryMetadataString(
"stories", GetNonNullString(storage, str_values[pos]));
break;
case metadata::benchmark_story_run_index:
writer->AppendTelemetryMetadataInt("storysetRepeats", *int_values[pos]);
break;
case metadata::benchmark_story_run_time_us:
writer->SetTelemetryMetadataTimestamp("traceStart", *int_values[pos]);
break;
case metadata::benchmark_story_tags: // repeated
writer->AppendTelemetryMetadataString(
"storyTags", GetNonNullString(storage, str_values[pos]));
break;
default:
PERFETTO_DLOG("Ignoring metadata key %zu", static_cast<size_t>(key));
break;
}
}
return util::OkStatus();
}
util::Status ExportStats(const TraceStorage* storage,
TraceFormatWriter* writer) {
const auto& stats = storage->stats();
for (size_t idx = 0; idx < stats::kNumKeys; idx++) {
if (stats::kTypes[idx] == stats::kSingle) {
writer->SetStats(stats::kNames[idx], stats[idx].value);
} else {
PERFETTO_DCHECK(stats::kTypes[idx] == stats::kIndexed);
writer->SetStats(stats::kNames[idx], stats[idx].indexed_values);
}
}
return util::OkStatus();
}
} // namespace
OutputWriter::OutputWriter() = default;
OutputWriter::~OutputWriter() = default;
util::Status ExportJson(const TraceStorage* storage,
OutputWriter* output,
ArgumentFilterPredicate argument_filter,
MetadataFilterPredicate metadata_filter,
LabelFilterPredicate label_filter) {
// TODO(eseckler): Implement argument/metadata/label filtering.
TraceFormatWriter writer(output, argument_filter, metadata_filter,
label_filter);
ArgsBuilder args_builder(storage);
util::Status status = ExportThreadNames(storage, &writer);
if (!status.ok())
return status;
status = ExportProcessNames(storage, &writer);
if (!status.ok())
return status;
status = ExportSlices(storage, args_builder, &writer);
if (!status.ok())
return status;
status = ExportRawEvents(storage, args_builder, &writer);
if (!status.ok())
return status;
status = ExportCpuProfileSamples(storage, &writer);
if (!status.ok())
return status;
status = ExportMetadata(storage, &writer);
if (!status.ok())
return status;
status = ExportStats(storage, &writer);
if (!status.ok())
return status;
return util::OkStatus();
}
util::Status ExportJson(TraceProcessorStorage* tp,
OutputWriter* output,
ArgumentFilterPredicate argument_filter,
MetadataFilterPredicate metadata_filter,
LabelFilterPredicate label_filter) {
const TraceStorage* storage = reinterpret_cast<TraceProcessorStorageImpl*>(tp)
->context()
->storage.get();
return ExportJson(storage, output, argument_filter, metadata_filter,
label_filter);
}
util::Status ExportJson(const TraceStorage* storage, FILE* output) {
FileWriter writer(output);
return ExportJson(storage, &writer, nullptr, nullptr, nullptr);
}
} // namespace json
} // namespace trace_processor
} // namespace perfetto
#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)