Path: blob/main/contrib/llvm-project/llvm/tools/llvm-xray/xray-converter.cpp
35230 views
//===- xray-converter.cpp: XRay Trace Conversion --------------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// Implements the trace conversion functions.9//10//===----------------------------------------------------------------------===//11#include "xray-converter.h"1213#include "trie-node.h"14#include "xray-registry.h"15#include "llvm/DebugInfo/Symbolize/Symbolize.h"16#include "llvm/Support/EndianStream.h"17#include "llvm/Support/FileSystem.h"18#include "llvm/Support/FormatVariadic.h"19#include "llvm/Support/ScopedPrinter.h"20#include "llvm/Support/YAMLTraits.h"21#include "llvm/Support/raw_ostream.h"22#include "llvm/XRay/InstrumentationMap.h"23#include "llvm/XRay/Trace.h"24#include "llvm/XRay/YAMLXRayRecord.h"2526using namespace llvm;27using namespace xray;2829// llvm-xray convert30// ----------------------------------------------------------------------------31static cl::SubCommand Convert("convert", "Trace Format Conversion");32static cl::opt<std::string> ConvertInput(cl::Positional,33cl::desc("<xray log file>"),34cl::Required, cl::sub(Convert));35enum class ConvertFormats { BINARY, YAML, CHROME_TRACE_EVENT };36static cl::opt<ConvertFormats> ConvertOutputFormat(37"output-format", cl::desc("output format"),38cl::values(clEnumValN(ConvertFormats::BINARY, "raw", "output in binary"),39clEnumValN(ConvertFormats::YAML, "yaml", "output in yaml"),40clEnumValN(ConvertFormats::CHROME_TRACE_EVENT, "trace_event",41"Output in chrome's trace event format. "42"May be visualized with the Catapult trace viewer.")),43cl::sub(Convert));44static cl::alias ConvertOutputFormat2("f", cl::aliasopt(ConvertOutputFormat),45cl::desc("Alias for -output-format"));46static cl::opt<std::string>47ConvertOutput("output", cl::value_desc("output file"), cl::init("-"),48cl::desc("output file; use '-' for stdout"),49cl::sub(Convert));50static cl::alias ConvertOutput2("o", cl::aliasopt(ConvertOutput),51cl::desc("Alias for -output"));5253static cl::opt<bool>54ConvertSymbolize("symbolize",55cl::desc("symbolize function ids from the input log"),56cl::init(false), cl::sub(Convert));57static cl::alias ConvertSymbolize2("y", cl::aliasopt(ConvertSymbolize),58cl::desc("Alias for -symbolize"));59static cl::opt<bool>60NoDemangle("no-demangle",61cl::desc("determines whether to demangle function name "62"when symbolizing function ids from the input log"),63cl::init(false), cl::sub(Convert));6465static cl::opt<bool> Demangle("demangle",66cl::desc("demangle symbols (default)"),67cl::sub(Convert));6869static cl::opt<std::string>70ConvertInstrMap("instr_map",71cl::desc("binary with the instrumentation map, or "72"a separate instrumentation map"),73cl::value_desc("binary with xray_instr_map"),74cl::sub(Convert), cl::init(""));75static cl::alias ConvertInstrMap2("m", cl::aliasopt(ConvertInstrMap),76cl::desc("Alias for -instr_map"));77static cl::opt<bool> ConvertSortInput(78"sort",79cl::desc("determines whether to sort input log records by timestamp"),80cl::sub(Convert), cl::init(true));81static cl::alias ConvertSortInput2("s", cl::aliasopt(ConvertSortInput),82cl::desc("Alias for -sort"));8384using llvm::yaml::Output;8586void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) {87YAMLXRayTrace Trace;88const auto &FH = Records.getFileHeader();89Trace.Header = {FH.Version, FH.Type, FH.ConstantTSC, FH.NonstopTSC,90FH.CycleFrequency};91Trace.Records.reserve(Records.size());92for (const auto &R : Records) {93Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId,94Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId)95: llvm::to_string(R.FuncId),96R.TSC, R.TId, R.PId, R.CallArgs, R.Data});97}98Output Out(OS, nullptr, 0);99Out.setWriteDefaultValues(false);100Out << Trace;101}102103void TraceConverter::exportAsRAWv1(const Trace &Records, raw_ostream &OS) {104// First write out the file header, in the correct endian-appropriate format105// (XRay assumes currently little endian).106support::endian::Writer Writer(OS, llvm::endianness::little);107const auto &FH = Records.getFileHeader();108Writer.write(FH.Version);109Writer.write(FH.Type);110uint32_t Bitfield{0};111if (FH.ConstantTSC)112Bitfield |= 1uL;113if (FH.NonstopTSC)114Bitfield |= 1uL << 1;115Writer.write(Bitfield);116Writer.write(FH.CycleFrequency);117118// There's 16 bytes of padding at the end of the file header.119static constexpr uint32_t Padding4B = 0;120Writer.write(Padding4B);121Writer.write(Padding4B);122Writer.write(Padding4B);123Writer.write(Padding4B);124125// Then write out the rest of the records, still in an endian-appropriate126// format.127for (const auto &R : Records) {128switch (R.Type) {129case RecordTypes::ENTER:130case RecordTypes::ENTER_ARG:131Writer.write(R.RecordType);132Writer.write(static_cast<uint8_t>(R.CPU));133Writer.write(uint8_t{0});134break;135case RecordTypes::EXIT:136Writer.write(R.RecordType);137Writer.write(static_cast<uint8_t>(R.CPU));138Writer.write(uint8_t{1});139break;140case RecordTypes::TAIL_EXIT:141Writer.write(R.RecordType);142Writer.write(static_cast<uint8_t>(R.CPU));143Writer.write(uint8_t{2});144break;145case RecordTypes::CUSTOM_EVENT:146case RecordTypes::TYPED_EVENT:147// Skip custom and typed event records for v1 logs.148continue;149}150Writer.write(R.FuncId);151Writer.write(R.TSC);152Writer.write(R.TId);153154if (FH.Version >= 3)155Writer.write(R.PId);156else157Writer.write(Padding4B);158159Writer.write(Padding4B);160Writer.write(Padding4B);161}162}163164namespace {165166// A structure that allows building a dictionary of stack ids for the Chrome167// trace event format.168struct StackIdData {169// Each Stack of function calls has a unique ID.170unsigned id;171172// Bookkeeping so that IDs can be maintained uniquely across threads.173// Traversal keeps sibling pointers to other threads stacks. This is helpful174// to determine when a thread encounters a new stack and should assign a new175// unique ID.176SmallVector<TrieNode<StackIdData> *, 4> siblings;177};178179using StackTrieNode = TrieNode<StackIdData>;180181// A helper function to find the sibling nodes for an encountered function in a182// thread of execution. Relies on the invariant that each time a new node is183// traversed in a thread, sibling bidirectional pointers are maintained.184SmallVector<StackTrieNode *, 4>185findSiblings(StackTrieNode *parent, int32_t FnId, uint32_t TId,186const DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>>187&StackRootsByThreadId) {188189SmallVector<StackTrieNode *, 4> Siblings{};190191if (parent == nullptr) {192for (const auto &map_iter : StackRootsByThreadId) {193// Only look for siblings in other threads.194if (map_iter.first != TId)195for (auto node_iter : map_iter.second) {196if (node_iter->FuncId == FnId)197Siblings.push_back(node_iter);198}199}200return Siblings;201}202203for (auto *ParentSibling : parent->ExtraData.siblings)204for (auto node_iter : ParentSibling->Callees)205if (node_iter->FuncId == FnId)206Siblings.push_back(node_iter);207208return Siblings;209}210211// Given a function being invoked in a thread with id TId, finds and returns the212// StackTrie representing the function call stack. If no node exists, creates213// the node. Assigns unique IDs to stacks newly encountered among all threads214// and keeps sibling links up to when creating new nodes.215StackTrieNode *findOrCreateStackNode(216StackTrieNode *Parent, int32_t FuncId, uint32_t TId,217DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>> &StackRootsByThreadId,218DenseMap<unsigned, StackTrieNode *> &StacksByStackId, unsigned *id_counter,219std::forward_list<StackTrieNode> &NodeStore) {220SmallVector<StackTrieNode *, 4> &ParentCallees =221Parent == nullptr ? StackRootsByThreadId[TId] : Parent->Callees;222auto match = find_if(ParentCallees, [FuncId](StackTrieNode *ParentCallee) {223return FuncId == ParentCallee->FuncId;224});225if (match != ParentCallees.end())226return *match;227228SmallVector<StackTrieNode *, 4> siblings =229findSiblings(Parent, FuncId, TId, StackRootsByThreadId);230if (siblings.empty()) {231NodeStore.push_front({FuncId, Parent, {}, {(*id_counter)++, {}}});232StackTrieNode *CurrentStack = &NodeStore.front();233StacksByStackId[*id_counter - 1] = CurrentStack;234ParentCallees.push_back(CurrentStack);235return CurrentStack;236}237unsigned stack_id = siblings[0]->ExtraData.id;238NodeStore.push_front({FuncId, Parent, {}, {stack_id, std::move(siblings)}});239StackTrieNode *CurrentStack = &NodeStore.front();240for (auto *sibling : CurrentStack->ExtraData.siblings)241sibling->ExtraData.siblings.push_back(CurrentStack);242ParentCallees.push_back(CurrentStack);243return CurrentStack;244}245246void writeTraceViewerRecord(uint16_t Version, raw_ostream &OS, int32_t FuncId,247uint32_t TId, uint32_t PId, bool Symbolize,248const FuncIdConversionHelper &FuncIdHelper,249double EventTimestampUs,250const StackTrieNode &StackCursor,251StringRef FunctionPhenotype) {252OS << " ";253if (Version >= 3) {254OS << llvm::formatv(255R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "{3}", )"256R"("ts" : "{4:f4}", "sf" : "{5}" })",257(Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)258: llvm::to_string(FuncId)),259FunctionPhenotype, TId, PId, EventTimestampUs,260StackCursor.ExtraData.id);261} else {262OS << llvm::formatv(263R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"264R"("ts" : "{3:f3}", "sf" : "{4}" })",265(Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)266: llvm::to_string(FuncId)),267FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);268}269}270271} // namespace272273void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,274raw_ostream &OS) {275const auto &FH = Records.getFileHeader();276auto Version = FH.Version;277auto CycleFreq = FH.CycleFrequency;278279unsigned id_counter = 0;280int NumOutputRecords = 0;281282OS << "{\n \"traceEvents\": [\n";283DenseMap<uint32_t, StackTrieNode *> StackCursorByThreadId{};284DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>> StackRootsByThreadId{};285DenseMap<unsigned, StackTrieNode *> StacksByStackId{};286std::forward_list<StackTrieNode> NodeStore{};287for (const auto &R : Records) {288// Chrome trace event format always wants data in micros.289// CyclesPerMicro = CycleHertz / 10^6290// TSC / CyclesPerMicro == TSC * 10^6 / CycleHertz == MicroTimestamp291// Could lose some precision here by converting the TSC to a double to292// multiply by the period in micros. 52 bit mantissa is a good start though.293// TODO: Make feature request to Chrome Trace viewer to accept ticks and a294// frequency or do some more involved calculation to avoid dangers of295// conversion.296double EventTimestampUs = double(1000000) / CycleFreq * double(R.TSC);297StackTrieNode *&StackCursor = StackCursorByThreadId[R.TId];298switch (R.Type) {299case RecordTypes::CUSTOM_EVENT:300case RecordTypes::TYPED_EVENT:301// TODO: Support typed and custom event rendering on Chrome Trace Viewer.302break;303case RecordTypes::ENTER:304case RecordTypes::ENTER_ARG:305StackCursor = findOrCreateStackNode(StackCursor, R.FuncId, R.TId,306StackRootsByThreadId, StacksByStackId,307&id_counter, NodeStore);308// Each record is represented as a json dictionary with function name,309// type of B for begin or E for end, thread id, process id,310// timestamp in microseconds, and a stack frame id. The ids are logged311// in an id dictionary after the events.312if (NumOutputRecords++ > 0) {313OS << ",\n";314}315writeTraceViewerRecord(Version, OS, R.FuncId, R.TId, R.PId, Symbolize,316FuncIdHelper, EventTimestampUs, *StackCursor, "B");317break;318case RecordTypes::EXIT:319case RecordTypes::TAIL_EXIT:320// No entries to record end for.321if (StackCursor == nullptr)322break;323// Should we emit an END record anyway or account this condition?324// (And/Or in loop termination below)325StackTrieNode *PreviousCursor = nullptr;326do {327if (NumOutputRecords++ > 0) {328OS << ",\n";329}330writeTraceViewerRecord(Version, OS, StackCursor->FuncId, R.TId, R.PId,331Symbolize, FuncIdHelper, EventTimestampUs,332*StackCursor, "E");333PreviousCursor = StackCursor;334StackCursor = StackCursor->Parent;335} while (PreviousCursor->FuncId != R.FuncId && StackCursor != nullptr);336break;337}338}339OS << "\n ],\n"; // Close the Trace Events array.340OS << " "341<< "\"displayTimeUnit\": \"ns\",\n";342343// The stackFrames dictionary substantially reduces size of the output file by344// avoiding repeating the entire call stack of function names for each entry.345OS << R"( "stackFrames": {)";346int stack_frame_count = 0;347for (auto map_iter : StacksByStackId) {348if (stack_frame_count++ == 0)349OS << "\n";350else351OS << ",\n";352OS << " ";353OS << llvm::formatv(354R"("{0}" : { "name" : "{1}")", map_iter.first,355(Symbolize ? FuncIdHelper.SymbolOrNumber(map_iter.second->FuncId)356: llvm::to_string(map_iter.second->FuncId)));357if (map_iter.second->Parent != nullptr)358OS << llvm::formatv(R"(, "parent": "{0}")",359map_iter.second->Parent->ExtraData.id);360OS << " }";361}362OS << "\n }\n"; // Close the stack frames map.363OS << "}\n"; // Close the JSON entry.364}365366namespace llvm {367namespace xray {368369static CommandRegistration Unused(&Convert, []() -> Error {370// FIXME: Support conversion to BINARY when upgrading XRay trace versions.371InstrumentationMap Map;372if (!ConvertInstrMap.empty()) {373auto InstrumentationMapOrError = loadInstrumentationMap(ConvertInstrMap);374if (!InstrumentationMapOrError)375return joinErrors(make_error<StringError>(376Twine("Cannot open instrumentation map '") +377ConvertInstrMap + "'",378std::make_error_code(std::errc::invalid_argument)),379InstrumentationMapOrError.takeError());380Map = std::move(*InstrumentationMapOrError);381}382383const auto &FunctionAddresses = Map.getFunctionAddresses();384symbolize::LLVMSymbolizer::Options SymbolizerOpts;385if (Demangle.getPosition() < NoDemangle.getPosition())386SymbolizerOpts.Demangle = false;387symbolize::LLVMSymbolizer Symbolizer(SymbolizerOpts);388llvm::xray::FuncIdConversionHelper FuncIdHelper(ConvertInstrMap, Symbolizer,389FunctionAddresses);390llvm::xray::TraceConverter TC(FuncIdHelper, ConvertSymbolize);391std::error_code EC;392raw_fd_ostream OS(ConvertOutput, EC,393ConvertOutputFormat == ConvertFormats::BINARY394? sys::fs::OpenFlags::OF_None395: sys::fs::OpenFlags::OF_TextWithCRLF);396if (EC)397return make_error<StringError>(398Twine("Cannot open file '") + ConvertOutput + "' for writing.", EC);399400auto TraceOrErr = loadTraceFile(ConvertInput, ConvertSortInput);401if (!TraceOrErr)402return joinErrors(403make_error<StringError>(404Twine("Failed loading input file '") + ConvertInput + "'.",405std::make_error_code(std::errc::executable_format_error)),406TraceOrErr.takeError());407408auto &T = *TraceOrErr;409switch (ConvertOutputFormat) {410case ConvertFormats::YAML:411TC.exportAsYAML(T, OS);412break;413case ConvertFormats::BINARY:414TC.exportAsRAWv1(T, OS);415break;416case ConvertFormats::CHROME_TRACE_EVENT:417TC.exportAsChromeTraceEventFormat(T, OS);418break;419}420return Error::success();421});422423} // namespace xray424} // namespace llvm425426427