Trace.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475
  1. //===- Trace.cpp - XRay Trace Loading implementation. ---------------------===//
  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. // XRay log reader implementation.
  10. //
  11. //===----------------------------------------------------------------------===//
  12. #include "llvm/XRay/Trace.h"
  13. #include "llvm/ADT/STLExtras.h"
  14. #include "llvm/Support/DataExtractor.h"
  15. #include "llvm/Support/Error.h"
  16. #include "llvm/Support/FileSystem.h"
  17. #include "llvm/XRay/BlockIndexer.h"
  18. #include "llvm/XRay/BlockVerifier.h"
  19. #include "llvm/XRay/FDRRecordConsumer.h"
  20. #include "llvm/XRay/FDRRecordProducer.h"
  21. #include "llvm/XRay/FDRRecords.h"
  22. #include "llvm/XRay/FDRTraceExpander.h"
  23. #include "llvm/XRay/FileHeaderReader.h"
  24. #include "llvm/XRay/YAMLXRayRecord.h"
  25. #include <memory>
  26. #include <vector>
  27. using namespace llvm;
  28. using namespace llvm::xray;
  29. using llvm::yaml::Input;
  30. namespace {
  31. Error loadNaiveFormatLog(StringRef Data, bool IsLittleEndian,
  32. XRayFileHeader &FileHeader,
  33. std::vector<XRayRecord> &Records) {
  34. if (Data.size() < 32)
  35. return make_error<StringError>(
  36. "Not enough bytes for an XRay log.",
  37. std::make_error_code(std::errc::invalid_argument));
  38. if (Data.size() - 32 == 0 || Data.size() % 32 != 0)
  39. return make_error<StringError>(
  40. "Invalid-sized XRay data.",
  41. std::make_error_code(std::errc::invalid_argument));
  42. DataExtractor Reader(Data, IsLittleEndian, 8);
  43. uint64_t OffsetPtr = 0;
  44. auto FileHeaderOrError = readBinaryFormatHeader(Reader, OffsetPtr);
  45. if (!FileHeaderOrError)
  46. return FileHeaderOrError.takeError();
  47. FileHeader = std::move(FileHeaderOrError.get());
  48. // Each record after the header will be 32 bytes, in the following format:
  49. //
  50. // (2) uint16 : record type
  51. // (1) uint8 : cpu id
  52. // (1) uint8 : type
  53. // (4) sint32 : function id
  54. // (8) uint64 : tsc
  55. // (4) uint32 : thread id
  56. // (4) uint32 : process id
  57. // (8) - : padding
  58. while (Reader.isValidOffset(OffsetPtr)) {
  59. if (!Reader.isValidOffsetForDataOfSize(OffsetPtr, 32))
  60. return createStringError(
  61. std::make_error_code(std::errc::executable_format_error),
  62. "Not enough bytes to read a full record at offset %" PRId64 ".",
  63. OffsetPtr);
  64. auto PreReadOffset = OffsetPtr;
  65. auto RecordType = Reader.getU16(&OffsetPtr);
  66. if (OffsetPtr == PreReadOffset)
  67. return createStringError(
  68. std::make_error_code(std::errc::executable_format_error),
  69. "Failed reading record type at offset %" PRId64 ".", OffsetPtr);
  70. switch (RecordType) {
  71. case 0: { // Normal records.
  72. Records.emplace_back();
  73. auto &Record = Records.back();
  74. Record.RecordType = RecordType;
  75. PreReadOffset = OffsetPtr;
  76. Record.CPU = Reader.getU8(&OffsetPtr);
  77. if (OffsetPtr == PreReadOffset)
  78. return createStringError(
  79. std::make_error_code(std::errc::executable_format_error),
  80. "Failed reading CPU field at offset %" PRId64 ".", OffsetPtr);
  81. PreReadOffset = OffsetPtr;
  82. auto Type = Reader.getU8(&OffsetPtr);
  83. if (OffsetPtr == PreReadOffset)
  84. return createStringError(
  85. std::make_error_code(std::errc::executable_format_error),
  86. "Failed reading record type field at offset %" PRId64 ".",
  87. OffsetPtr);
  88. switch (Type) {
  89. case 0:
  90. Record.Type = RecordTypes::ENTER;
  91. break;
  92. case 1:
  93. Record.Type = RecordTypes::EXIT;
  94. break;
  95. case 2:
  96. Record.Type = RecordTypes::TAIL_EXIT;
  97. break;
  98. case 3:
  99. Record.Type = RecordTypes::ENTER_ARG;
  100. break;
  101. default:
  102. return createStringError(
  103. std::make_error_code(std::errc::executable_format_error),
  104. "Unknown record type '%d' at offset %" PRId64 ".", Type, OffsetPtr);
  105. }
  106. PreReadOffset = OffsetPtr;
  107. Record.FuncId = Reader.getSigned(&OffsetPtr, sizeof(int32_t));
  108. if (OffsetPtr == PreReadOffset)
  109. return createStringError(
  110. std::make_error_code(std::errc::executable_format_error),
  111. "Failed reading function id field at offset %" PRId64 ".",
  112. OffsetPtr);
  113. PreReadOffset = OffsetPtr;
  114. Record.TSC = Reader.getU64(&OffsetPtr);
  115. if (OffsetPtr == PreReadOffset)
  116. return createStringError(
  117. std::make_error_code(std::errc::executable_format_error),
  118. "Failed reading TSC field at offset %" PRId64 ".", OffsetPtr);
  119. PreReadOffset = OffsetPtr;
  120. Record.TId = Reader.getU32(&OffsetPtr);
  121. if (OffsetPtr == PreReadOffset)
  122. return createStringError(
  123. std::make_error_code(std::errc::executable_format_error),
  124. "Failed reading thread id field at offset %" PRId64 ".", OffsetPtr);
  125. PreReadOffset = OffsetPtr;
  126. Record.PId = Reader.getU32(&OffsetPtr);
  127. if (OffsetPtr == PreReadOffset)
  128. return createStringError(
  129. std::make_error_code(std::errc::executable_format_error),
  130. "Failed reading process id at offset %" PRId64 ".", OffsetPtr);
  131. break;
  132. }
  133. case 1: { // Arg payload record.
  134. auto &Record = Records.back();
  135. // We skip the next two bytes of the record, because we don't need the
  136. // type and the CPU record for arg payloads.
  137. OffsetPtr += 2;
  138. PreReadOffset = OffsetPtr;
  139. int32_t FuncId = Reader.getSigned(&OffsetPtr, sizeof(int32_t));
  140. if (OffsetPtr == PreReadOffset)
  141. return createStringError(
  142. std::make_error_code(std::errc::executable_format_error),
  143. "Failed reading function id field at offset %" PRId64 ".",
  144. OffsetPtr);
  145. PreReadOffset = OffsetPtr;
  146. auto TId = Reader.getU32(&OffsetPtr);
  147. if (OffsetPtr == PreReadOffset)
  148. return createStringError(
  149. std::make_error_code(std::errc::executable_format_error),
  150. "Failed reading thread id field at offset %" PRId64 ".", OffsetPtr);
  151. PreReadOffset = OffsetPtr;
  152. auto PId = Reader.getU32(&OffsetPtr);
  153. if (OffsetPtr == PreReadOffset)
  154. return createStringError(
  155. std::make_error_code(std::errc::executable_format_error),
  156. "Failed reading process id field at offset %" PRId64 ".",
  157. OffsetPtr);
  158. // Make a check for versions above 3 for the Pid field
  159. if (Record.FuncId != FuncId || Record.TId != TId ||
  160. (FileHeader.Version >= 3 ? Record.PId != PId : false))
  161. return createStringError(
  162. std::make_error_code(std::errc::executable_format_error),
  163. "Corrupted log, found arg payload following non-matching "
  164. "function+thread record. Record for function %d != %d at offset "
  165. "%" PRId64 ".",
  166. Record.FuncId, FuncId, OffsetPtr);
  167. PreReadOffset = OffsetPtr;
  168. auto Arg = Reader.getU64(&OffsetPtr);
  169. if (OffsetPtr == PreReadOffset)
  170. return createStringError(
  171. std::make_error_code(std::errc::executable_format_error),
  172. "Failed reading argument payload at offset %" PRId64 ".",
  173. OffsetPtr);
  174. Record.CallArgs.push_back(Arg);
  175. break;
  176. }
  177. default:
  178. return createStringError(
  179. std::make_error_code(std::errc::executable_format_error),
  180. "Unknown record type '%d' at offset %" PRId64 ".", RecordType,
  181. OffsetPtr);
  182. }
  183. // Advance the offset pointer enough bytes to align to 32-byte records for
  184. // basic mode logs.
  185. OffsetPtr += 8;
  186. }
  187. return Error::success();
  188. }
  189. /// Reads a log in FDR mode for version 1 of this binary format. FDR mode is
  190. /// defined as part of the compiler-rt project in xray_fdr_logging.h, and such
  191. /// a log consists of the familiar 32 bit XRayHeader, followed by sequences of
  192. /// of interspersed 16 byte Metadata Records and 8 byte Function Records.
  193. ///
  194. /// The following is an attempt to document the grammar of the format, which is
  195. /// parsed by this function for little-endian machines. Since the format makes
  196. /// use of BitFields, when we support big-endian architectures, we will need to
  197. /// adjust not only the endianness parameter to llvm's RecordExtractor, but also
  198. /// the bit twiddling logic, which is consistent with the little-endian
  199. /// convention that BitFields within a struct will first be packed into the
  200. /// least significant bits the address they belong to.
  201. ///
  202. /// We expect a format complying with the grammar in the following pseudo-EBNF
  203. /// in Version 1 of the FDR log.
  204. ///
  205. /// FDRLog: XRayFileHeader ThreadBuffer*
  206. /// XRayFileHeader: 32 bytes to identify the log as FDR with machine metadata.
  207. /// Includes BufferSize
  208. /// ThreadBuffer: NewBuffer WallClockTime NewCPUId FunctionSequence EOB
  209. /// BufSize: 8 byte unsigned integer indicating how large the buffer is.
  210. /// NewBuffer: 16 byte metadata record with Thread Id.
  211. /// WallClockTime: 16 byte metadata record with human readable time.
  212. /// Pid: 16 byte metadata record with Pid
  213. /// NewCPUId: 16 byte metadata record with CPUId and a 64 bit TSC reading.
  214. /// EOB: 16 byte record in a thread buffer plus mem garbage to fill BufSize.
  215. /// FunctionSequence: NewCPUId | TSCWrap | FunctionRecord
  216. /// TSCWrap: 16 byte metadata record with a full 64 bit TSC reading.
  217. /// FunctionRecord: 8 byte record with FunctionId, entry/exit, and TSC delta.
  218. ///
  219. /// In Version 2, we make the following changes:
  220. ///
  221. /// ThreadBuffer: BufferExtents NewBuffer WallClockTime NewCPUId
  222. /// FunctionSequence
  223. /// BufferExtents: 16 byte metdata record describing how many usable bytes are
  224. /// in the buffer. This is measured from the start of the buffer
  225. /// and must always be at least 48 (bytes).
  226. ///
  227. /// In Version 3, we make the following changes:
  228. ///
  229. /// ThreadBuffer: BufferExtents NewBuffer WallClockTime Pid NewCPUId
  230. /// FunctionSequence
  231. /// EOB: *deprecated*
  232. ///
  233. /// In Version 4, we make the following changes:
  234. ///
  235. /// CustomEventRecord now includes the CPU data.
  236. ///
  237. /// In Version 5, we make the following changes:
  238. ///
  239. /// CustomEventRecord and TypedEventRecord now use TSC delta encoding similar to
  240. /// what FunctionRecord instances use, and we no longer need to include the CPU
  241. /// id in the CustomEventRecord.
  242. ///
  243. Error loadFDRLog(StringRef Data, bool IsLittleEndian,
  244. XRayFileHeader &FileHeader, std::vector<XRayRecord> &Records) {
  245. if (Data.size() < 32)
  246. return createStringError(std::make_error_code(std::errc::invalid_argument),
  247. "Not enough bytes for an XRay FDR log.");
  248. DataExtractor DE(Data, IsLittleEndian, 8);
  249. uint64_t OffsetPtr = 0;
  250. auto FileHeaderOrError = readBinaryFormatHeader(DE, OffsetPtr);
  251. if (!FileHeaderOrError)
  252. return FileHeaderOrError.takeError();
  253. FileHeader = std::move(FileHeaderOrError.get());
  254. // First we load the records into memory.
  255. std::vector<std::unique_ptr<Record>> FDRRecords;
  256. {
  257. FileBasedRecordProducer P(FileHeader, DE, OffsetPtr);
  258. LogBuilderConsumer C(FDRRecords);
  259. while (DE.isValidOffsetForDataOfSize(OffsetPtr, 1)) {
  260. auto R = P.produce();
  261. if (!R)
  262. return R.takeError();
  263. if (auto E = C.consume(std::move(R.get())))
  264. return E;
  265. }
  266. }
  267. // Next we index the records into blocks.
  268. BlockIndexer::Index Index;
  269. {
  270. BlockIndexer Indexer(Index);
  271. for (auto &R : FDRRecords)
  272. if (auto E = R->apply(Indexer))
  273. return E;
  274. if (auto E = Indexer.flush())
  275. return E;
  276. }
  277. // Then we verify the consistency of the blocks.
  278. {
  279. for (auto &PTB : Index) {
  280. auto &Blocks = PTB.second;
  281. for (auto &B : Blocks) {
  282. BlockVerifier Verifier;
  283. for (auto *R : B.Records)
  284. if (auto E = R->apply(Verifier))
  285. return E;
  286. if (auto E = Verifier.verify())
  287. return E;
  288. }
  289. }
  290. }
  291. // This is now the meat of the algorithm. Here we sort the blocks according to
  292. // the Walltime record in each of the blocks for the same thread. This allows
  293. // us to more consistently recreate the execution trace in temporal order.
  294. // After the sort, we then reconstitute `Trace` records using a stateful
  295. // visitor associated with a single process+thread pair.
  296. {
  297. for (auto &PTB : Index) {
  298. auto &Blocks = PTB.second;
  299. llvm::sort(Blocks, [](const BlockIndexer::Block &L,
  300. const BlockIndexer::Block &R) {
  301. return (L.WallclockTime->seconds() < R.WallclockTime->seconds() &&
  302. L.WallclockTime->nanos() < R.WallclockTime->nanos());
  303. });
  304. auto Adder = [&](const XRayRecord &R) { Records.push_back(R); };
  305. TraceExpander Expander(Adder, FileHeader.Version);
  306. for (auto &B : Blocks) {
  307. for (auto *R : B.Records)
  308. if (auto E = R->apply(Expander))
  309. return E;
  310. }
  311. if (auto E = Expander.flush())
  312. return E;
  313. }
  314. }
  315. return Error::success();
  316. }
  317. Error loadYAMLLog(StringRef Data, XRayFileHeader &FileHeader,
  318. std::vector<XRayRecord> &Records) {
  319. YAMLXRayTrace Trace;
  320. Input In(Data);
  321. In >> Trace;
  322. if (In.error())
  323. return make_error<StringError>("Failed loading YAML Data.", In.error());
  324. FileHeader.Version = Trace.Header.Version;
  325. FileHeader.Type = Trace.Header.Type;
  326. FileHeader.ConstantTSC = Trace.Header.ConstantTSC;
  327. FileHeader.NonstopTSC = Trace.Header.NonstopTSC;
  328. FileHeader.CycleFrequency = Trace.Header.CycleFrequency;
  329. if (FileHeader.Version != 1)
  330. return make_error<StringError>(
  331. Twine("Unsupported XRay file version: ") + Twine(FileHeader.Version),
  332. std::make_error_code(std::errc::invalid_argument));
  333. Records.clear();
  334. std::transform(Trace.Records.begin(), Trace.Records.end(),
  335. std::back_inserter(Records), [&](const YAMLXRayRecord &R) {
  336. return XRayRecord{R.RecordType, R.CPU, R.Type,
  337. R.FuncId, R.TSC, R.TId,
  338. R.PId, R.CallArgs, R.Data};
  339. });
  340. return Error::success();
  341. }
  342. } // namespace
  343. Expected<Trace> llvm::xray::loadTraceFile(StringRef Filename, bool Sort) {
  344. Expected<sys::fs::file_t> FdOrErr = sys::fs::openNativeFileForRead(Filename);
  345. if (!FdOrErr)
  346. return FdOrErr.takeError();
  347. uint64_t FileSize;
  348. if (auto EC = sys::fs::file_size(Filename, FileSize)) {
  349. return make_error<StringError>(
  350. Twine("Cannot read log from '") + Filename + "'", EC);
  351. }
  352. if (FileSize < 4) {
  353. return make_error<StringError>(
  354. Twine("File '") + Filename + "' too small for XRay.",
  355. std::make_error_code(std::errc::executable_format_error));
  356. }
  357. // Map the opened file into memory and use a StringRef to access it later.
  358. std::error_code EC;
  359. sys::fs::mapped_file_region MappedFile(
  360. *FdOrErr, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0,
  361. EC);
  362. sys::fs::closeFile(*FdOrErr);
  363. if (EC) {
  364. return make_error<StringError>(
  365. Twine("Cannot read log from '") + Filename + "'", EC);
  366. }
  367. auto Data = StringRef(MappedFile.data(), MappedFile.size());
  368. // TODO: Lift the endianness and implementation selection here.
  369. DataExtractor LittleEndianDE(Data, true, 8);
  370. auto TraceOrError = loadTrace(LittleEndianDE, Sort);
  371. if (!TraceOrError) {
  372. DataExtractor BigEndianDE(Data, false, 8);
  373. consumeError(TraceOrError.takeError());
  374. TraceOrError = loadTrace(BigEndianDE, Sort);
  375. }
  376. return TraceOrError;
  377. }
  378. Expected<Trace> llvm::xray::loadTrace(const DataExtractor &DE, bool Sort) {
  379. // Attempt to detect the file type using file magic. We have a slight bias
  380. // towards the binary format, and we do this by making sure that the first 4
  381. // bytes of the binary file is some combination of the following byte
  382. // patterns: (observe the code loading them assumes they're little endian)
  383. //
  384. // 0x01 0x00 0x00 0x00 - version 1, "naive" format
  385. // 0x01 0x00 0x01 0x00 - version 1, "flight data recorder" format
  386. // 0x02 0x00 0x01 0x00 - version 2, "flight data recorder" format
  387. //
  388. // YAML files don't typically have those first four bytes as valid text so we
  389. // try loading assuming YAML if we don't find these bytes.
  390. //
  391. // Only if we can't load either the binary or the YAML format will we yield an
  392. // error.
  393. DataExtractor HeaderExtractor(DE.getData(), DE.isLittleEndian(), 8);
  394. uint64_t OffsetPtr = 0;
  395. uint16_t Version = HeaderExtractor.getU16(&OffsetPtr);
  396. uint16_t Type = HeaderExtractor.getU16(&OffsetPtr);
  397. enum BinaryFormatType { NAIVE_FORMAT = 0, FLIGHT_DATA_RECORDER_FORMAT = 1 };
  398. Trace T;
  399. switch (Type) {
  400. case NAIVE_FORMAT:
  401. if (Version == 1 || Version == 2 || Version == 3) {
  402. if (auto E = loadNaiveFormatLog(DE.getData(), DE.isLittleEndian(),
  403. T.FileHeader, T.Records))
  404. return std::move(E);
  405. } else {
  406. return make_error<StringError>(
  407. Twine("Unsupported version for Basic/Naive Mode logging: ") +
  408. Twine(Version),
  409. std::make_error_code(std::errc::executable_format_error));
  410. }
  411. break;
  412. case FLIGHT_DATA_RECORDER_FORMAT:
  413. if (Version >= 1 && Version <= 5) {
  414. if (auto E = loadFDRLog(DE.getData(), DE.isLittleEndian(), T.FileHeader,
  415. T.Records))
  416. return std::move(E);
  417. } else {
  418. return make_error<StringError>(
  419. Twine("Unsupported version for FDR Mode logging: ") + Twine(Version),
  420. std::make_error_code(std::errc::executable_format_error));
  421. }
  422. break;
  423. default:
  424. if (auto E = loadYAMLLog(DE.getData(), T.FileHeader, T.Records))
  425. return std::move(E);
  426. }
  427. if (Sort)
  428. llvm::stable_sort(T.Records, [&](const XRayRecord &L, const XRayRecord &R) {
  429. return L.TSC < R.TSC;
  430. });
  431. return std::move(T);
  432. }