Path: blob/main/contrib/llvm-project/llvm/lib/XRay/Trace.cpp
35234 views
//===- Trace.cpp - XRay Trace Loading implementation. ---------------------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// XRay log reader implementation.9//10//===----------------------------------------------------------------------===//11#include "llvm/XRay/Trace.h"12#include "llvm/ADT/STLExtras.h"13#include "llvm/Support/DataExtractor.h"14#include "llvm/Support/Error.h"15#include "llvm/Support/FileSystem.h"16#include "llvm/XRay/BlockIndexer.h"17#include "llvm/XRay/BlockVerifier.h"18#include "llvm/XRay/FDRRecordConsumer.h"19#include "llvm/XRay/FDRRecordProducer.h"20#include "llvm/XRay/FDRRecords.h"21#include "llvm/XRay/FDRTraceExpander.h"22#include "llvm/XRay/FileHeaderReader.h"23#include "llvm/XRay/YAMLXRayRecord.h"24#include <memory>25#include <vector>2627using namespace llvm;28using namespace llvm::xray;29using llvm::yaml::Input;3031namespace {3233Error loadNaiveFormatLog(StringRef Data, bool IsLittleEndian,34XRayFileHeader &FileHeader,35std::vector<XRayRecord> &Records) {36if (Data.size() < 32)37return make_error<StringError>(38"Not enough bytes for an XRay log.",39std::make_error_code(std::errc::invalid_argument));4041if (Data.size() - 32 == 0 || Data.size() % 32 != 0)42return make_error<StringError>(43"Invalid-sized XRay data.",44std::make_error_code(std::errc::invalid_argument));4546DataExtractor Reader(Data, IsLittleEndian, 8);47uint64_t OffsetPtr = 0;48auto FileHeaderOrError = readBinaryFormatHeader(Reader, OffsetPtr);49if (!FileHeaderOrError)50return FileHeaderOrError.takeError();51FileHeader = std::move(FileHeaderOrError.get());5253size_t NumReservations = llvm::divideCeil(Reader.size() - OffsetPtr, 32U);54Records.reserve(NumReservations);5556// Each record after the header will be 32 bytes, in the following format:57//58// (2) uint16 : record type59// (1) uint8 : cpu id60// (1) uint8 : type61// (4) sint32 : function id62// (8) uint64 : tsc63// (4) uint32 : thread id64// (4) uint32 : process id65// (8) - : padding66while (Reader.isValidOffset(OffsetPtr)) {67if (!Reader.isValidOffsetForDataOfSize(OffsetPtr, 32))68return createStringError(69std::make_error_code(std::errc::executable_format_error),70"Not enough bytes to read a full record at offset %" PRId64 ".",71OffsetPtr);72auto PreReadOffset = OffsetPtr;73auto RecordType = Reader.getU16(&OffsetPtr);74if (OffsetPtr == PreReadOffset)75return createStringError(76std::make_error_code(std::errc::executable_format_error),77"Failed reading record type at offset %" PRId64 ".", OffsetPtr);7879switch (RecordType) {80case 0: { // Normal records.81Records.emplace_back();82auto &Record = Records.back();83Record.RecordType = RecordType;8485PreReadOffset = OffsetPtr;86Record.CPU = Reader.getU8(&OffsetPtr);87if (OffsetPtr == PreReadOffset)88return createStringError(89std::make_error_code(std::errc::executable_format_error),90"Failed reading CPU field at offset %" PRId64 ".", OffsetPtr);9192PreReadOffset = OffsetPtr;93auto Type = Reader.getU8(&OffsetPtr);94if (OffsetPtr == PreReadOffset)95return createStringError(96std::make_error_code(std::errc::executable_format_error),97"Failed reading record type field at offset %" PRId64 ".",98OffsetPtr);99100switch (Type) {101case 0:102Record.Type = RecordTypes::ENTER;103break;104case 1:105Record.Type = RecordTypes::EXIT;106break;107case 2:108Record.Type = RecordTypes::TAIL_EXIT;109break;110case 3:111Record.Type = RecordTypes::ENTER_ARG;112break;113default:114return createStringError(115std::make_error_code(std::errc::executable_format_error),116"Unknown record type '%d' at offset %" PRId64 ".", Type, OffsetPtr);117}118119PreReadOffset = OffsetPtr;120Record.FuncId = Reader.getSigned(&OffsetPtr, sizeof(int32_t));121if (OffsetPtr == PreReadOffset)122return createStringError(123std::make_error_code(std::errc::executable_format_error),124"Failed reading function id field at offset %" PRId64 ".",125OffsetPtr);126127PreReadOffset = OffsetPtr;128Record.TSC = Reader.getU64(&OffsetPtr);129if (OffsetPtr == PreReadOffset)130return createStringError(131std::make_error_code(std::errc::executable_format_error),132"Failed reading TSC field at offset %" PRId64 ".", OffsetPtr);133134PreReadOffset = OffsetPtr;135Record.TId = Reader.getU32(&OffsetPtr);136if (OffsetPtr == PreReadOffset)137return createStringError(138std::make_error_code(std::errc::executable_format_error),139"Failed reading thread id field at offset %" PRId64 ".", OffsetPtr);140141PreReadOffset = OffsetPtr;142Record.PId = Reader.getU32(&OffsetPtr);143if (OffsetPtr == PreReadOffset)144return createStringError(145std::make_error_code(std::errc::executable_format_error),146"Failed reading process id at offset %" PRId64 ".", OffsetPtr);147148break;149}150case 1: { // Arg payload record.151auto &Record = Records.back();152153// We skip the next two bytes of the record, because we don't need the154// type and the CPU record for arg payloads.155OffsetPtr += 2;156PreReadOffset = OffsetPtr;157int32_t FuncId = Reader.getSigned(&OffsetPtr, sizeof(int32_t));158if (OffsetPtr == PreReadOffset)159return createStringError(160std::make_error_code(std::errc::executable_format_error),161"Failed reading function id field at offset %" PRId64 ".",162OffsetPtr);163164PreReadOffset = OffsetPtr;165auto TId = Reader.getU32(&OffsetPtr);166if (OffsetPtr == PreReadOffset)167return createStringError(168std::make_error_code(std::errc::executable_format_error),169"Failed reading thread id field at offset %" PRId64 ".", OffsetPtr);170171PreReadOffset = OffsetPtr;172auto PId = Reader.getU32(&OffsetPtr);173if (OffsetPtr == PreReadOffset)174return createStringError(175std::make_error_code(std::errc::executable_format_error),176"Failed reading process id field at offset %" PRId64 ".",177OffsetPtr);178179// Make a check for versions above 3 for the Pid field180if (Record.FuncId != FuncId || Record.TId != TId ||181(FileHeader.Version >= 3 ? Record.PId != PId : false))182return createStringError(183std::make_error_code(std::errc::executable_format_error),184"Corrupted log, found arg payload following non-matching "185"function+thread record. Record for function %d != %d at offset "186"%" PRId64 ".",187Record.FuncId, FuncId, OffsetPtr);188189PreReadOffset = OffsetPtr;190auto Arg = Reader.getU64(&OffsetPtr);191if (OffsetPtr == PreReadOffset)192return createStringError(193std::make_error_code(std::errc::executable_format_error),194"Failed reading argument payload at offset %" PRId64 ".",195OffsetPtr);196197Record.CallArgs.push_back(Arg);198break;199}200default:201return createStringError(202std::make_error_code(std::errc::executable_format_error),203"Unknown record type '%d' at offset %" PRId64 ".", RecordType,204OffsetPtr);205}206// Advance the offset pointer enough bytes to align to 32-byte records for207// basic mode logs.208OffsetPtr += 8;209}210return Error::success();211}212213/// Reads a log in FDR mode for version 1 of this binary format. FDR mode is214/// defined as part of the compiler-rt project in xray_fdr_logging.h, and such215/// a log consists of the familiar 32 bit XRayHeader, followed by sequences of216/// of interspersed 16 byte Metadata Records and 8 byte Function Records.217///218/// The following is an attempt to document the grammar of the format, which is219/// parsed by this function for little-endian machines. Since the format makes220/// use of BitFields, when we support big-endian architectures, we will need to221/// adjust not only the endianness parameter to llvm's RecordExtractor, but also222/// the bit twiddling logic, which is consistent with the little-endian223/// convention that BitFields within a struct will first be packed into the224/// least significant bits the address they belong to.225///226/// We expect a format complying with the grammar in the following pseudo-EBNF227/// in Version 1 of the FDR log.228///229/// FDRLog: XRayFileHeader ThreadBuffer*230/// XRayFileHeader: 32 bytes to identify the log as FDR with machine metadata.231/// Includes BufferSize232/// ThreadBuffer: NewBuffer WallClockTime NewCPUId FunctionSequence EOB233/// BufSize: 8 byte unsigned integer indicating how large the buffer is.234/// NewBuffer: 16 byte metadata record with Thread Id.235/// WallClockTime: 16 byte metadata record with human readable time.236/// Pid: 16 byte metadata record with Pid237/// NewCPUId: 16 byte metadata record with CPUId and a 64 bit TSC reading.238/// EOB: 16 byte record in a thread buffer plus mem garbage to fill BufSize.239/// FunctionSequence: NewCPUId | TSCWrap | FunctionRecord240/// TSCWrap: 16 byte metadata record with a full 64 bit TSC reading.241/// FunctionRecord: 8 byte record with FunctionId, entry/exit, and TSC delta.242///243/// In Version 2, we make the following changes:244///245/// ThreadBuffer: BufferExtents NewBuffer WallClockTime NewCPUId246/// FunctionSequence247/// BufferExtents: 16 byte metdata record describing how many usable bytes are248/// in the buffer. This is measured from the start of the buffer249/// and must always be at least 48 (bytes).250///251/// In Version 3, we make the following changes:252///253/// ThreadBuffer: BufferExtents NewBuffer WallClockTime Pid NewCPUId254/// FunctionSequence255/// EOB: *deprecated*256///257/// In Version 4, we make the following changes:258///259/// CustomEventRecord now includes the CPU data.260///261/// In Version 5, we make the following changes:262///263/// CustomEventRecord and TypedEventRecord now use TSC delta encoding similar to264/// what FunctionRecord instances use, and we no longer need to include the CPU265/// id in the CustomEventRecord.266///267Error loadFDRLog(StringRef Data, bool IsLittleEndian,268XRayFileHeader &FileHeader, std::vector<XRayRecord> &Records) {269270if (Data.size() < 32)271return createStringError(std::make_error_code(std::errc::invalid_argument),272"Not enough bytes for an XRay FDR log.");273DataExtractor DE(Data, IsLittleEndian, 8);274275uint64_t OffsetPtr = 0;276auto FileHeaderOrError = readBinaryFormatHeader(DE, OffsetPtr);277if (!FileHeaderOrError)278return FileHeaderOrError.takeError();279FileHeader = std::move(FileHeaderOrError.get());280281// First we load the records into memory.282std::vector<std::unique_ptr<Record>> FDRRecords;283284{285FileBasedRecordProducer P(FileHeader, DE, OffsetPtr);286LogBuilderConsumer C(FDRRecords);287while (DE.isValidOffsetForDataOfSize(OffsetPtr, 1)) {288auto R = P.produce();289if (!R)290return R.takeError();291if (auto E = C.consume(std::move(R.get())))292return E;293}294}295296// Next we index the records into blocks.297BlockIndexer::Index Index;298{299BlockIndexer Indexer(Index);300for (auto &R : FDRRecords)301if (auto E = R->apply(Indexer))302return E;303if (auto E = Indexer.flush())304return E;305}306307// Then we verify the consistency of the blocks.308{309for (auto &PTB : Index) {310auto &Blocks = PTB.second;311for (auto &B : Blocks) {312BlockVerifier Verifier;313for (auto *R : B.Records)314if (auto E = R->apply(Verifier))315return E;316if (auto E = Verifier.verify())317return E;318}319}320}321322// 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{328for (auto &PTB : Index) {329auto &Blocks = PTB.second;330llvm::sort(Blocks, [](const BlockIndexer::Block &L,331const BlockIndexer::Block &R) {332return (L.WallclockTime->seconds() < R.WallclockTime->seconds() &&333L.WallclockTime->nanos() < R.WallclockTime->nanos());334});335auto Adder = [&](const XRayRecord &R) { Records.push_back(R); };336TraceExpander Expander(Adder, FileHeader.Version);337for (auto &B : Blocks) {338for (auto *R : B.Records)339if (auto E = R->apply(Expander))340return E;341}342if (auto E = Expander.flush())343return E;344}345}346347return Error::success();348}349350Error loadYAMLLog(StringRef Data, XRayFileHeader &FileHeader,351std::vector<XRayRecord> &Records) {352YAMLXRayTrace Trace;353Input In(Data);354In >> Trace;355if (In.error())356return make_error<StringError>("Failed loading YAML Data.", In.error());357358FileHeader.Version = Trace.Header.Version;359FileHeader.Type = Trace.Header.Type;360FileHeader.ConstantTSC = Trace.Header.ConstantTSC;361FileHeader.NonstopTSC = Trace.Header.NonstopTSC;362FileHeader.CycleFrequency = Trace.Header.CycleFrequency;363364if (FileHeader.Version != 1)365return make_error<StringError>(366Twine("Unsupported XRay file version: ") + Twine(FileHeader.Version),367std::make_error_code(std::errc::invalid_argument));368369Records.clear();370std::transform(Trace.Records.begin(), Trace.Records.end(),371std::back_inserter(Records), [&](const YAMLXRayRecord &R) {372return XRayRecord{R.RecordType, R.CPU, R.Type,373R.FuncId, R.TSC, R.TId,374R.PId, R.CallArgs, R.Data};375});376return Error::success();377}378} // namespace379380Expected<Trace> llvm::xray::loadTraceFile(StringRef Filename, bool Sort) {381Expected<sys::fs::file_t> FdOrErr = sys::fs::openNativeFileForRead(Filename);382if (!FdOrErr)383return FdOrErr.takeError();384385uint64_t FileSize;386if (auto EC = sys::fs::file_size(Filename, FileSize)) {387return make_error<StringError>(388Twine("Cannot read log from '") + Filename + "'", EC);389}390if (FileSize < 4) {391return make_error<StringError>(392Twine("File '") + Filename + "' too small for XRay.",393std::make_error_code(std::errc::executable_format_error));394}395396// Map the opened file into memory and use a StringRef to access it later.397std::error_code EC;398sys::fs::mapped_file_region MappedFile(399*FdOrErr, sys::fs::mapped_file_region::mapmode::readonly, FileSize, 0,400EC);401sys::fs::closeFile(*FdOrErr);402if (EC) {403return make_error<StringError>(404Twine("Cannot read log from '") + Filename + "'", EC);405}406auto Data = StringRef(MappedFile.data(), MappedFile.size());407408// TODO: Lift the endianness and implementation selection here.409DataExtractor LittleEndianDE(Data, true, 8);410auto TraceOrError = loadTrace(LittleEndianDE, Sort);411if (!TraceOrError) {412DataExtractor BigEndianDE(Data, false, 8);413consumeError(TraceOrError.takeError());414TraceOrError = loadTrace(BigEndianDE, Sort);415}416return TraceOrError;417}418419Expected<Trace> llvm::xray::loadTrace(const DataExtractor &DE, bool Sort) {420// Attempt to detect the file type using file magic. We have a slight bias421// towards the binary format, and we do this by making sure that the first 4422// bytes of the binary file is some combination of the following byte423// patterns: (observe the code loading them assumes they're little endian)424//425// 0x01 0x00 0x00 0x00 - version 1, "naive" format426// 0x01 0x00 0x01 0x00 - version 1, "flight data recorder" format427// 0x02 0x00 0x01 0x00 - version 2, "flight data recorder" format428//429// YAML files don't typically have those first four bytes as valid text so we430// try loading assuming YAML if we don't find these bytes.431//432// Only if we can't load either the binary or the YAML format will we yield an433// error.434DataExtractor HeaderExtractor(DE.getData(), DE.isLittleEndian(), 8);435uint64_t OffsetPtr = 0;436uint16_t Version = HeaderExtractor.getU16(&OffsetPtr);437uint16_t Type = HeaderExtractor.getU16(&OffsetPtr);438439enum BinaryFormatType { NAIVE_FORMAT = 0, FLIGHT_DATA_RECORDER_FORMAT = 1 };440441Trace T;442switch (Type) {443case NAIVE_FORMAT:444if (Version == 1 || Version == 2 || Version == 3) {445if (auto E = loadNaiveFormatLog(DE.getData(), DE.isLittleEndian(),446T.FileHeader, T.Records))447return std::move(E);448} else {449return make_error<StringError>(450Twine("Unsupported version for Basic/Naive Mode logging: ") +451Twine(Version),452std::make_error_code(std::errc::executable_format_error));453}454break;455case FLIGHT_DATA_RECORDER_FORMAT:456if (Version >= 1 && Version <= 5) {457if (auto E = loadFDRLog(DE.getData(), DE.isLittleEndian(), T.FileHeader,458T.Records))459return std::move(E);460} else {461return make_error<StringError>(462Twine("Unsupported version for FDR Mode logging: ") + Twine(Version),463std::make_error_code(std::errc::executable_format_error));464}465break;466default:467if (auto E = loadYAMLLog(DE.getData(), T.FileHeader, T.Records))468return std::move(E);469}470471if (Sort)472llvm::stable_sort(T.Records, [&](const XRayRecord &L, const XRayRecord &R) {473return L.TSC < R.TSC;474});475476return std::move(T);477}478479480