brintos

brintos / llvm-project-archived public Read only

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