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