123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425 |
- //===- xray-converter.cpp: XRay Trace Conversion --------------------------===//
- //
- // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
- // See https://llvm.org/LICENSE.txt for license information.
- // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
- //
- //===----------------------------------------------------------------------===//
- //
- // Implements the trace conversion functions.
- //
- //===----------------------------------------------------------------------===//
- #include "xray-converter.h"
- #include "trie-node.h"
- #include "xray-registry.h"
- #include "llvm/DebugInfo/Symbolize/Symbolize.h"
- #include "llvm/Support/EndianStream.h"
- #include "llvm/Support/FileSystem.h"
- #include "llvm/Support/FormatVariadic.h"
- #include "llvm/Support/ScopedPrinter.h"
- #include "llvm/Support/YAMLTraits.h"
- #include "llvm/Support/raw_ostream.h"
- #include "llvm/XRay/InstrumentationMap.h"
- #include "llvm/XRay/Trace.h"
- #include "llvm/XRay/YAMLXRayRecord.h"
- using namespace llvm;
- using namespace xray;
- // llvm-xray convert
- // ----------------------------------------------------------------------------
- static cl::SubCommand Convert("convert", "Trace Format Conversion");
- static cl::opt<std::string> ConvertInput(cl::Positional,
- cl::desc("<xray log file>"),
- cl::Required, cl::sub(Convert));
- enum class ConvertFormats { BINARY, YAML, CHROME_TRACE_EVENT };
- static cl::opt<ConvertFormats> ConvertOutputFormat(
- "output-format", cl::desc("output format"),
- cl::values(clEnumValN(ConvertFormats::BINARY, "raw", "output in binary"),
- clEnumValN(ConvertFormats::YAML, "yaml", "output in yaml"),
- clEnumValN(ConvertFormats::CHROME_TRACE_EVENT, "trace_event",
- "Output in chrome's trace event format. "
- "May be visualized with the Catapult trace viewer.")),
- cl::sub(Convert));
- static cl::alias ConvertOutputFormat2("f", cl::aliasopt(ConvertOutputFormat),
- cl::desc("Alias for -output-format"));
- static cl::opt<std::string>
- ConvertOutput("output", cl::value_desc("output file"), cl::init("-"),
- cl::desc("output file; use '-' for stdout"),
- cl::sub(Convert));
- static cl::alias ConvertOutput2("o", cl::aliasopt(ConvertOutput),
- cl::desc("Alias for -output"));
- static cl::opt<bool>
- ConvertSymbolize("symbolize",
- cl::desc("symbolize function ids from the input log"),
- cl::init(false), cl::sub(Convert));
- static cl::alias ConvertSymbolize2("y", cl::aliasopt(ConvertSymbolize),
- cl::desc("Alias for -symbolize"));
- static cl::opt<bool>
- NoDemangle("no-demangle",
- cl::desc("determines whether to demangle function name "
- "when symbolizing function ids from the input log"),
- cl::init(false), cl::sub(Convert));
- static cl::opt<bool> Demangle("demangle",
- cl::desc("demangle symbols (default)"),
- cl::sub(Convert));
- static cl::opt<std::string>
- ConvertInstrMap("instr_map",
- cl::desc("binary with the instrumentation map, or "
- "a separate instrumentation map"),
- cl::value_desc("binary with xray_instr_map"),
- cl::sub(Convert), cl::init(""));
- static cl::alias ConvertInstrMap2("m", cl::aliasopt(ConvertInstrMap),
- cl::desc("Alias for -instr_map"));
- static cl::opt<bool> ConvertSortInput(
- "sort",
- cl::desc("determines whether to sort input log records by timestamp"),
- cl::sub(Convert), cl::init(true));
- static cl::alias ConvertSortInput2("s", cl::aliasopt(ConvertSortInput),
- cl::desc("Alias for -sort"));
- using llvm::yaml::Output;
- void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) {
- YAMLXRayTrace Trace;
- const auto &FH = Records.getFileHeader();
- Trace.Header = {FH.Version, FH.Type, FH.ConstantTSC, FH.NonstopTSC,
- FH.CycleFrequency};
- Trace.Records.reserve(Records.size());
- for (const auto &R : Records) {
- Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId,
- Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId)
- : llvm::to_string(R.FuncId),
- R.TSC, R.TId, R.PId, R.CallArgs, R.Data});
- }
- Output Out(OS, nullptr, 0);
- Out.setWriteDefaultValues(false);
- Out << Trace;
- }
- void TraceConverter::exportAsRAWv1(const Trace &Records, raw_ostream &OS) {
- // First write out the file header, in the correct endian-appropriate format
- // (XRay assumes currently little endian).
- support::endian::Writer Writer(OS, support::endianness::little);
- const auto &FH = Records.getFileHeader();
- Writer.write(FH.Version);
- Writer.write(FH.Type);
- uint32_t Bitfield{0};
- if (FH.ConstantTSC)
- Bitfield |= 1uL;
- if (FH.NonstopTSC)
- Bitfield |= 1uL << 1;
- Writer.write(Bitfield);
- Writer.write(FH.CycleFrequency);
- // There's 16 bytes of padding at the end of the file header.
- static constexpr uint32_t Padding4B = 0;
- Writer.write(Padding4B);
- Writer.write(Padding4B);
- Writer.write(Padding4B);
- Writer.write(Padding4B);
- // Then write out the rest of the records, still in an endian-appropriate
- // format.
- for (const auto &R : Records) {
- switch (R.Type) {
- case RecordTypes::ENTER:
- case RecordTypes::ENTER_ARG:
- Writer.write(R.RecordType);
- Writer.write(static_cast<uint8_t>(R.CPU));
- Writer.write(uint8_t{0});
- break;
- case RecordTypes::EXIT:
- Writer.write(R.RecordType);
- Writer.write(static_cast<uint8_t>(R.CPU));
- Writer.write(uint8_t{1});
- break;
- case RecordTypes::TAIL_EXIT:
- Writer.write(R.RecordType);
- Writer.write(static_cast<uint8_t>(R.CPU));
- Writer.write(uint8_t{2});
- break;
- case RecordTypes::CUSTOM_EVENT:
- case RecordTypes::TYPED_EVENT:
- // Skip custom and typed event records for v1 logs.
- continue;
- }
- Writer.write(R.FuncId);
- Writer.write(R.TSC);
- Writer.write(R.TId);
- if (FH.Version >= 3)
- Writer.write(R.PId);
- else
- Writer.write(Padding4B);
- Writer.write(Padding4B);
- Writer.write(Padding4B);
- }
- }
- namespace {
- // A structure that allows building a dictionary of stack ids for the Chrome
- // trace event format.
- struct StackIdData {
- // Each Stack of function calls has a unique ID.
- unsigned id;
- // Bookkeeping so that IDs can be maintained uniquely across threads.
- // Traversal keeps sibling pointers to other threads stacks. This is helpful
- // to determine when a thread encounters a new stack and should assign a new
- // unique ID.
- SmallVector<TrieNode<StackIdData> *, 4> siblings;
- };
- using StackTrieNode = TrieNode<StackIdData>;
- // A helper function to find the sibling nodes for an encountered function in a
- // thread of execution. Relies on the invariant that each time a new node is
- // traversed in a thread, sibling bidirectional pointers are maintained.
- SmallVector<StackTrieNode *, 4>
- findSiblings(StackTrieNode *parent, int32_t FnId, uint32_t TId,
- const DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>>
- &StackRootsByThreadId) {
- SmallVector<StackTrieNode *, 4> Siblings{};
- if (parent == nullptr) {
- for (auto map_iter : StackRootsByThreadId) {
- // Only look for siblings in other threads.
- if (map_iter.first != TId)
- for (auto node_iter : map_iter.second) {
- if (node_iter->FuncId == FnId)
- Siblings.push_back(node_iter);
- }
- }
- return Siblings;
- }
- for (auto *ParentSibling : parent->ExtraData.siblings)
- for (auto node_iter : ParentSibling->Callees)
- if (node_iter->FuncId == FnId)
- Siblings.push_back(node_iter);
- return Siblings;
- }
- // Given a function being invoked in a thread with id TId, finds and returns the
- // StackTrie representing the function call stack. If no node exists, creates
- // the node. Assigns unique IDs to stacks newly encountered among all threads
- // and keeps sibling links up to when creating new nodes.
- StackTrieNode *findOrCreateStackNode(
- StackTrieNode *Parent, int32_t FuncId, uint32_t TId,
- DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>> &StackRootsByThreadId,
- DenseMap<unsigned, StackTrieNode *> &StacksByStackId, unsigned *id_counter,
- std::forward_list<StackTrieNode> &NodeStore) {
- SmallVector<StackTrieNode *, 4> &ParentCallees =
- Parent == nullptr ? StackRootsByThreadId[TId] : Parent->Callees;
- auto match = find_if(ParentCallees, [FuncId](StackTrieNode *ParentCallee) {
- return FuncId == ParentCallee->FuncId;
- });
- if (match != ParentCallees.end())
- return *match;
- SmallVector<StackTrieNode *, 4> siblings =
- findSiblings(Parent, FuncId, TId, StackRootsByThreadId);
- if (siblings.empty()) {
- NodeStore.push_front({FuncId, Parent, {}, {(*id_counter)++, {}}});
- StackTrieNode *CurrentStack = &NodeStore.front();
- StacksByStackId[*id_counter - 1] = CurrentStack;
- ParentCallees.push_back(CurrentStack);
- return CurrentStack;
- }
- unsigned stack_id = siblings[0]->ExtraData.id;
- NodeStore.push_front({FuncId, Parent, {}, {stack_id, std::move(siblings)}});
- StackTrieNode *CurrentStack = &NodeStore.front();
- for (auto *sibling : CurrentStack->ExtraData.siblings)
- sibling->ExtraData.siblings.push_back(CurrentStack);
- ParentCallees.push_back(CurrentStack);
- return CurrentStack;
- }
- void writeTraceViewerRecord(uint16_t Version, raw_ostream &OS, int32_t FuncId,
- uint32_t TId, uint32_t PId, bool Symbolize,
- const FuncIdConversionHelper &FuncIdHelper,
- double EventTimestampUs,
- const StackTrieNode &StackCursor,
- StringRef FunctionPhenotype) {
- OS << " ";
- if (Version >= 3) {
- OS << llvm::formatv(
- R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "{3}", )"
- R"("ts" : "{4:f4}", "sf" : "{5}" })",
- (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
- : llvm::to_string(FuncId)),
- FunctionPhenotype, TId, PId, EventTimestampUs,
- StackCursor.ExtraData.id);
- } else {
- OS << llvm::formatv(
- R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"
- R"("ts" : "{3:f3}", "sf" : "{4}" })",
- (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
- : llvm::to_string(FuncId)),
- FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);
- }
- }
- } // namespace
- void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,
- raw_ostream &OS) {
- const auto &FH = Records.getFileHeader();
- auto Version = FH.Version;
- auto CycleFreq = FH.CycleFrequency;
- unsigned id_counter = 0;
- int NumOutputRecords = 0;
- OS << "{\n \"traceEvents\": [\n";
- DenseMap<uint32_t, StackTrieNode *> StackCursorByThreadId{};
- DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>> StackRootsByThreadId{};
- DenseMap<unsigned, StackTrieNode *> StacksByStackId{};
- std::forward_list<StackTrieNode> NodeStore{};
- for (const auto &R : Records) {
- // Chrome trace event format always wants data in micros.
- // CyclesPerMicro = CycleHertz / 10^6
- // TSC / CyclesPerMicro == TSC * 10^6 / CycleHertz == MicroTimestamp
- // Could lose some precision here by converting the TSC to a double to
- // multiply by the period in micros. 52 bit mantissa is a good start though.
- // TODO: Make feature request to Chrome Trace viewer to accept ticks and a
- // frequency or do some more involved calculation to avoid dangers of
- // conversion.
- double EventTimestampUs = double(1000000) / CycleFreq * double(R.TSC);
- StackTrieNode *&StackCursor = StackCursorByThreadId[R.TId];
- switch (R.Type) {
- case RecordTypes::CUSTOM_EVENT:
- case RecordTypes::TYPED_EVENT:
- // TODO: Support typed and custom event rendering on Chrome Trace Viewer.
- break;
- case RecordTypes::ENTER:
- case RecordTypes::ENTER_ARG:
- StackCursor = findOrCreateStackNode(StackCursor, R.FuncId, R.TId,
- StackRootsByThreadId, StacksByStackId,
- &id_counter, NodeStore);
- // Each record is represented as a json dictionary with function name,
- // type of B for begin or E for end, thread id, process id,
- // timestamp in microseconds, and a stack frame id. The ids are logged
- // in an id dictionary after the events.
- if (NumOutputRecords++ > 0) {
- OS << ",\n";
- }
- writeTraceViewerRecord(Version, OS, R.FuncId, R.TId, R.PId, Symbolize,
- FuncIdHelper, EventTimestampUs, *StackCursor, "B");
- break;
- case RecordTypes::EXIT:
- case RecordTypes::TAIL_EXIT:
- // No entries to record end for.
- if (StackCursor == nullptr)
- break;
- // Should we emit an END record anyway or account this condition?
- // (And/Or in loop termination below)
- StackTrieNode *PreviousCursor = nullptr;
- do {
- if (NumOutputRecords++ > 0) {
- OS << ",\n";
- }
- writeTraceViewerRecord(Version, OS, StackCursor->FuncId, R.TId, R.PId,
- Symbolize, FuncIdHelper, EventTimestampUs,
- *StackCursor, "E");
- PreviousCursor = StackCursor;
- StackCursor = StackCursor->Parent;
- } while (PreviousCursor->FuncId != R.FuncId && StackCursor != nullptr);
- break;
- }
- }
- OS << "\n ],\n"; // Close the Trace Events array.
- OS << " "
- << "\"displayTimeUnit\": \"ns\",\n";
- // The stackFrames dictionary substantially reduces size of the output file by
- // avoiding repeating the entire call stack of function names for each entry.
- OS << R"( "stackFrames": {)";
- int stack_frame_count = 0;
- for (auto map_iter : StacksByStackId) {
- if (stack_frame_count++ == 0)
- OS << "\n";
- else
- OS << ",\n";
- OS << " ";
- OS << llvm::formatv(
- R"("{0}" : { "name" : "{1}")", map_iter.first,
- (Symbolize ? FuncIdHelper.SymbolOrNumber(map_iter.second->FuncId)
- : llvm::to_string(map_iter.second->FuncId)));
- if (map_iter.second->Parent != nullptr)
- OS << llvm::formatv(R"(, "parent": "{0}")",
- map_iter.second->Parent->ExtraData.id);
- OS << " }";
- }
- OS << "\n }\n"; // Close the stack frames map.
- OS << "}\n"; // Close the JSON entry.
- }
- namespace llvm {
- namespace xray {
- static CommandRegistration Unused(&Convert, []() -> Error {
- // FIXME: Support conversion to BINARY when upgrading XRay trace versions.
- InstrumentationMap Map;
- if (!ConvertInstrMap.empty()) {
- auto InstrumentationMapOrError = loadInstrumentationMap(ConvertInstrMap);
- if (!InstrumentationMapOrError)
- return joinErrors(make_error<StringError>(
- Twine("Cannot open instrumentation map '") +
- ConvertInstrMap + "'",
- std::make_error_code(std::errc::invalid_argument)),
- InstrumentationMapOrError.takeError());
- Map = std::move(*InstrumentationMapOrError);
- }
- const auto &FunctionAddresses = Map.getFunctionAddresses();
- symbolize::LLVMSymbolizer::Options SymbolizerOpts;
- if (Demangle.getPosition() < NoDemangle.getPosition())
- SymbolizerOpts.Demangle = false;
- symbolize::LLVMSymbolizer Symbolizer(SymbolizerOpts);
- llvm::xray::FuncIdConversionHelper FuncIdHelper(ConvertInstrMap, Symbolizer,
- FunctionAddresses);
- llvm::xray::TraceConverter TC(FuncIdHelper, ConvertSymbolize);
- std::error_code EC;
- raw_fd_ostream OS(ConvertOutput, EC,
- ConvertOutputFormat == ConvertFormats::BINARY
- ? sys::fs::OpenFlags::OF_None
- : sys::fs::OpenFlags::OF_TextWithCRLF);
- if (EC)
- return make_error<StringError>(
- Twine("Cannot open file '") + ConvertOutput + "' for writing.", EC);
- auto TraceOrErr = loadTraceFile(ConvertInput, ConvertSortInput);
- if (!TraceOrErr)
- return joinErrors(
- make_error<StringError>(
- Twine("Failed loading input file '") + ConvertInput + "'.",
- std::make_error_code(std::errc::executable_format_error)),
- TraceOrErr.takeError());
- auto &T = *TraceOrErr;
- switch (ConvertOutputFormat) {
- case ConvertFormats::YAML:
- TC.exportAsYAML(T, OS);
- break;
- case ConvertFormats::BINARY:
- TC.exportAsRAWv1(T, OS);
- break;
- case ConvertFormats::CHROME_TRACE_EVENT:
- TC.exportAsChromeTraceEventFormat(T, OS);
- break;
- }
- return Error::success();
- });
- } // namespace xray
- } // namespace llvm
|