blob: 5a191a3520693849cc471f95d9b2358dfbfff8b2 [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 <inttypes.h>
#include <json/reader.h>
#include <json/value.h>
#include <json/writer.h>
#include <stdio.h>
#include "perfetto/ext/base/string_splitter.h"
#include "src/trace_processor/export_json.h"
#include "src/trace_processor/metadata.h"
#include "src/trace_processor/trace_storage.h"
namespace {
class TraceFormatWriter {
public:
TraceFormatWriter(FILE* output) : output_(output), first_event_(true) {
WriteHeader();
}
~TraceFormatWriter() { WriteFooter(); }
void WriteCommonEvent(const Json::Value& event) {
if (!first_event_) {
fputs(",", output_);
}
Json::FastWriter writer;
fputs(writer.write(event).c_str(), output_);
first_event_ = false;
}
void WriteMetadataEvent(const char* metadata_type,
const char* metadata_value,
uint32_t tid,
uint32_t pid) {
if (!first_event_) {
fputs(",", output_);
}
Json::FastWriter writer;
Json::Value value;
value["ph"] = "M";
value["cat"] = "__metadata";
value["ts"] = 0;
value["name"] = metadata_type;
value["tid"] = Json::UInt(tid);
value["pid"] = Json::UInt(pid);
Json::Value args;
args["name"] = metadata_value;
value["args"] = args;
fputs(writer.write(value).c_str(), output_);
first_event_ = false;
}
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;
}
private:
void WriteHeader() { fputs("{\"traceEvents\":[\n", output_); }
void WriteFooter() {
if (metadata_.empty()) {
fputs("]}", output_);
} else {
fputs("],\n\"metadata\":", output_);
Json::FastWriter writer;
fputs(writer.write(metadata_).c_str(), output_);
fputs("\n}", output_);
}
fflush(output_);
}
FILE* output_;
bool first_event_;
Json::Value metadata_;
};
std::string PrintUint64(uint64_t x) {
char hex_str[19];
sprintf(hex_str, "0x%" PRIx64, x);
return hex_str;
}
} // anonymous namespace
namespace perfetto {
namespace trace_processor {
namespace json {
namespace {
class ArgsBuilder {
public:
explicit ArgsBuilder(const TraceStorage* storage)
: string_pool_(&storage->string_pool()) {
const TraceStorage::Args& args = storage->args();
Json::Value empty_value(Json::objectValue);
if (args.args_count() == 0) {
args_sets_.resize(1, empty_value);
return;
}
args_sets_.resize(args.set_ids().back() + 1, empty_value);
for (size_t i = 0; i < args.args_count(); ++i) {
ArgSetId set_id = args.set_ids()[i];
const char* key = string_pool_->Get(args.keys()[i]).c_str();
Variadic value = args.arg_values()[i];
AppendArg(set_id, key, VariadicToJson(value));
}
PostprocessArgs();
}
const Json::Value& GetArgs(ArgSetId set_id) const {
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 string_pool_->Get(variadic.string_value).c_str();
case Variadic::kReal:
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(string_pool_->Get(variadic.string_value).c_str(), 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();) {
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);
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 StringPool* string_pool_;
std::vector<Json::Value> args_sets_;
};
ResultCode ExportThreadNames(const TraceStorage* storage,
TraceFormatWriter* writer) {
const StringPool& string_pool = storage->string_pool();
for (UniqueTid i = 1; i < storage->thread_count(); ++i) {
auto thread = storage->GetThread(i);
if (thread.name_id > 0) {
const char* thread_name = string_pool.Get(thread.name_id).c_str();
uint32_t pid = thread.upid ? storage->GetProcess(*thread.upid).pid : 0;
writer->WriteMetadataEvent("thread_name", thread_name, thread.tid, pid);
}
}
return kResultOk;
}
ResultCode ExportProcessNames(const TraceStorage* storage,
TraceFormatWriter* writer) {
const StringPool& string_pool = storage->string_pool();
for (UniquePid i = 1; i < storage->process_count(); ++i) {
auto process = storage->GetProcess(i);
if (process.name_id > 0) {
const char* process_name = string_pool.Get(process.name_id).c_str();
writer->WriteMetadataEvent("process_name", process_name, 0, process.pid);
}
}
return kResultOk;
}
ResultCode ExportSlices(const TraceStorage* storage,
TraceFormatWriter* writer) {
const StringPool& string_pool = storage->string_pool();
ArgsBuilder args_builder(storage);
const auto& slices = storage->nestable_slices();
for (uint32_t i = 0; i < slices.slice_count(); ++i) {
Json::Value event;
event["ts"] = Json::Int64(slices.start_ns()[i] / 1000);
event["cat"] = string_pool.Get(slices.categories()[i]).c_str();
event["name"] = string_pool.Get(slices.names()[i]).c_str();
event["pid"] = 0;
Json::Value args = args_builder.GetArgs(slices.arg_set_ids()[i]);
if (!args.empty()) {
event["args"] = args;
}
if (slices.types()[i] == RefType::kRefTrack) { // Async event.
uint32_t track_id = static_cast<uint32_t>(slices.refs()[i]);
VirtualTrackScope scope = storage->virtual_tracks().scopes()[track_id];
UniquePid upid = storage->virtual_tracks().upids()[track_id];
if (scope == VirtualTrackScope::kGlobal) {
event["id2"]["global"] = PrintUint64(track_id);
} else {
event["id2"]["local"] = PrintUint64(track_id);
event["pid"] = storage->GetProcess(upid).pid;
}
if (slices.durations()[i] == 0) { // Instant async event.
event["ph"] = "n";
writer->WriteCommonEvent(event);
} else { // Async start and end.
event["ph"] = "b";
writer->WriteCommonEvent(event);
event["ph"] = "e";
event["ts"] =
Json::Int64((slices.start_ns()[i] + slices.durations()[i]) / 1000);
event.removeMember("args");
writer->WriteCommonEvent(event);
}
} else { // Sync event.
const auto& thread_slices = storage->thread_slices();
int64_t thread_ts_us = 0;
int64_t thread_duration_us = 0;
base::Optional<uint32_t> thread_slice_row =
thread_slices.FindRowForSliceId(i);
if (thread_slice_row) {
thread_ts_us =
thread_slices.thread_timestamp_ns()[*thread_slice_row] / 1000;
thread_duration_us =
thread_slices.thread_duration_ns()[*thread_slice_row] / 1000;
}
if (slices.durations()[i] == 0) { // Instant event.
event["ph"] = "i";
if (slices.types()[i] == RefType::kRefUtid) {
UniqueTid utid = static_cast<UniqueTid>(slices.refs()[i]);
auto thread = storage->GetThread(utid);
if (thread.upid) {
event["pid"] = storage->GetProcess(*thread.upid).pid;
}
if (thread_ts_us > 0) {
event["tts"] = Json::Int64(thread_ts_us);
}
event["tid"] = thread.tid;
event["s"] = "t";
} else if (slices.types()[i] == RefType::kRefUpid) {
UniquePid upid = static_cast<UniquePid>(slices.refs()[i]);
event["pid"] = storage->GetProcess(upid).pid;
event["s"] = "p";
} else if (slices.types()[i] == RefType::kRefNoRef) {
event["s"] = "g";
} else {
return kResultWrongRefType;
}
writer->WriteCommonEvent(event);
} else { // Complete event.
if (slices.types()[i] != RefType::kRefUtid) {
return kResultWrongRefType;
}
event["ph"] = "X";
event["dur"] = Json::Int64(slices.durations()[i] / 1000);
UniqueTid utid = static_cast<UniqueTid>(slices.refs()[i]);
auto thread = storage->GetThread(utid);
event["tid"] = thread.tid;
if (thread.upid) {
event["pid"] = storage->GetProcess(*thread.upid).pid;
}
if (thread_ts_us > 0) {
event["tts"] = Json::Int64(thread_ts_us);
event["tdur"] = Json::Int64(thread_duration_us);
}
writer->WriteCommonEvent(event);
}
}
}
return kResultOk;
}
ResultCode ExportMetadata(const TraceStorage* storage,
TraceFormatWriter* writer) {
const StringPool& string_pool = storage->string_pool();
const auto& trace_metadata = storage->metadata();
const auto& keys = trace_metadata.keys();
const auto& values = trace_metadata.values();
for (size_t pos = 0; pos < keys.size(); pos++) {
// Cast away from enum type, as otherwise -Wswitch-enum will demand an
// exhaustive list of cases, even if there's a default case.
switch (static_cast<size_t>(keys[pos])) {
case metadata::benchmark_description:
writer->AppendTelemetryMetadataString(
"benchmarkDescriptions",
string_pool.Get(values[pos].string_value).c_str());
break;
case metadata::benchmark_name:
writer->AppendTelemetryMetadataString(
"benchmarks", string_pool.Get(values[pos].string_value).c_str());
break;
case metadata::benchmark_start_time_us:
writer->SetTelemetryMetadataTimestamp("benchmarkStart",
values[pos].int_value);
break;
case metadata::benchmark_had_failures:
if (pos < values.size())
writer->AppendTelemetryMetadataBool("hadFailures",
values[pos].int_value);
break;
case metadata::benchmark_label:
writer->AppendTelemetryMetadataString(
"labels", string_pool.Get(values[pos].string_value).c_str());
break;
case metadata::benchmark_story_name:
writer->AppendTelemetryMetadataString(
"stories", string_pool.Get(values[pos].string_value).c_str());
break;
case metadata::benchmark_story_run_index:
writer->AppendTelemetryMetadataInt("storysetRepeats",
values[pos].int_value);
break;
case metadata::benchmark_story_run_time_us:
writer->SetTelemetryMetadataTimestamp("traceStart",
values[pos].int_value);
break;
case metadata::benchmark_story_tags: // repeated
writer->AppendTelemetryMetadataString(
"storyTags", string_pool.Get(values[pos].string_value).c_str());
break;
default:
PERFETTO_DFATAL("unexpected metadata key");
break;
}
}
return kResultOk;
}
} // anonymous namespace
ResultCode ExportJson(const TraceStorage* storage, FILE* output) {
TraceFormatWriter writer(output);
ResultCode code = ExportThreadNames(storage, &writer);
if (code != kResultOk)
return code;
code = ExportProcessNames(storage, &writer);
if (code != kResultOk)
return code;
code = ExportSlices(storage, &writer);
if (code != kResultOk)
return code;
code = ExportMetadata(storage, &writer);
if (code != kResultOk)
return code;
return kResultOk;
}
} // namespace json
} // namespace trace_processor
} // namespace perfetto