xray-converter.cpp 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425
  1. //===- xray-converter.cpp: XRay Trace Conversion --------------------------===//
  2. //
  3. // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
  4. // See https://llvm.org/LICENSE.txt for license information.
  5. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  6. //
  7. //===----------------------------------------------------------------------===//
  8. //
  9. // Implements the trace conversion functions.
  10. //
  11. //===----------------------------------------------------------------------===//
  12. #include "xray-converter.h"
  13. #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"
  25. using namespace llvm;
  26. using namespace xray;
  27. // llvm-xray convert
  28. // ----------------------------------------------------------------------------
  29. static cl::SubCommand Convert("convert", "Trace Format Conversion");
  30. static cl::opt<std::string> ConvertInput(cl::Positional,
  31. cl::desc("<xray log file>"),
  32. cl::Required, cl::sub(Convert));
  33. enum class ConvertFormats { BINARY, YAML, CHROME_TRACE_EVENT };
  34. static cl::opt<ConvertFormats> ConvertOutputFormat(
  35. "output-format", cl::desc("output format"),
  36. cl::values(clEnumValN(ConvertFormats::BINARY, "raw", "output in binary"),
  37. clEnumValN(ConvertFormats::YAML, "yaml", "output in yaml"),
  38. clEnumValN(ConvertFormats::CHROME_TRACE_EVENT, "trace_event",
  39. "Output in chrome's trace event format. "
  40. "May be visualized with the Catapult trace viewer.")),
  41. cl::sub(Convert));
  42. static cl::alias ConvertOutputFormat2("f", cl::aliasopt(ConvertOutputFormat),
  43. cl::desc("Alias for -output-format"));
  44. static cl::opt<std::string>
  45. ConvertOutput("output", cl::value_desc("output file"), cl::init("-"),
  46. cl::desc("output file; use '-' for stdout"),
  47. cl::sub(Convert));
  48. static cl::alias ConvertOutput2("o", cl::aliasopt(ConvertOutput),
  49. cl::desc("Alias for -output"));
  50. static cl::opt<bool>
  51. ConvertSymbolize("symbolize",
  52. cl::desc("symbolize function ids from the input log"),
  53. cl::init(false), cl::sub(Convert));
  54. static cl::alias ConvertSymbolize2("y", cl::aliasopt(ConvertSymbolize),
  55. cl::desc("Alias for -symbolize"));
  56. static cl::opt<bool>
  57. NoDemangle("no-demangle",
  58. cl::desc("determines whether to demangle function name "
  59. "when symbolizing function ids from the input log"),
  60. cl::init(false), cl::sub(Convert));
  61. static cl::opt<bool> Demangle("demangle",
  62. cl::desc("demangle symbols (default)"),
  63. cl::sub(Convert));
  64. static cl::opt<std::string>
  65. ConvertInstrMap("instr_map",
  66. cl::desc("binary with the instrumentation map, or "
  67. "a separate instrumentation map"),
  68. cl::value_desc("binary with xray_instr_map"),
  69. cl::sub(Convert), cl::init(""));
  70. static cl::alias ConvertInstrMap2("m", cl::aliasopt(ConvertInstrMap),
  71. cl::desc("Alias for -instr_map"));
  72. static cl::opt<bool> ConvertSortInput(
  73. "sort",
  74. cl::desc("determines whether to sort input log records by timestamp"),
  75. cl::sub(Convert), cl::init(true));
  76. static cl::alias ConvertSortInput2("s", cl::aliasopt(ConvertSortInput),
  77. cl::desc("Alias for -sort"));
  78. using llvm::yaml::Output;
  79. void TraceConverter::exportAsYAML(const Trace &Records, raw_ostream &OS) {
  80. YAMLXRayTrace Trace;
  81. const auto &FH = Records.getFileHeader();
  82. Trace.Header = {FH.Version, FH.Type, FH.ConstantTSC, FH.NonstopTSC,
  83. FH.CycleFrequency};
  84. Trace.Records.reserve(Records.size());
  85. for (const auto &R : Records) {
  86. Trace.Records.push_back({R.RecordType, R.CPU, R.Type, R.FuncId,
  87. Symbolize ? FuncIdHelper.SymbolOrNumber(R.FuncId)
  88. : llvm::to_string(R.FuncId),
  89. R.TSC, R.TId, R.PId, R.CallArgs, R.Data});
  90. }
  91. Output Out(OS, nullptr, 0);
  92. Out.setWriteDefaultValues(false);
  93. Out << Trace;
  94. }
  95. void TraceConverter::exportAsRAWv1(const Trace &Records, raw_ostream &OS) {
  96. // First write out the file header, in the correct endian-appropriate format
  97. // (XRay assumes currently little endian).
  98. support::endian::Writer Writer(OS, support::endianness::little);
  99. const auto &FH = Records.getFileHeader();
  100. Writer.write(FH.Version);
  101. Writer.write(FH.Type);
  102. uint32_t Bitfield{0};
  103. if (FH.ConstantTSC)
  104. Bitfield |= 1uL;
  105. if (FH.NonstopTSC)
  106. Bitfield |= 1uL << 1;
  107. Writer.write(Bitfield);
  108. Writer.write(FH.CycleFrequency);
  109. // There's 16 bytes of padding at the end of the file header.
  110. static constexpr uint32_t Padding4B = 0;
  111. Writer.write(Padding4B);
  112. Writer.write(Padding4B);
  113. Writer.write(Padding4B);
  114. Writer.write(Padding4B);
  115. // Then write out the rest of the records, still in an endian-appropriate
  116. // format.
  117. for (const auto &R : Records) {
  118. switch (R.Type) {
  119. case RecordTypes::ENTER:
  120. case RecordTypes::ENTER_ARG:
  121. Writer.write(R.RecordType);
  122. Writer.write(static_cast<uint8_t>(R.CPU));
  123. Writer.write(uint8_t{0});
  124. break;
  125. case RecordTypes::EXIT:
  126. Writer.write(R.RecordType);
  127. Writer.write(static_cast<uint8_t>(R.CPU));
  128. Writer.write(uint8_t{1});
  129. break;
  130. case RecordTypes::TAIL_EXIT:
  131. Writer.write(R.RecordType);
  132. Writer.write(static_cast<uint8_t>(R.CPU));
  133. Writer.write(uint8_t{2});
  134. break;
  135. case RecordTypes::CUSTOM_EVENT:
  136. case RecordTypes::TYPED_EVENT:
  137. // Skip custom and typed event records for v1 logs.
  138. continue;
  139. }
  140. Writer.write(R.FuncId);
  141. Writer.write(R.TSC);
  142. Writer.write(R.TId);
  143. if (FH.Version >= 3)
  144. Writer.write(R.PId);
  145. else
  146. Writer.write(Padding4B);
  147. Writer.write(Padding4B);
  148. Writer.write(Padding4B);
  149. }
  150. }
  151. namespace {
  152. // A structure that allows building a dictionary of stack ids for the Chrome
  153. // trace event format.
  154. struct StackIdData {
  155. // Each Stack of function calls has a unique ID.
  156. unsigned id;
  157. // Bookkeeping so that IDs can be maintained uniquely across threads.
  158. // Traversal keeps sibling pointers to other threads stacks. This is helpful
  159. // to determine when a thread encounters a new stack and should assign a new
  160. // unique ID.
  161. SmallVector<TrieNode<StackIdData> *, 4> siblings;
  162. };
  163. using StackTrieNode = TrieNode<StackIdData>;
  164. // A helper function to find the sibling nodes for an encountered function in a
  165. // thread of execution. Relies on the invariant that each time a new node is
  166. // traversed in a thread, sibling bidirectional pointers are maintained.
  167. SmallVector<StackTrieNode *, 4>
  168. findSiblings(StackTrieNode *parent, int32_t FnId, uint32_t TId,
  169. const DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>>
  170. &StackRootsByThreadId) {
  171. SmallVector<StackTrieNode *, 4> Siblings{};
  172. if (parent == nullptr) {
  173. for (auto map_iter : StackRootsByThreadId) {
  174. // Only look for siblings in other threads.
  175. if (map_iter.first != TId)
  176. for (auto node_iter : map_iter.second) {
  177. if (node_iter->FuncId == FnId)
  178. Siblings.push_back(node_iter);
  179. }
  180. }
  181. return Siblings;
  182. }
  183. for (auto *ParentSibling : parent->ExtraData.siblings)
  184. for (auto node_iter : ParentSibling->Callees)
  185. if (node_iter->FuncId == FnId)
  186. Siblings.push_back(node_iter);
  187. return Siblings;
  188. }
  189. // Given a function being invoked in a thread with id TId, finds and returns the
  190. // StackTrie representing the function call stack. If no node exists, creates
  191. // the node. Assigns unique IDs to stacks newly encountered among all threads
  192. // and keeps sibling links up to when creating new nodes.
  193. StackTrieNode *findOrCreateStackNode(
  194. StackTrieNode *Parent, int32_t FuncId, uint32_t TId,
  195. DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>> &StackRootsByThreadId,
  196. DenseMap<unsigned, StackTrieNode *> &StacksByStackId, unsigned *id_counter,
  197. std::forward_list<StackTrieNode> &NodeStore) {
  198. SmallVector<StackTrieNode *, 4> &ParentCallees =
  199. Parent == nullptr ? StackRootsByThreadId[TId] : Parent->Callees;
  200. auto match = find_if(ParentCallees, [FuncId](StackTrieNode *ParentCallee) {
  201. return FuncId == ParentCallee->FuncId;
  202. });
  203. if (match != ParentCallees.end())
  204. return *match;
  205. SmallVector<StackTrieNode *, 4> siblings =
  206. findSiblings(Parent, FuncId, TId, StackRootsByThreadId);
  207. if (siblings.empty()) {
  208. NodeStore.push_front({FuncId, Parent, {}, {(*id_counter)++, {}}});
  209. StackTrieNode *CurrentStack = &NodeStore.front();
  210. StacksByStackId[*id_counter - 1] = CurrentStack;
  211. ParentCallees.push_back(CurrentStack);
  212. return CurrentStack;
  213. }
  214. unsigned stack_id = siblings[0]->ExtraData.id;
  215. NodeStore.push_front({FuncId, Parent, {}, {stack_id, std::move(siblings)}});
  216. StackTrieNode *CurrentStack = &NodeStore.front();
  217. for (auto *sibling : CurrentStack->ExtraData.siblings)
  218. sibling->ExtraData.siblings.push_back(CurrentStack);
  219. ParentCallees.push_back(CurrentStack);
  220. return CurrentStack;
  221. }
  222. void writeTraceViewerRecord(uint16_t Version, raw_ostream &OS, int32_t FuncId,
  223. uint32_t TId, uint32_t PId, bool Symbolize,
  224. const FuncIdConversionHelper &FuncIdHelper,
  225. double EventTimestampUs,
  226. const StackTrieNode &StackCursor,
  227. StringRef FunctionPhenotype) {
  228. OS << " ";
  229. if (Version >= 3) {
  230. OS << llvm::formatv(
  231. R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "{3}", )"
  232. R"("ts" : "{4:f4}", "sf" : "{5}" })",
  233. (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
  234. : llvm::to_string(FuncId)),
  235. FunctionPhenotype, TId, PId, EventTimestampUs,
  236. StackCursor.ExtraData.id);
  237. } else {
  238. OS << llvm::formatv(
  239. R"({ "name" : "{0}", "ph" : "{1}", "tid" : "{2}", "pid" : "1", )"
  240. R"("ts" : "{3:f3}", "sf" : "{4}" })",
  241. (Symbolize ? FuncIdHelper.SymbolOrNumber(FuncId)
  242. : llvm::to_string(FuncId)),
  243. FunctionPhenotype, TId, EventTimestampUs, StackCursor.ExtraData.id);
  244. }
  245. }
  246. } // namespace
  247. void TraceConverter::exportAsChromeTraceEventFormat(const Trace &Records,
  248. raw_ostream &OS) {
  249. const auto &FH = Records.getFileHeader();
  250. auto Version = FH.Version;
  251. auto CycleFreq = FH.CycleFrequency;
  252. unsigned id_counter = 0;
  253. int NumOutputRecords = 0;
  254. OS << "{\n \"traceEvents\": [\n";
  255. DenseMap<uint32_t, StackTrieNode *> StackCursorByThreadId{};
  256. DenseMap<uint32_t, SmallVector<StackTrieNode *, 4>> StackRootsByThreadId{};
  257. DenseMap<unsigned, StackTrieNode *> StacksByStackId{};
  258. std::forward_list<StackTrieNode> NodeStore{};
  259. for (const auto &R : Records) {
  260. // Chrome trace event format always wants data in micros.
  261. // CyclesPerMicro = CycleHertz / 10^6
  262. // TSC / CyclesPerMicro == TSC * 10^6 / CycleHertz == MicroTimestamp
  263. // Could lose some precision here by converting the TSC to a double to
  264. // multiply by the period in micros. 52 bit mantissa is a good start though.
  265. // TODO: Make feature request to Chrome Trace viewer to accept ticks and a
  266. // frequency or do some more involved calculation to avoid dangers of
  267. // conversion.
  268. double EventTimestampUs = double(1000000) / CycleFreq * double(R.TSC);
  269. StackTrieNode *&StackCursor = StackCursorByThreadId[R.TId];
  270. switch (R.Type) {
  271. case RecordTypes::CUSTOM_EVENT:
  272. case RecordTypes::TYPED_EVENT:
  273. // TODO: Support typed and custom event rendering on Chrome Trace Viewer.
  274. break;
  275. case RecordTypes::ENTER:
  276. case RecordTypes::ENTER_ARG:
  277. StackCursor = findOrCreateStackNode(StackCursor, R.FuncId, R.TId,
  278. StackRootsByThreadId, StacksByStackId,
  279. &id_counter, NodeStore);
  280. // Each record is represented as a json dictionary with function name,
  281. // type of B for begin or E for end, thread id, process id,
  282. // timestamp in microseconds, and a stack frame id. The ids are logged
  283. // in an id dictionary after the events.
  284. if (NumOutputRecords++ > 0) {
  285. OS << ",\n";
  286. }
  287. writeTraceViewerRecord(Version, OS, R.FuncId, R.TId, R.PId, Symbolize,
  288. FuncIdHelper, EventTimestampUs, *StackCursor, "B");
  289. break;
  290. case RecordTypes::EXIT:
  291. case RecordTypes::TAIL_EXIT:
  292. // No entries to record end for.
  293. if (StackCursor == nullptr)
  294. break;
  295. // Should we emit an END record anyway or account this condition?
  296. // (And/Or in loop termination below)
  297. StackTrieNode *PreviousCursor = nullptr;
  298. do {
  299. if (NumOutputRecords++ > 0) {
  300. OS << ",\n";
  301. }
  302. writeTraceViewerRecord(Version, OS, StackCursor->FuncId, R.TId, R.PId,
  303. Symbolize, FuncIdHelper, EventTimestampUs,
  304. *StackCursor, "E");
  305. PreviousCursor = StackCursor;
  306. StackCursor = StackCursor->Parent;
  307. } while (PreviousCursor->FuncId != R.FuncId && StackCursor != nullptr);
  308. break;
  309. }
  310. }
  311. OS << "\n ],\n"; // Close the Trace Events array.
  312. OS << " "
  313. << "\"displayTimeUnit\": \"ns\",\n";
  314. // The stackFrames dictionary substantially reduces size of the output file by
  315. // avoiding repeating the entire call stack of function names for each entry.
  316. OS << R"( "stackFrames": {)";
  317. int stack_frame_count = 0;
  318. for (auto map_iter : StacksByStackId) {
  319. if (stack_frame_count++ == 0)
  320. OS << "\n";
  321. else
  322. OS << ",\n";
  323. OS << " ";
  324. OS << llvm::formatv(
  325. R"("{0}" : { "name" : "{1}")", map_iter.first,
  326. (Symbolize ? FuncIdHelper.SymbolOrNumber(map_iter.second->FuncId)
  327. : llvm::to_string(map_iter.second->FuncId)));
  328. if (map_iter.second->Parent != nullptr)
  329. OS << llvm::formatv(R"(, "parent": "{0}")",
  330. map_iter.second->Parent->ExtraData.id);
  331. OS << " }";
  332. }
  333. OS << "\n }\n"; // Close the stack frames map.
  334. OS << "}\n"; // Close the JSON entry.
  335. }
  336. namespace llvm {
  337. namespace xray {
  338. static CommandRegistration Unused(&Convert, []() -> Error {
  339. // FIXME: Support conversion to BINARY when upgrading XRay trace versions.
  340. InstrumentationMap Map;
  341. if (!ConvertInstrMap.empty()) {
  342. auto InstrumentationMapOrError = loadInstrumentationMap(ConvertInstrMap);
  343. if (!InstrumentationMapOrError)
  344. return joinErrors(make_error<StringError>(
  345. Twine("Cannot open instrumentation map '") +
  346. ConvertInstrMap + "'",
  347. std::make_error_code(std::errc::invalid_argument)),
  348. InstrumentationMapOrError.takeError());
  349. Map = std::move(*InstrumentationMapOrError);
  350. }
  351. const auto &FunctionAddresses = Map.getFunctionAddresses();
  352. symbolize::LLVMSymbolizer::Options SymbolizerOpts;
  353. if (Demangle.getPosition() < NoDemangle.getPosition())
  354. SymbolizerOpts.Demangle = false;
  355. symbolize::LLVMSymbolizer Symbolizer(SymbolizerOpts);
  356. llvm::xray::FuncIdConversionHelper FuncIdHelper(ConvertInstrMap, Symbolizer,
  357. FunctionAddresses);
  358. llvm::xray::TraceConverter TC(FuncIdHelper, ConvertSymbolize);
  359. std::error_code EC;
  360. raw_fd_ostream OS(ConvertOutput, EC,
  361. ConvertOutputFormat == ConvertFormats::BINARY
  362. ? sys::fs::OpenFlags::OF_None
  363. : sys::fs::OpenFlags::OF_TextWithCRLF);
  364. if (EC)
  365. return make_error<StringError>(
  366. Twine("Cannot open file '") + ConvertOutput + "' for writing.", EC);
  367. auto TraceOrErr = loadTraceFile(ConvertInput, ConvertSortInput);
  368. if (!TraceOrErr)
  369. return joinErrors(
  370. make_error<StringError>(
  371. Twine("Failed loading input file '") + ConvertInput + "'.",
  372. std::make_error_code(std::errc::executable_format_error)),
  373. TraceOrErr.takeError());
  374. auto &T = *TraceOrErr;
  375. switch (ConvertOutputFormat) {
  376. case ConvertFormats::YAML:
  377. TC.exportAsYAML(T, OS);
  378. break;
  379. case ConvertFormats::BINARY:
  380. TC.exportAsRAWv1(T, OS);
  381. break;
  382. case ConvertFormats::CHROME_TRACE_EVENT:
  383. TC.exportAsChromeTraceEventFormat(T, OS);
  384. break;
  385. }
  386. return Error::success();
  387. });
  388. } // namespace xray
  389. } // namespace llvm