blob: f0a0479b2f6eac67b2dffed9b046c616c331a662 [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.
*/
#include "perfetto/ext/trace_processor/export_json.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <cstring>
#include <deque>
#include <limits>
#include <map>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <tuple>
#include <unordered_map>
#include <utility>
#include <vector>
#include "perfetto/base/build_config.h"
#include "perfetto/base/logging.h"
#include "perfetto/base/status.h"
#include "perfetto/ext/base/string_splitter.h"
#include "perfetto/ext/base/string_utils.h"
#include "perfetto/public/compiler.h"
#include "perfetto/trace_processor/basic_types.h"
#include "src/trace_processor/containers/null_term_string_view.h"
#include "src/trace_processor/export_json.h"
#include "src/trace_processor/storage/metadata.h"
#include "src/trace_processor/storage/stats.h"
#include "src/trace_processor/storage/trace_storage.h"
#include "src/trace_processor/tables/metadata_tables_py.h"
#include "src/trace_processor/tables/profiler_tables_py.h"
#include "src/trace_processor/trace_processor_storage_impl.h"
#include "src/trace_processor/types/trace_processor_context.h"
#include "src/trace_processor/types/variadic.h"
#include "src/trace_processor/util/status_macros.h"
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
#include <json/config.h>
#include <json/reader.h>
#include <json/value.h>
#include <json/writer.h>
#endif
namespace perfetto::trace_processor::json {
namespace {
class FileWriter : public OutputWriter {
public:
explicit FileWriter(FILE* file) : file_(file) {}
~FileWriter() override { fflush(file_); }
base::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 base::ErrStatus("Error writing to file: %d", ferror(file_));
return base::OkStatus();
}
private:
FILE* file_;
};
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
using IndexMap = perfetto::trace_processor::TraceStorage::Stats::IndexMap;
const char kLegacyEventArgsKey[] = "legacy_event";
const char kLegacyEventPassthroughUtidKey[] = "passthrough_utid";
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 kStrippedArgument[] = "__stripped__";
const char* GetNonNullString(const TraceStorage* storage,
std::optional<StringId> id) {
return id == std::nullopt || *id == kNullStringId
? ""
: storage->GetString(*id).c_str();
}
class JsonExporter {
public:
JsonExporter(const TraceStorage* storage,
OutputWriter* output,
ArgumentFilterPredicate argument_filter,
MetadataFilterPredicate metadata_filter,
LabelFilterPredicate label_filter)
: storage_(storage),
args_builder_(storage_),
writer_(output,
std::move(argument_filter),
std::move(metadata_filter),
std::move(label_filter)) {}
base::Status Export() {
RETURN_IF_ERROR(MapUniquePidsAndTids());
RETURN_IF_ERROR(ExportThreadNames());
RETURN_IF_ERROR(ExportProcessNames());
RETURN_IF_ERROR(ExportProcessUptimes());
RETURN_IF_ERROR(ExportSlices());
RETURN_IF_ERROR(ExportFlows());
RETURN_IF_ERROR(ExportRawEvents());
RETURN_IF_ERROR(ExportCpuProfileSamples());
RETURN_IF_ERROR(ExportMetadata());
RETURN_IF_ERROR(ExportStats());
RETURN_IF_ERROR(ExportMemorySnapshots());
return base::OkStatus();
}
private:
class TraceFormatWriter {
public:
TraceFormatWriter(OutputWriter* output,
ArgumentFilterPredicate argument_filter,
MetadataFilterPredicate metadata_filter,
LabelFilterPredicate label_filter)
: output_(output),
argument_filter_(std::move(argument_filter)),
metadata_filter_(std::move(metadata_filter)),
label_filter_(std::move(label_filter)),
first_event_(true) {
Json::StreamWriterBuilder b;
b.settings_["indentation"] = "";
writer_.reset(b.newStreamWriter());
WriteHeader();
}
~TraceFormatWriter() { WriteFooter(); }
void WriteCommonEvent(const Json::Value& event) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
DoWriteEvent(event);
}
void AddAsyncBeginEvent(const Json::Value& event) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
async_begin_events_.push_back(event);
}
void AddAsyncInstantEvent(const Json::Value& event) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
async_instant_events_.push_back(event);
}
void AddAsyncEndEvent(const Json::Value& event) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
async_end_events_.push_back(event);
}
void SortAndEmitAsyncEvents() {
// Catapult doesn't handle out-of-order begin/end events well, especially
// when their timestamps are the same, but their order is incorrect. Since
// we process events sorted by begin timestamp, |async_begin_events_| and
// |async_instant_events_| are already sorted. We now only have to sort
// |async_end_events_| and merge-sort all events into a single sequence.
// Sort |async_end_events_|. Note that we should order by ascending
// timestamp, but in reverse-stable order. This way, a child slices's end
// is emitted before its parent's end event, even if both end events have
// the same timestamp. To accomplish this, we perform a stable sort in
// descending order and later iterate via reverse iterators.
struct {
bool operator()(const Json::Value& a, const Json::Value& b) const {
return a["ts"].asInt64() > b["ts"].asInt64();
}
} CompareEvents;
std::stable_sort(async_end_events_.begin(), async_end_events_.end(),
CompareEvents);
// Merge sort by timestamp. If events share the same timestamp, prefer
// instant events, then end events, so that old slices close before new
// ones are opened, but instant events remain in their deepest nesting
// level.
auto instant_event_it = async_instant_events_.begin();
auto end_event_it = async_end_events_.rbegin();
auto begin_event_it = async_begin_events_.begin();
auto has_instant_event = instant_event_it != async_instant_events_.end();
auto has_end_event = end_event_it != async_end_events_.rend();
auto has_begin_event = begin_event_it != async_begin_events_.end();
auto emit_next_instant = [&instant_event_it, &has_instant_event, this]() {
DoWriteEvent(*instant_event_it);
instant_event_it++;
has_instant_event = instant_event_it != async_instant_events_.end();
};
auto emit_next_end = [&end_event_it, &has_end_event, this]() {
DoWriteEvent(*end_event_it);
end_event_it++;
has_end_event = end_event_it != async_end_events_.rend();
};
auto emit_next_begin = [&begin_event_it, &has_begin_event, this]() {
DoWriteEvent(*begin_event_it);
begin_event_it++;
has_begin_event = begin_event_it != async_begin_events_.end();
};
auto emit_next_instant_or_end = [&instant_event_it, &end_event_it,
&emit_next_instant, &emit_next_end]() {
if ((*instant_event_it)["ts"].asInt64() <=
(*end_event_it)["ts"].asInt64()) {
emit_next_instant();
} else {
emit_next_end();
}
};
auto emit_next_instant_or_begin = [&instant_event_it, &begin_event_it,
&emit_next_instant,
&emit_next_begin]() {
if ((*instant_event_it)["ts"].asInt64() <=
(*begin_event_it)["ts"].asInt64()) {
emit_next_instant();
} else {
emit_next_begin();
}
};
auto emit_next_end_or_begin = [&end_event_it, &begin_event_it,
&emit_next_end, &emit_next_begin]() {
if ((*end_event_it)["ts"].asInt64() <=
(*begin_event_it)["ts"].asInt64()) {
emit_next_end();
} else {
emit_next_begin();
}
};
// While we still have events in all iterators, consider each.
while (has_instant_event && has_end_event && has_begin_event) {
if ((*instant_event_it)["ts"].asInt64() <=
(*end_event_it)["ts"].asInt64()) {
emit_next_instant_or_begin();
} else {
emit_next_end_or_begin();
}
}
// Only instant and end events left.
while (has_instant_event && has_end_event) {
emit_next_instant_or_end();
}
// Only instant and begin events left.
while (has_instant_event && has_begin_event) {
emit_next_instant_or_begin();
}
// Only end and begin events left.
while (has_end_event && has_begin_event) {
emit_next_end_or_begin();
}
// Remaining instant events.
while (has_instant_event) {
emit_next_instant();
}
// Remaining end events.
while (has_end_event) {
emit_next_end();
}
// Remaining begin events.
while (has_begin_event) {
emit_next_begin();
}
}
void WriteMetadataEvent(const char* metadata_type,
const char* metadata_arg_name,
const char* metadata_arg_value,
uint32_t pid,
uint32_t tid) {
if (label_filter_ && !label_filter_("traceEvents"))
return;
std::ostringstream ss;
if (!first_event_)
ss << ",\n";
Json::Value value;
value["ph"] = "M";
value["cat"] = "__metadata";
value["ts"] = 0;
value["name"] = metadata_type;
value["pid"] = Json::Int(pid);
value["tid"] = Json::Int(tid);
Json::Value args;
args[metadata_arg_name] = metadata_arg_value;
value["args"] = args;
writer_->write(value, &ss);
output_->AppendString(ss.str());
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] = static_cast<double>(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() {
SortAndEmitAsyncEvents();
// Filter metadata entries.
if (metadata_filter_) {
for (const auto& member : metadata_.getMemberNames()) {
if (!metadata_filter_(member.c_str()))
metadata_[member] = kStrippedArgument;
}
}
if ((!label_filter_ || label_filter_("traceEvents")) &&
!user_trace_data_.empty()) {
user_trace_data_ += "]";
Json::CharReaderBuilder builder;
auto reader =
std::unique_ptr<Json::CharReader>(builder.newCharReader());
Json::Value result;
if (reader->parse(user_trace_data_.data(),
user_trace_data_.data() + user_trace_data_.length(),
&result, nullptr)) {
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());
}
}
std::ostringstream ss;
if (!label_filter_)
ss << "]";
if ((!label_filter_ || label_filter_("systemTraceEvents")) &&
!system_trace_data_.empty()) {
ss << ",\"systemTraceEvents\":\n";
writer_->write(Json::Value(system_trace_data_), &ss);
}
if ((!label_filter_ || label_filter_("metadata")) && !metadata_.empty()) {
ss << ",\"metadata\":\n";
writer_->write(metadata_, &ss);
}
if (!label_filter_)
ss << "}";
output_->AppendString(ss.str());
}
void DoWriteEvent(const Json::Value& event) {
std::ostringstream ss;
if (!first_event_)
ss << ",\n";
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;
}
}
writer_->write(event_copy, &ss);
} else {
writer_->write(event, &ss);
}
first_event_ = false;
output_->AppendString(ss.str());
}
OutputWriter* output_;
ArgumentFilterPredicate argument_filter_;
MetadataFilterPredicate metadata_filter_;
LabelFilterPredicate label_filter_;
std::unique_ptr<Json::StreamWriter> writer_;
bool first_event_;
Json::Value metadata_;
std::string system_trace_data_;
std::string user_trace_data_;
std::vector<Json::Value> async_begin_events_;
std::vector<Json::Value> async_instant_events_;
std::vector<Json::Value> async_end_events_;
};
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[count - 1].arg_set_id() + 1, empty_value_);
for (auto it = arg_table.IterateRows(); it; ++it) {
ArgSetId set_id = it.arg_set_id();
const char* key = storage->GetString(it.key()).c_str();
Variadic value = storage_->GetArgValue(it.row_number().row_number());
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 base::Uint64ToHexString(variadic.pointer_value);
case Variadic::kBool:
return variadic.bool_value;
case Variadic::kNull:
return base::Uint64ToHexString(0);
case Variadic::kJson:
Json::CharReaderBuilder b;
auto reader = std::unique_ptr<Json::CharReader>(b.newCharReader());
Json::Value result;
std::string v = GetNonNullString(storage_, variadic.json_value);
reader->parse(v.data(), v.data() + v.length(), &result, nullptr);
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 == std::string::npos) { // A single item
target = &(*target)[key_part];
} else { // A list item
target = &(*target)[key_part.substr(0, bracketpos)];
while (bracketpos != std::string::npos) {
// We constructed this string from an int earlier in trace_processor
// so it shouldn't be possible for this (or the StringToUInt32
// below) to fail.
std::string s =
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;
}
std::optional<uint32_t> index = base::StringToUInt32(s);
if (PERFETTO_UNLIKELY(!index)) {
PERFETTO_ELOG("Expected to be able to extract index from %s",
key_part.c_str());
return;
}
target = &(*target)[index.value()];
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");
}
if (args.isMember("source")) {
Json::Value source = args["source"];
if (source.isObject() && source.isMember("function_name")) {
args["function_name"] = source["function_name"];
args["file_name"] = source["file_name"];
args.removeMember("source");
}
}
}
}
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_;
};
base::Status MapUniquePidsAndTids() {
const auto& process_table = storage_->process_table();
for (auto it = process_table.IterateRows(); it; ++it) {
UniquePid upid = it.id().value;
uint32_t exported_pid = it.pid();
auto it_and_inserted =
exported_pids_to_upids_.emplace(exported_pid, upid);
if (!it_and_inserted.second) {
exported_pid = NextExportedPidOrTidForDuplicates();
it_and_inserted = exported_pids_to_upids_.emplace(exported_pid, upid);
}
upids_to_exported_pids_.emplace(upid, exported_pid);
}
const auto& thread_table = storage_->thread_table();
for (auto it = thread_table.IterateRows(); it; ++it) {
UniqueTid utid = it.id().value;
uint32_t exported_pid = 0;
std::optional<UniquePid> upid = it.upid();
if (upid) {
auto exported_pid_it = upids_to_exported_pids_.find(*upid);
PERFETTO_DCHECK(exported_pid_it != upids_to_exported_pids_.end());
exported_pid = exported_pid_it->second;
}
uint32_t exported_tid = it.tid();
auto it_and_inserted = exported_pids_and_tids_to_utids_.emplace(
std::make_pair(exported_pid, exported_tid), utid);
if (!it_and_inserted.second) {
exported_tid = NextExportedPidOrTidForDuplicates();
it_and_inserted = exported_pids_and_tids_to_utids_.emplace(
std::make_pair(exported_pid, exported_tid), utid);
}
utids_to_exported_pids_and_tids_.emplace(
utid, std::make_pair(exported_pid, exported_tid));
}
return base::OkStatus();
}
base::Status ExportThreadNames() {
const auto& thread_table = storage_->thread_table();
for (auto it = thread_table.IterateRows(); it; ++it) {
auto opt_name = it.name();
if (opt_name.has_value()) {
UniqueTid utid = it.id().value;
const char* thread_name = GetNonNullString(storage_, opt_name);
auto pid_and_tid = UtidToPidAndTid(utid);
writer_.WriteMetadataEvent("thread_name", "name", thread_name,
pid_and_tid.first, pid_and_tid.second);
}
}
return base::OkStatus();
}
base::Status ExportProcessNames() {
const auto& process_table = storage_->process_table();
for (auto it = process_table.IterateRows(); it; ++it) {
auto opt_name = it.name();
if (opt_name.has_value()) {
UniquePid upid = it.id().value;
const char* process_name = GetNonNullString(storage_, opt_name);
writer_.WriteMetadataEvent("process_name", "name", process_name,
UpidToPid(upid), /*tid=*/0);
}
}
return base::OkStatus();
}
// For each process it writes an approximate uptime, based on the process'
// start time and the last slice in the entire trace. This same last slice is
// used with all processes, so the process could have ended earlier.
base::Status ExportProcessUptimes() {
int64_t last_timestamp_ns = FindLastSliceTimestamp();
if (last_timestamp_ns <= 0)
return base::OkStatus();
const auto& process_table = storage_->process_table();
for (auto it = process_table.IterateRows(); it; ++it) {
std::optional<int64_t> start_timestamp_ns = it.start_ts();
if (!start_timestamp_ns.has_value()) {
continue;
}
UniquePid upid = it.id().value;
int64_t process_uptime_seconds =
(last_timestamp_ns - start_timestamp_ns.value()) /
(1000l * 1000 * 1000);
writer_.WriteMetadataEvent("process_uptime_seconds", "uptime",
std::to_string(process_uptime_seconds).c_str(),
UpidToPid(upid), /*tid=*/0);
}
return base::OkStatus();
}
// Returns the last slice's end timestamp for the entire trace. If no slices
// are found 0 is returned.
int64_t FindLastSliceTimestamp() {
int64_t last_ts = 0;
for (auto it = storage_->slice_table().IterateRows(); it; ++it) {
last_ts = std::max(last_ts, it.ts() + it.dur());
}
return last_ts;
}
base::Status ExportSlices() {
const auto& slices = storage_->slice_table();
for (auto it = slices.IterateRows(); it; ++it) {
// Skip slices with empty category - these are ftrace/system slices that
// were also imported into the raw table and will be exported from there
// by trace_to_text.
// TODO(b/153609716): Add a src column or do_not_export flag instead.
if (!it.category())
continue;
auto cat = storage_->GetString(*it.category());
if (cat.c_str() == nullptr || cat == "binder")
continue;
Json::Value event;
event["ts"] = Json::Int64(it.ts() / 1000);
event["cat"] = GetNonNullString(storage_, it.category());
event["name"] = GetNonNullString(storage_, it.name());
event["pid"] = 0;
event["tid"] = 0;
std::optional<UniqueTid> legacy_utid;
std::string legacy_phase;
event["args"] = args_builder_.GetArgs(it.arg_set_id()); // Makes a copy.
if (event["args"].isMember(kLegacyEventArgsKey)) {
const auto& legacy_args = event["args"][kLegacyEventArgsKey];
if (legacy_args.isMember(kLegacyEventPassthroughUtidKey)) {
legacy_utid = legacy_args[kLegacyEventPassthroughUtidKey].asUInt();
}
if (legacy_args.isMember(kLegacyEventPhaseKey)) {
legacy_phase = legacy_args[kLegacyEventPhaseKey].asString();
}
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.
TrackId track_id = it.track_id();
const auto& track_table = storage_->track_table();
auto track_row_ref = *track_table.FindById(track_id);
auto track_args_id = track_row_ref.source_arg_set_id();
const Json::Value* track_args = nullptr;
bool legacy_chrome_track = false;
bool is_child_track = false;
if (track_args_id) {
track_args = &args_builder_.GetArgs(*track_args_id);
legacy_chrome_track = (*track_args)["source"].asString() == "chrome";
is_child_track = track_args->isMember("is_root_in_scope") &&
!(*track_args)["is_root_in_scope"].asBool();
}
const auto& thread_track = storage_->thread_track_table();
const auto& process_track = storage_->process_track_table();
const auto& virtual_track_slices = storage_->virtual_track_slices();
int64_t duration_ns = it.dur();
std::optional<int64_t> thread_ts_ns;
std::optional<int64_t> thread_duration_ns;
std::optional<int64_t> thread_instruction_count;
std::optional<int64_t> thread_instruction_delta;
if (it.thread_dur()) {
thread_ts_ns = it.thread_ts();
thread_duration_ns = it.thread_dur();
thread_instruction_count = it.thread_instruction_count();
thread_instruction_delta = it.thread_instruction_delta();
} else {
SliceId id = it.id();
std::optional<uint32_t> vtrack_slice_row =
virtual_track_slices.FindRowForSliceId(id);
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 tt_rr = thread_track.FindById(track_id);
if (tt_rr && !is_child_track) {
// Synchronous (thread) slice or instant event.
UniqueTid utid = tt_rr->utid();
auto pid_and_tid = UtidToPidAndTid(utid);
event["pid"] = Json::Int(pid_and_tid.first);
event["tid"] = Json::Int(pid_and_tid.second);
if (duration_ns == 0) {
if (legacy_phase.empty()) {
// Use "I" instead of "i" phase for backwards-compat with old
// consumers.
event["ph"] = "I";
} else {
event["ph"] = legacy_phase;
}
if (thread_ts_ns && thread_ts_ns > 0) {
event["tts"] = Json::Int64(*thread_ts_ns / 1000);
}
if (thread_instruction_count && *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 && *thread_ts_ns > 0) {
event["tts"] = Json::Int64(*thread_ts_ns / 1000);
// Only write thread duration for completed events.
if (duration_ns > 0 && thread_duration_ns)
event["tdur"] = Json::Int64(*thread_duration_ns / 1000);
}
if (thread_instruction_count && *thread_instruction_count > 0) {
event["ticount"] = Json::Int64(*thread_instruction_count);
// Only write thread instruction delta for completed events.
if (duration_ns > 0 && thread_instruction_delta)
event["tidelta"] = Json::Int64(*thread_instruction_delta);
}
}
writer_.WriteCommonEvent(event);
} else if (is_child_track ||
(legacy_chrome_track && track_args->isMember("trace_id"))) {
// Async event slice.
auto pt_rr = process_track.FindById(track_id);
if (legacy_chrome_track) {
// Legacy async tracks are always process-associated and have args.
PERFETTO_DCHECK(pt_rr);
PERFETTO_DCHECK(track_args);
UniquePid upid = pt_rr->upid();
uint32_t exported_pid = UpidToPid(upid);
event["pid"] = Json::Int(exported_pid);
event["tid"] =
Json::Int(legacy_utid ? UtidToPidAndTid(*legacy_utid).second
: exported_pid);
// 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("trace_id"));
PERFETTO_DCHECK(track_args->isMember("trace_id_is_process_scoped"));
PERFETTO_DCHECK(track_args->isMember("source_scope"));
uint64_t trace_id =
static_cast<uint64_t>((*track_args)["trace_id"].asInt64());
std::string source_scope = (*track_args)["source_scope"].asString();
if (!source_scope.empty())
event["scope"] = source_scope;
bool trace_id_is_process_scoped =
(*track_args)["trace_id_is_process_scoped"].asBool();
if (trace_id_is_process_scoped) {
event["id2"]["local"] = base::Uint64ToHexString(trace_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"] = base::Uint64ToHexString(trace_id);
}
} else {
if (tt_rr) {
UniqueTid utid = tt_rr->utid();
auto pid_and_tid = UtidToPidAndTid(utid);
event["pid"] = Json::Int(pid_and_tid.first);
event["tid"] = Json::Int(pid_and_tid.second);
event["id2"]["local"] = base::Uint64ToHexString(track_id.value);
} else if (pt_rr) {
uint32_t upid = pt_rr->upid();
uint32_t exported_pid = UpidToPid(upid);
event["pid"] = Json::Int(exported_pid);
event["tid"] =
Json::Int(legacy_utid ? UtidToPidAndTid(*legacy_utid).second
: exported_pid);
event["id2"]["local"] = base::Uint64ToHexString(track_id.value);
} else {
if (legacy_utid) {
auto pid_and_tid = UtidToPidAndTid(*legacy_utid);
event["pid"] = Json::Int(pid_and_tid.first);
event["tid"] = Json::Int(pid_and_tid.second);
}
// 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"] = base::Uint64ToHexString(track_id.value);
}
}
if (thread_ts_ns && *thread_ts_ns > 0) {
event["tts"] = Json::Int64(*thread_ts_ns / 1000);
event["use_async_tts"] = Json::Int(1);
}
if (thread_instruction_count && *thread_instruction_count > 0) {
event["ticount"] = Json::Int64(*thread_instruction_count);
event["use_async_tts"] = Json::Int(1);
}
if (duration_ns == 0) {
if (legacy_phase.empty()) {
// Instant async event.
event["ph"] = "n";
writer_.AddAsyncInstantEvent(event);
} else {
// Async step events.
event["ph"] = legacy_phase;
writer_.AddAsyncBeginEvent(event);
}
} else { // Async start and end.
event["ph"] = legacy_phase.empty() ? "b" : legacy_phase;
writer_.AddAsyncBeginEvent(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"] = legacy_phase.empty() ? "e" : "F";
event["ts"] = Json::Int64((it.ts() + duration_ns) / 1000);
if (thread_ts_ns && thread_duration_ns && *thread_ts_ns > 0) {
event["tts"] =
Json::Int64((*thread_ts_ns + *thread_duration_ns) / 1000);
}
if (thread_instruction_count && thread_instruction_delta &&
*thread_instruction_count > 0) {
event["ticount"] = Json::Int64(
(*thread_instruction_count + *thread_instruction_delta));
}
event["args"].clear();
writer_.AddAsyncEndEvent(event);
}
}
} else {
// Global or process-scoped instant event.
PERFETTO_DCHECK(legacy_chrome_track || !is_child_track);
if (duration_ns != 0) {
// We don't support exporting slices on the default global or process
// track to JSON (JSON only supports instant events on these tracks).
PERFETTO_DLOG(
"skipping non-instant slice on global or process track");
} else {
if (legacy_phase.empty()) {
// Use "I" instead of "i" phase for backwards-compat with old
// consumers.
event["ph"] = "I";
} else {
event["ph"] = legacy_phase;
}
auto pt_rr = process_track.FindById(track_id);
if (pt_rr.has_value()) {
UniquePid upid = pt_rr->upid();
uint32_t exported_pid = UpidToPid(upid);
event["pid"] = Json::Int(exported_pid);
event["tid"] =
Json::Int(legacy_utid ? UtidToPidAndTid(*legacy_utid).second
: exported_pid);
event["s"] = "p";
} else {
event["s"] = "g";
}
writer_.WriteCommonEvent(event);
}
}
}
return base::OkStatus();
}
std::optional<Json::Value> CreateFlowEventV1(uint32_t flow_id,
SliceId slice_id,
const std::string& name,
const std::string& cat,
Json::Value args,
bool flow_begin) {
const auto& slices = storage_->slice_table();
const auto& thread_tracks = storage_->thread_track_table();
auto opt_slice_rr = slices.FindById(slice_id);
if (!opt_slice_rr)
return std::nullopt;
auto slice_rr = opt_slice_rr.value();
TrackId track_id = slice_rr.track_id();
auto opt_ttrr = thread_tracks.FindById(track_id);
// catapult only supports flow events attached to thread-track slices
if (!opt_ttrr)
return std::nullopt;
auto pid_and_tid = UtidToPidAndTid(opt_ttrr->utid());
Json::Value event;
event["id"] = flow_id;
event["pid"] = Json::Int(pid_and_tid.first);
event["tid"] = Json::Int(pid_and_tid.second);
event["cat"] = cat;
event["name"] = name;
event["ph"] = (flow_begin ? "s" : "f");
event["ts"] = Json::Int64(slice_rr.ts() / 1000);
if (!flow_begin) {
event["bp"] = "e";
}
event["args"] = std::move(args);
return std::move(event);
}
base::Status ExportFlows() {
const auto& flow_table = storage_->flow_table();
const auto& slice_table = storage_->slice_table();
for (auto it = flow_table.IterateRows(); it; ++it) {
SliceId slice_out = it.slice_out();
SliceId slice_in = it.slice_in();
uint32_t arg_set_id = it.arg_set_id();
std::string cat;
std::string name;
auto args = args_builder_.GetArgs(arg_set_id);
if (arg_set_id != kInvalidArgSetId) {
cat = args["cat"].asString();
name = args["name"].asString();
// Don't export these args since they are only used for this export and
// weren't part of the original event.
args.removeMember("name");
args.removeMember("cat");
} else {
auto rr = slice_table.FindById(slice_out);
PERFETTO_DCHECK(rr.has_value());
cat = GetNonNullString(storage_, rr->category());
name = GetNonNullString(storage_, rr->name());
}
uint32_t i = it.row_number().row_number();
auto out_event = CreateFlowEventV1(i, slice_out, name, cat, args,
/* flow_begin = */ true);
auto in_event = CreateFlowEventV1(i, slice_in, name, cat, std::move(args),
/* flow_begin = */ false);
if (out_event && in_event) {
writer_.WriteCommonEvent(out_event.value());
writer_.WriteCommonEvent(in_event.value());
}
}
return base::OkStatus();
}
Json::Value ConvertLegacyRawEventToJson(
const tables::RawTable::ConstIterator& it) {
Json::Value event;
event["ts"] = Json::Int64(it.ts() / 1000);
UniqueTid utid = static_cast<UniqueTid>(it.utid());
auto pid_and_tid = UtidToPidAndTid(utid);
event["pid"] = Json::Int(pid_and_tid.first);
event["tid"] = Json::Int(pid_and_tid.second);
// 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(it.arg_set_id());
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"] = base::Uint64ToHexString(
legacy_args[kLegacyEventUnscopedIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventGlobalIdKey)) {
event["id2"]["global"] = base::Uint64ToHexString(
legacy_args[kLegacyEventGlobalIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventLocalIdKey)) {
event["id2"]["local"] = base::Uint64ToHexString(
legacy_args[kLegacyEventLocalIdKey].asUInt64());
}
if (legacy_args.isMember(kLegacyEventIdScopeKey))
event["scope"] = legacy_args[kLegacyEventIdScopeKey];
event["args"].removeMember(kLegacyEventArgsKey);
return event;
}
base::Status ExportRawEvents() {
std::optional<StringId> raw_legacy_event_key_id =
storage_->string_pool().GetId("track_event.legacy_event");
std::optional<StringId> raw_legacy_system_trace_event_id =
storage_->string_pool().GetId("chrome_event.legacy_system_trace");
std::optional<StringId> raw_legacy_user_trace_event_id =
storage_->string_pool().GetId("chrome_event.legacy_user_trace");
std::optional<StringId> raw_chrome_metadata_event_id =
storage_->string_pool().GetId("chrome_event.metadata");
const auto& events = storage_->raw_table();
for (auto it = events.IterateRows(); it; ++it) {
if (raw_legacy_event_key_id && it.name() == *raw_legacy_event_key_id) {
Json::Value event = ConvertLegacyRawEventToJson(it);
writer_.WriteCommonEvent(event);
} else if (raw_legacy_system_trace_event_id &&
it.name() == *raw_legacy_system_trace_event_id) {
Json::Value args = args_builder_.GetArgs(it.arg_set_id());
PERFETTO_DCHECK(args.isMember("data"));
writer_.AddSystemTraceData(args["data"].asString());
} else if (raw_legacy_user_trace_event_id &&
it.name() == *raw_legacy_user_trace_event_id) {
Json::Value args = args_builder_.GetArgs(it.arg_set_id());
PERFETTO_DCHECK(args.isMember("data"));
writer_.AddUserTraceData(args["data"].asString());
} else if (raw_chrome_metadata_event_id &&
it.name() == *raw_chrome_metadata_event_id) {
Json::Value args = args_builder_.GetArgs(it.arg_set_id());
writer_.MergeMetadata(args);
}
}
return base::OkStatus();
}
class MergedProfileSamplesEmitter {
public:
// The TraceFormatWriter must outlive this instance.
explicit MergedProfileSamplesEmitter(TraceFormatWriter& writer)
: writer_(writer) {}
MergedProfileSamplesEmitter(const MergedProfileSamplesEmitter&) = delete;
MergedProfileSamplesEmitter& operator=(const MergedProfileSamplesEmitter&) =
delete;
MergedProfileSamplesEmitter& operator=(
MergedProfileSamplesEmitter&& value) = delete;
uint64_t AddEventForUtid(UniqueTid utid,
int64_t ts,
CallsiteId callsite_id,
const Json::Value& event) {
auto current_sample = current_events_.find(utid);
// If there's a current entry for our thread and it matches the callsite
// of the new sample, update the entry with the new timestamp. Otherwise
// create a new entry.
if (current_sample != current_events_.end() &&
current_sample->second.callsite_id() == callsite_id) {
current_sample->second.UpdateWithNewSample(ts);
return current_sample->second.event_id();
}
if (current_sample != current_events_.end()) {
current_events_.erase(current_sample);
}
auto new_entry = current_events_.emplace(
std::piecewise_construct, std::forward_as_tuple(utid),
std::forward_as_tuple(writer_, callsite_id, ts, event));
return new_entry.first->second.event_id();
}
static uint64_t GenerateNewEventId() {
// "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 (unless they're nested under a merged event).
static size_t g_id_counter = 0;
return ++g_id_counter;
}
private:
class Sample {
public:
Sample(TraceFormatWriter& writer,
CallsiteId callsite_id,
int64_t ts,
Json::Value event)
: writer_(writer),
callsite_id_(callsite_id),
begin_ts_(ts),
end_ts_(ts),
event_(std::move(event)),
event_id_(MergedProfileSamplesEmitter::GenerateNewEventId()),
sample_count_(1) {}
Sample(const Sample&) = delete;
Sample& operator=(const Sample&) = delete;
Sample(Sample&&) = delete;
Sample& operator=(Sample&& value) = delete;
~Sample() {
// No point writing a merged event if we only got a single sample
// as ExportCpuProfileSamples will already be writing the instant event.
if (sample_count_ == 1)
return;
event_["id"] = base::Uint64ToHexString(event_id_);
// Write the BEGIN event.
event_["ph"] = "b";
// We subtract 1us as a workaround for the first async event not
// nesting underneath the parent event if the timestamp is identical.
int64_t begin_in_us_ = begin_ts_ / 1000;
event_["ts"] = Json::Int64(std::min(begin_in_us_ - 1, begin_in_us_));
writer_.WriteCommonEvent(event_);
// Write the END event.
event_["ph"] = "e";
event_["ts"] = Json::Int64(end_ts_ / 1000);
// No need for args for the end event; remove them to save some space.
event_["args"].clear();
writer_.WriteCommonEvent(event_);
}
void UpdateWithNewSample(int64_t ts) {
// We assume samples for a given thread will appear in timestamp
// order; if this assumption stops holding true, we'll have to sort the
// samples first.
if (ts < end_ts_ || begin_ts_ > ts) {
PERFETTO_ELOG(
"Got an timestamp out of sequence while merging stack samples "
"during JSON export!\n");
PERFETTO_DCHECK(false);
}
end_ts_ = ts;
sample_count_++;
}
uint64_t event_id() const { return event_id_; }
CallsiteId callsite_id() const { return callsite_id_; }
TraceFormatWriter& writer_;
CallsiteId callsite_id_;
int64_t begin_ts_;
int64_t end_ts_;
Json::Value event_;
uint64_t event_id_;
size_t sample_count_;
};
std::unordered_map<UniqueTid, Sample> current_events_;
TraceFormatWriter& writer_;
};
base::Status ExportCpuProfileSamples() {
MergedProfileSamplesEmitter merged_sample_emitter(writer_);
const tables::CpuProfileStackSampleTable& samples =
storage_->cpu_profile_stack_sample_table();
for (auto it = samples.IterateRows(); it; ++it) {
Json::Value event;
event["ts"] = Json::Int64(it.ts() / 1000);
UniqueTid utid = static_cast<UniqueTid>(it.utid());
auto pid_and_tid = UtidToPidAndTid(utid);
event["pid"] = Json::Int(pid_and_tid.first);
event["tid"] = Json::Int(pid_and_tid.second);
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);
const auto& callsites = storage_->stack_profile_callsite_table();
const auto& frames = storage_->stack_profile_frame_table();
const auto& mappings = storage_->stack_profile_mapping_table();
std::vector<std::string> callstack;
std::optional<CallsiteId> opt_callsite_id = it.callsite_id();
while (opt_callsite_id) {
CallsiteId callsite_id = *opt_callsite_id;
auto callsite_row = *callsites.FindById(callsite_id);
FrameId frame_id = callsite_row.frame_id();
auto frame_row = *frames.FindById(frame_id);
MappingId mapping_id = frame_row.mapping();
auto mapping_row = *mappings.FindById(mapping_id);
NullTermStringView symbol_name;
auto opt_symbol_set_id = frame_row.symbol_set_id();
if (opt_symbol_set_id) {
symbol_name = storage_->GetString(
storage_->symbol_table()[*opt_symbol_set_id].name());
}
base::StackString<1024> frame_entry(
"%s - %s [%s]\n",
(symbol_name.empty()
? base::Uint64ToHexString(
static_cast<uint64_t>(frame_row.rel_pc()))
.c_str()
: symbol_name.c_str()),
GetNonNullString(storage_, mapping_row.name()),
GetNonNullString(storage_, mapping_row.build_id()));
callstack.emplace_back(frame_entry.ToStdString());
opt_callsite_id = callsite_row.parent_id();
}
std::string merged_callstack;
for (auto entry = callstack.rbegin(); entry != callstack.rend();
++entry) {
merged_callstack += *entry;
}
event["args"]["frames"] = merged_callstack;
event["args"]["process_priority"] = it.process_priority();
// 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"] = Json::Int(pid_and_tid.second);
// Emit duration events for adjacent samples with the same callsite.
// For now, only do this when the trace has already been symbolized i.e.
// are not directly output by Chrome, to avoid interfering with other
// processing pipelines.
std::optional<CallsiteId> opt_current_callsite_id = it.callsite_id();
if (opt_current_callsite_id && storage_->symbol_table().row_count() > 0) {
uint64_t parent_event_id = merged_sample_emitter.AddEventForUtid(
utid, it.ts(), *opt_current_callsite_id, event);
event["id"] = base::Uint64ToHexString(parent_event_id);
} else {
event["id"] = base::Uint64ToHexString(
MergedProfileSamplesEmitter::GenerateNewEventId());
}
writer_.WriteCommonEvent(event);
}
return base::OkStatus();
}
base::Status ExportMetadata() {
const auto& trace_metadata = storage_->metadata_table();
// Create a mapping from key string ids to keys.
std::unordered_map<StringId, metadata::KeyId> 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::KeyId>(i);
}
for (auto it = trace_metadata.IterateRows(); it; ++it) {
auto key_it = key_map.find(it.name());
// Skip exporting dynamic entries; the cr-xxx entries that come from
// the ChromeMetadata proto message are already exported from the raw
// table.
if (key_it == key_map.end())
continue;
// Cast away from enum type, as otherwise -Wswitch-enum will demand an
// exhaustive list of cases, even if there's a default case.
metadata::KeyId key = key_it->second;
switch (static_cast<size_t>(key)) {
case metadata::benchmark_description:
writer_.AppendTelemetryMetadataString(
"benchmarkDescriptions",
storage_->string_pool().Get(*it.str_value()).c_str());
break;
case metadata::benchmark_name:
writer_.AppendTelemetryMetadataString(
"benchmarks",
storage_->string_pool().Get(*it.str_value()).c_str());
break;
case metadata::benchmark_start_time_us:
writer_.SetTelemetryMetadataTimestamp("benchmarkStart",
*it.int_value());
break;
case metadata::benchmark_had_failures:
writer_.AppendTelemetryMetadataBool("hadFailures", *it.int_value());
break;
case metadata::benchmark_label:
writer_.AppendTelemetryMetadataString(
"labels", storage_->string_pool().Get(*it.str_value()).c_str());
break;
case metadata::benchmark_story_name:
writer_.AppendTelemetryMetadataString(
"stories", storage_->string_pool().Get(*it.str_value()).c_str());
break;
case metadata::benchmark_story_run_index:
writer_.AppendTelemetryMetadataInt("storysetRepeats",
*it.int_value());
break;
case metadata::benchmark_story_run_time_us:
writer_.SetTelemetryMetadataTimestamp("traceStart", *it.int_value());
break;
case metadata::benchmark_story_tags: // repeated
writer_.AppendTelemetryMetadataString(
"storyTags",
storage_->string_pool().Get(*it.str_value()).c_str());
break;
default:
PERFETTO_DLOG("Ignoring metadata key %zu", static_cast<size_t>(key));
break;
}
}
return base::OkStatus();
}
base::Status ExportStats() {
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 base::OkStatus();
}
base::Status ExportMemorySnapshots() {
const auto& memory_snapshots = storage_->memory_snapshot_table();
std::optional<StringId> private_footprint_id =
storage_->string_pool().GetId("chrome.private_footprint_kb");
std::optional<StringId> peak_resident_set_id =
storage_->string_pool().GetId("chrome.peak_resident_set_kb");
for (auto sit = memory_snapshots.IterateRows(); sit; ++sit) {
Json::Value event_base;
event_base["ph"] = "v";
event_base["cat"] = "disabled-by-default-memory-infra";
auto snapshot_id = sit.id();
event_base["id"] = base::Uint64ToHexString(snapshot_id.value);
int64_t snapshot_ts = sit.timestamp();
event_base["ts"] = Json::Int64(snapshot_ts / 1000);
// TODO(crbug:1116359): Add dump type to the snapshot proto
// to properly fill event_base["name"]
event_base["name"] = "periodic_interval";
event_base["args"]["dumps"]["level_of_detail"] =
GetNonNullString(storage_, sit.detail_level());
// Export OS dump events for processes with relevant data.
const auto& process_table = storage_->process_table();
for (auto pit = process_table.IterateRows(); pit; ++pit) {
Json::Value event = FillInProcessEventDetails(event_base, pit.pid());
Json::Value& totals = event["args"]["dumps"]["process_totals"];
const auto& process_counters = storage_->process_counter_track_table();
for (auto it = process_counters.IterateRows(); it; ++it) {
if (it.upid() != pit.id().value)
continue;
TrackId track_id = it.id();
if (private_footprint_id && (it.name() == private_footprint_id)) {
totals["private_footprint_bytes"] = base::Uint64ToHexStringNoPrefix(
GetCounterValue(track_id, snapshot_ts));
} else if (peak_resident_set_id &&
(it.name() == peak_resident_set_id)) {
totals["peak_resident_set_size"] = base::Uint64ToHexStringNoPrefix(
GetCounterValue(track_id, snapshot_ts));
}
}
auto process_args_id = pit.arg_set_id();
if (process_args_id) {
const Json::Value* process_args =
&args_builder_.GetArgs(process_args_id);
if (process_args->isMember("is_peak_rss_resettable")) {
totals["is_peak_rss_resettable"] =
(*process_args)["is_peak_rss_resettable"];
}
}
const auto& smaps_table = storage_->profiler_smaps_table();
// Do not create vm_regions without memory maps, since catapult expects
// to have rows.
Json::Value* smaps =
smaps_table.row_count() > 0
? &event["args"]["dumps"]["process_mmaps"]["vm_regions"]
: nullptr;
for (auto it = smaps_table.IterateRows(); it; ++it) {
if (it.upid() != pit.id().value)
continue;
if (it.ts() != snapshot_ts)
continue;
Json::Value region;
region["mf"] = GetNonNullString(storage_, it.file_name());
region["pf"] = Json::Int64(it.protection_flags());
region["sa"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.start_address()));
region["sz"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.size_kb()) * 1024);
region["ts"] = Json::Int64(it.module_timestamp());
region["id"] = GetNonNullString(storage_, it.module_debugid());
region["df"] = GetNonNullString(storage_, it.module_debug_path());
region["bs"]["pc"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.private_clean_resident_kb()) * 1024);
region["bs"]["pd"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.private_dirty_kb()) * 1024);
region["bs"]["pss"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.proportional_resident_kb()) * 1024);
region["bs"]["sc"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.shared_clean_resident_kb()) * 1024);
region["bs"]["sd"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.shared_dirty_resident_kb()) * 1024);
region["bs"]["sw"] = base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.swap_kb()) * 1024);
smaps->append(region);
}
if (!totals.empty() || (smaps && !smaps->empty()))
writer_.WriteCommonEvent(event);
}
// Export chrome dump events for process snapshots in current memory
// snapshot.
const auto& process_snapshots = storage_->process_memory_snapshot_table();
for (auto psit = process_snapshots.IterateRows(); psit; ++psit) {
if (psit.snapshot_id() != snapshot_id)
continue;
auto process_snapshot_id = psit.id();
uint32_t pid = UpidToPid(psit.upid());
// Shared memory nodes are imported into a fake process with pid 0.
// Catapult expects them to be associated with one of the real processes
// of the snapshot, so we choose the first one we can find and replace
// the pid.
if (pid == 0) {
for (auto iit = process_snapshots.IterateRows(); iit; ++iit) {
if (iit.snapshot_id() != snapshot_id)
continue;
uint32_t new_pid = UpidToPid(iit.upid());
if (new_pid != 0) {
pid = new_pid;
break;
}
}
}
Json::Value event = FillInProcessEventDetails(event_base, pid);
const auto& sn = storage_->memory_snapshot_node_table();
for (auto it = sn.IterateRows(); it; ++it) {
if (it.process_snapshot_id() != process_snapshot_id) {
continue;
}
const char* path = GetNonNullString(storage_, it.path());
event["args"]["dumps"]["allocators"][path]["guid"] =
base::Uint64ToHexStringNoPrefix(
static_cast<uint64_t>(it.id().value));
if (it.size()) {
AddAttributeToMemoryNode(&event, path, "size", it.size(), "bytes");
}
if (it.effective_size()) {
AddAttributeToMemoryNode(&event, path, "effective_size",
it.effective_size(), "bytes");
}
auto node_args_id = it.arg_set_id();
if (!node_args_id)
continue;
const Json::Value* node_args =
&args_builder_.GetArgs(node_args_id.value());
for (const auto& arg_name : node_args->getMemberNames()) {
const Json::Value& arg_value = (*node_args)[arg_name]["value"];
if (arg_value.empty())
continue;
if (arg_value.isString()) {
AddAttributeToMemoryNode(&event, path, arg_name,
arg_value.asString());
} else if (arg_value.isInt64()) {
Json::Value unit = (*node_args)[arg_name]["unit"];
if (unit.empty())
unit = "unknown";
AddAttributeToMemoryNode(&event, path, arg_name,
arg_value.asInt64(), unit.asString());
}
}
}
const auto& snapshot_edges = storage_->memory_snapshot_edge_table();
for (auto it = snapshot_edges.IterateRows(); it; ++it) {
SnapshotNodeId source_node_id = it.source_node_id();
auto source_node_rr = *sn.FindById(source_node_id);
if (source_node_rr.process_snapshot_id() != process_snapshot_id) {
continue;
}
Json::Value edge;
edge["source"] =
base::Uint64ToHexStringNoPrefix(it.source_node_id().value);
edge["target"] =
base::Uint64ToHexStringNoPrefix(it.target_node_id().value);
edge["importance"] = Json::Int(it.importance());
edge["type"] = "ownership";
event["args"]["dumps"]["allocators_graph"].append(edge);
}
writer_.WriteCommonEvent(event);
}
}
return base::OkStatus();
}
uint32_t UpidToPid(UniquePid upid) {
auto pid_it = upids_to_exported_pids_.find(upid);
PERFETTO_DCHECK(pid_it != upids_to_exported_pids_.end());
return pid_it->second;
}
std::pair<uint32_t, uint32_t> UtidToPidAndTid(UniqueTid utid) {
auto pid_and_tid_it = utids_to_exported_pids_and_tids_.find(utid);
PERFETTO_DCHECK(pid_and_tid_it != utids_to_exported_pids_and_tids_.end());
return pid_and_tid_it->second;
}
uint32_t NextExportedPidOrTidForDuplicates() {
// Ensure that the exported substitute value does not represent a valid
// pid/tid. This would be very unlikely in practice.
while (IsValidPidOrTid(next_exported_pid_or_tid_for_duplicates_))
next_exported_pid_or_tid_for_duplicates_--;
return next_exported_pid_or_tid_for_duplicates_--;
}
bool IsValidPidOrTid(uint32_t pid_or_tid) {
const auto& process_table = storage_->process_table();
for (auto it = process_table.IterateRows(); it; ++it) {
if (it.pid() == pid_or_tid)
return true;
}
const auto& thread_table = storage_->thread_table();
for (auto it = thread_table.IterateRows(); it; ++it) {
if (it.tid() == pid_or_tid)
return true;
}
return false;
}
static Json::Value FillInProcessEventDetails(const Json::Value& event,
uint32_t pid) {
Json::Value output = event;
output["pid"] = Json::Int(pid);
output["tid"] = Json::Int(-1);
return output;
}
static void AddAttributeToMemoryNode(Json::Value* event,
const std::string& path,
const std::string& key,
int64_t value,
const std::string& units) {
(*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["value"] =
base::Uint64ToHexStringNoPrefix(static_cast<uint64_t>(value));
(*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["type"] =
"scalar";
(*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["units"] =
units;
}
static void AddAttributeToMemoryNode(Json::Value* event,
const std::string& path,
const std::string& key,
const std::string& value,
const std::string& units = "") {
(*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["value"] =
value;
(*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["type"] =
"string";
(*event)["args"]["dumps"]["allocators"][path]["attrs"][key]["units"] =
units;
}
uint64_t GetCounterValue(TrackId track_id, int64_t ts) {
const auto& counter_table = storage_->counter_table();
auto begin = counter_table.ts().begin();
auto end = counter_table.ts().end();
PERFETTO_DCHECK(counter_table.ts().IsSorted() &&
counter_table.ts().IsColumnType<int64_t>());
// The timestamp column is sorted, so we can binary search for a matching
// timestamp. Note that we don't use RowMap operations like FilterInto()
// here because they bloat trace processor's binary size in Chrome too much.
auto it = std::lower_bound(begin, end, ts,
[](const SqlValue& value, int64_t expected_ts) {
return value.AsLong() < expected_ts;
});
for (; it < end; ++it) {
if ((*it).AsLong() != ts)
break;
if (auto rr = counter_table[it.row()]; rr.track_id() == track_id) {
return static_cast<uint64_t>(rr.value());
}
}
return 0;
}
const TraceStorage* storage_;
ArgsBuilder args_builder_;
TraceFormatWriter writer_;
// If a pid/tid is duplicated between two or more different processes/threads
// (pid/tid reuse), we export the subsequent occurrences with different
// pids/tids that is visibly different from regular pids/tids - counting down
// from uint32_t max.
uint32_t next_exported_pid_or_tid_for_duplicates_ =
std::numeric_limits<uint32_t>::max();
std::map<UniquePid, uint32_t> upids_to_exported_pids_;
std::map<uint32_t, UniquePid> exported_pids_to_upids_;
std::map<UniqueTid, std::pair<uint32_t, uint32_t>>
utids_to_exported_pids_and_tids_;
std::map<std::pair<uint32_t, uint32_t>, UniqueTid>
exported_pids_and_tids_to_utids_;
};
#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
} // namespace
OutputWriter::OutputWriter() = default;
OutputWriter::~OutputWriter() = default;
base::Status ExportJson(const TraceStorage* storage,
OutputWriter* output,
ArgumentFilterPredicate argument_filter,
MetadataFilterPredicate metadata_filter,
LabelFilterPredicate label_filter) {
#if PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
JsonExporter exporter(storage, output, std::move(argument_filter),
std::move(metadata_filter), std::move(label_filter));
return exporter.Export();
#else
perfetto::base::ignore_result(storage);
perfetto::base::ignore_result(output);
perfetto::base::ignore_result(argument_filter);
perfetto::base::ignore_result(metadata_filter);
perfetto::base::ignore_result(label_filter);
return base::ErrStatus("JSON support is not compiled in this build");
#endif // PERFETTO_BUILDFLAG(PERFETTO_TP_JSON)
}
base::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, std::move(argument_filter),
std::move(metadata_filter), std::move(label_filter));
}
base::Status ExportJson(const TraceStorage* storage, FILE* output) {
FileWriter writer(output);
return ExportJson(storage, &writer, nullptr, nullptr, nullptr);
}
} // namespace perfetto::trace_processor::json