Index: llvm/trunk/include/llvm/XRay/FDRLogBuilder.h =================================================================== --- llvm/trunk/include/llvm/XRay/FDRLogBuilder.h +++ llvm/trunk/include/llvm/XRay/FDRLogBuilder.h @@ -0,0 +1,41 @@ +//===- FDRLogBuilder.h - XRay FDR Log Building Utility --------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_INCLUDE_LLVM_XRAY_FDRLOGBUILDER_H_ +#define LLVM_INCLUDE_LLVM_XRAY_FDRLOGBUILDER_H_ + +#include "llvm/XRay/FDRRecords.h" + +namespace llvm { +namespace xray { + +/// The LogBuilder class allows for creating ad-hoc collections of records +/// through the `add<...>(...)` function. An example use of this API is in +/// crafting arbitrary sequences of records: +/// +/// auto Records = LogBuilder() +/// .add(256) +/// .add(1) +/// .consume(); +/// +class LogBuilder { + std::vector> Records; + +public: + template LogBuilder &add(T &&... A) { + Records.emplace_back(new R(std::forward(A)...)); + return *this; + } + + std::vector> consume() { return std::move(Records); } +}; + +} // namespace xray +} // namespace llvm + +#endif // LLVM_INCLUDE_LLVM_XRAY_FDRLOGBUILDER_H_ Index: llvm/trunk/include/llvm/XRay/FDRRecords.h =================================================================== --- llvm/trunk/include/llvm/XRay/FDRRecords.h +++ llvm/trunk/include/llvm/XRay/FDRRecords.h @@ -0,0 +1,292 @@ +//===- FDRRecords.h - XRay Flight Data Recorder Mode Records --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Define types and operations on these types that represent the different kinds +// of records we encounter in XRay flight data recorder mode traces. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_LIB_XRAY_FDRRECORDS_H_ +#define LLVM_LIB_XRAY_FDRRECORDS_H_ + +#include "llvm/Support/DataExtractor.h" +#include "llvm/Support/Error.h" +#include "llvm/XRay/XRayRecord.h" +#include + +namespace llvm { +namespace xray { + +class RecordVisitor; +class RecordInitializer; + +class Record { +protected: + enum class Type { + Unknown, + Function, + Metadata, + }; + +public: + Record(const Record &) = delete; + Record(Record &&) = delete; + Record &operator=(const Record &) = delete; + Record &operator=(Record &&) = delete; + Record() = default; + + virtual Type type() const = 0; + + // Each Record should be able to apply an abstract visitor, and choose the + // appropriate function in the visitor to invoke, given its own type. + virtual Error apply(RecordVisitor &V) = 0; + + virtual ~Record() = default; +}; + +class MetadataRecord : public Record { +protected: + static constexpr int kMetadataBodySize = 15; + friend class RecordInitializer; + +public: + enum class MetadataType : unsigned { + Unknown, + BufferExtents, + WallClockTime, + NewCPUId, + TSCWrap, + CustomEvent, + CallArg, + PIDEntry, + NewBuffer, + EndOfBuffer, + }; + + Type type() const override { return Type::Metadata; } + + // All metadata records must know to provide the type of their open + // metadata record. + virtual MetadataType metadataType() const = 0; + + virtual ~MetadataRecord() = default; +}; + +// What follows are specific Metadata record types which encapsulate the +// information associated with specific metadata record types in an FDR mode +// log. +class BufferExtents : public MetadataRecord { + uint64_t Size = 0; + friend class RecordInitializer; + +public: + BufferExtents() = default; + explicit BufferExtents(uint64_t S) : MetadataRecord(), Size(S) {} + + MetadataType metadataType() const override { + return MetadataType::BufferExtents; + } + + uint64_t size() const { return Size; } + + Error apply(RecordVisitor &V) override; +}; + +class WallclockRecord : public MetadataRecord { + uint64_t Seconds = 0; + uint32_t Nanos = 0; + friend class RecordInitializer; + +public: + WallclockRecord() = default; + explicit WallclockRecord(uint64_t S, uint32_t N) + : MetadataRecord(), Seconds(S), Nanos(N) {} + + MetadataType metadataType() const override { + return MetadataType::WallClockTime; + } + + uint64_t seconds() const { return Seconds; } + uint32_t nanos() const { return Nanos; } + + Error apply(RecordVisitor &V) override; +}; + +class NewCPUIDRecord : public MetadataRecord { + uint16_t CPUId = 0; + friend class RecordInitializer; + +public: + NewCPUIDRecord() = default; + explicit NewCPUIDRecord(uint16_t C) : MetadataRecord(), CPUId(C) {} + + MetadataType metadataType() const override { return MetadataType::NewCPUId; } + + uint16_t cpuid() const { return CPUId; } + + Error apply(RecordVisitor &V) override; +}; + +class TSCWrapRecord : public MetadataRecord { + uint64_t BaseTSC = 0; + friend class RecordInitializer; + +public: + TSCWrapRecord() = default; + explicit TSCWrapRecord(uint64_t B) : MetadataRecord(), BaseTSC(B) {} + + MetadataType metadataType() const override { return MetadataType::TSCWrap; } + + uint64_t tsc() const { return BaseTSC; } + + Error apply(RecordVisitor &V) override; +}; + +class CustomEventRecord : public MetadataRecord { + int32_t Size = 0; + uint64_t TSC = 0; + std::string Data{}; + friend class RecordInitializer; + +public: + CustomEventRecord() = default; + explicit CustomEventRecord(uint64_t S, uint64_t T, std::string D) + : MetadataRecord(), Size(S), TSC(T), Data(std::move(D)) {} + + MetadataType metadataType() const override { + return MetadataType::CustomEvent; + } + + int32_t size() const { return Size; } + uint64_t tsc() const { return TSC; } + StringRef data() const { return Data; } + + Error apply(RecordVisitor &V) override; +}; + +class CallArgRecord : public MetadataRecord { + uint64_t Arg; + friend class RecordInitializer; + +public: + CallArgRecord() = default; + explicit CallArgRecord(uint64_t A) : MetadataRecord(), Arg(A) {} + + MetadataType metadataType() const override { return MetadataType::CallArg; } + + uint64_t arg() const { return Arg; } + + Error apply(RecordVisitor &V) override; +}; + +class PIDRecord : public MetadataRecord { + uint64_t PID = 0; + friend class RecordInitializer; + +public: + PIDRecord() = default; + explicit PIDRecord(uint64_t P) : MetadataRecord(), PID(P) {} + + MetadataType metadataType() const override { return MetadataType::PIDEntry; } + + uint64_t pid() const { return PID; } + + Error apply(RecordVisitor &V) override; +}; + +class NewBufferRecord : public MetadataRecord { + int32_t TID = 0; + friend class RecordInitializer; + +public: + NewBufferRecord() = default; + explicit NewBufferRecord(int32_t T) : MetadataRecord(), TID(T) {} + + MetadataType metadataType() const override { return MetadataType::NewBuffer; } + + int32_t tid() const { return TID; } + + Error apply(RecordVisitor &V) override; +}; + +class EndBufferRecord : public MetadataRecord { +public: + EndBufferRecord() = default; + + MetadataType metadataType() const override { + return MetadataType::EndOfBuffer; + } + + Error apply(RecordVisitor &V) override; +}; + +class FunctionRecord : public Record { + RecordTypes Kind; + int32_t FuncId; + uint32_t Delta; + friend class RecordInitializer; + + static constexpr unsigned kFunctionRecordSize = 8; + +public: + FunctionRecord() = default; + explicit FunctionRecord(RecordTypes K, int32_t F, uint32_t D) + : Record(), Kind(K), FuncId(F), Delta(D) {} + + Type type() const override { return Type::Function; } + + // A function record is a concrete record type which has a number of common + // properties. + RecordTypes recordType() const { return Kind; } + int32_t functionId() const { return FuncId; } + uint64_t delta() const { return Delta; } + + Error apply(RecordVisitor &V) override; +}; + +class RecordVisitor { +public: + virtual ~RecordVisitor() = default; + + // Support all specific kinds of records: + virtual Error visit(BufferExtents &) = 0; + virtual Error visit(WallclockRecord &) = 0; + virtual Error visit(NewCPUIDRecord &) = 0; + virtual Error visit(TSCWrapRecord &) = 0; + virtual Error visit(CustomEventRecord &) = 0; + virtual Error visit(CallArgRecord &) = 0; + virtual Error visit(PIDRecord &) = 0; + virtual Error visit(NewBufferRecord &) = 0; + virtual Error visit(EndBufferRecord &) = 0; + virtual Error visit(FunctionRecord &) = 0; +}; + +class RecordInitializer : public RecordVisitor { + DataExtractor &E; + uint32_t &OffsetPtr; + +public: + explicit RecordInitializer(DataExtractor &DE, uint32_t &OP) + : RecordVisitor(), E(DE), OffsetPtr(OP) {} + + Error visit(BufferExtents &) override; + Error visit(WallclockRecord &) override; + Error visit(NewCPUIDRecord &) override; + Error visit(TSCWrapRecord &) override; + Error visit(CustomEventRecord &) override; + Error visit(CallArgRecord &) override; + Error visit(PIDRecord &) override; + Error visit(NewBufferRecord &) override; + Error visit(EndBufferRecord &) override; + Error visit(FunctionRecord &) override; +}; + +} // namespace xray +} // namespace llvm + +#endif // LLVM_LIB_XRAY_FDRRECORDS_H_ Index: llvm/trunk/include/llvm/XRay/FDRTraceWriter.h =================================================================== --- llvm/trunk/include/llvm/XRay/FDRTraceWriter.h +++ llvm/trunk/include/llvm/XRay/FDRTraceWriter.h @@ -0,0 +1,53 @@ +//===- FDRTraceWriter.h - XRay FDR Trace Writer -----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Test a utility that can write out XRay FDR Mode formatted trace files. +// +//===----------------------------------------------------------------------===// +#ifndef LLVM_INCLUDE_LLVM_XRAY_FDRTRACEWRITER_H_ +#define LLVM_INCLUDE_LLVM_XRAY_FDRTRACEWRITER_H_ + +#include "llvm/Support/raw_ostream.h" +#include "llvm/XRay/FDRRecords.h" +#include "llvm/XRay/XRayRecord.h" + +namespace llvm { +namespace xray { + +/// The FDRTraceWriter allows us to hand-craft an XRay Flight Data Recorder +/// (FDR) mode log file. This is used primarily for testing, generating +/// sequences of FDR records that can be read/processed. It can also be used to +/// generate various kinds of execution traces without using the XRay runtime. +/// Note that this writer does not do any validation, but uses the types of +/// records defined in the FDRRecords.h file. +class FDRTraceWriter : public RecordVisitor { +public: + // Construct an FDRTraceWriter associated with an output stream. + explicit FDRTraceWriter(raw_ostream &O, const XRayFileHeader &H); + ~FDRTraceWriter(); + + Error visit(BufferExtents &) override; + Error visit(WallclockRecord &) override; + Error visit(NewCPUIDRecord &) override; + Error visit(TSCWrapRecord &) override; + Error visit(CustomEventRecord &) override; + Error visit(CallArgRecord &) override; + Error visit(PIDRecord &) override; + Error visit(NewBufferRecord &) override; + Error visit(EndBufferRecord &) override; + Error visit(FunctionRecord &) override; + +private: + raw_ostream &OS; +}; + +} // namespace xray +} // namespace llvm + +#endif // LLVM_INCLUDE_LLVM_XRAY_FDRTRACEWRITER_H_ Index: llvm/trunk/lib/XRay/CMakeLists.txt =================================================================== --- llvm/trunk/lib/XRay/CMakeLists.txt +++ llvm/trunk/lib/XRay/CMakeLists.txt @@ -1,7 +1,10 @@ add_llvm_library(LLVMXRay + FDRRecords.cpp + FDRTraceWriter.cpp FileHeaderReader.cpp InstrumentationMap.cpp - Profile.cpp + Profile.cpp + RecordInitializer.cpp Trace.cpp ADDITIONAL_HEADER_DIRS Index: llvm/trunk/lib/XRay/FDRRecords.cpp =================================================================== --- llvm/trunk/lib/XRay/FDRRecords.cpp +++ llvm/trunk/lib/XRay/FDRRecords.cpp @@ -0,0 +1,31 @@ +//===- FDRRecords.cpp - XRay Flight Data Recorder Mode Records -----------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Define types and operations on these types that represent the different kinds +// of records we encounter in XRay flight data recorder mode traces. +// +//===----------------------------------------------------------------------===// +#include "llvm/XRay/FDRRecords.h" + +namespace llvm { +namespace xray { + +Error BufferExtents::apply(RecordVisitor &V) { return V.visit(*this); } +Error WallclockRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error NewCPUIDRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error TSCWrapRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error CustomEventRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error CallArgRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error PIDRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error NewBufferRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error EndBufferRecord::apply(RecordVisitor &V) { return V.visit(*this); } +Error FunctionRecord::apply(RecordVisitor &V) { return V.visit(*this); } + +} // namespace xray +} // namespace llvm Index: llvm/trunk/lib/XRay/FDRTraceWriter.cpp =================================================================== --- llvm/trunk/lib/XRay/FDRTraceWriter.cpp +++ llvm/trunk/lib/XRay/FDRTraceWriter.cpp @@ -0,0 +1,145 @@ +//===- FDRTraceWriter.cpp - XRay FDR Trace Writer ---------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Test a utility that can write out XRay FDR Mode formatted trace files. +// +//===----------------------------------------------------------------------===// +#include "llvm/XRay/FDRTraceWriter.h" +#include + +namespace llvm { +namespace xray { + +namespace { + +struct alignas(32) FileHeader { + uint16_t Version; + uint16_t Type; + bool ConstantTSC : 1; + bool NonstopTSC : 1; + alignas(8) uint64_t CycleFrequency; + char FreeForm[16]; +}; + +struct MetadataBlob { + uint8_t Type : 1; + uint8_t RecordKind : 7; + char Data[15]; +} __attribute__((packed)); + +struct FunctionDeltaBlob { + uint8_t Type : 1; + uint8_t RecordKind : 3; + int FuncId : 28; + uint32_t TSCDelta; +} __attribute__((packed)); + +template struct IndexedMemcpy { + template < + class Tuple, + typename std::enable_if< + (Index < + std::tuple_size::type>::value), + int>::type = 0> + static void Copy(char *Dest, Tuple &&T) { + auto Next = static_cast(std::memcpy( + Dest, reinterpret_cast(&std::get(T)), + sizeof(std::get(T)))) + + sizeof(std::get(T)); + IndexedMemcpy::Copy(Next, T); + } + + template < + class Tuple, + typename std::enable_if< + (Index >= + std::tuple_size::type>::value), + int>::type = 0> + static void Copy(char *, Tuple &&) {} +}; + +template +Error writeMetadata(raw_ostream &OS, Data... Ds) { + MetadataBlob B; + B.Type = 1; + B.RecordKind = Kind; + IndexedMemcpy<0>::Copy(B.Data, std::make_tuple(Ds...)); + OS.write(reinterpret_cast(&B), sizeof(MetadataBlob)); + return Error::success(); +} + +} // namespace + +FDRTraceWriter::FDRTraceWriter(raw_ostream &O, const XRayFileHeader &H) + : OS(O) { + // We need to re-construct a header, by writing the fields we care about for + // traces, in the format that the runtime would have written. + FileHeader Raw; + Raw.Version = H.Version; + Raw.Type = H.Type; + Raw.ConstantTSC = H.ConstantTSC; + Raw.NonstopTSC = H.NonstopTSC; + Raw.CycleFrequency = H.CycleFrequency; + memcpy(&Raw.FreeForm, H.FreeFormData, 16); + OS.write(reinterpret_cast(&Raw), sizeof(XRayFileHeader)); +} + +FDRTraceWriter::~FDRTraceWriter() {} + +Error FDRTraceWriter::visit(BufferExtents &R) { + return writeMetadata<7u>(OS, R.size()); +} + +Error FDRTraceWriter::visit(WallclockRecord &R) { + return writeMetadata<4u>(OS, R.seconds(), R.nanos()); +} + +Error FDRTraceWriter::visit(NewCPUIDRecord &R) { + return writeMetadata<2u>(OS, R.cpuid()); +} + +Error FDRTraceWriter::visit(TSCWrapRecord &R) { + return writeMetadata<3u>(OS, R.tsc()); +} + +Error FDRTraceWriter::visit(CustomEventRecord &R) { + if (auto E = writeMetadata<5u>(OS, R.size(), R.tsc())) + return E; + OS.write(R.data().data(), R.data().size()); + return Error::success(); +} + +Error FDRTraceWriter::visit(CallArgRecord &R) { + return writeMetadata<6u>(OS, R.arg()); +} + +Error FDRTraceWriter::visit(PIDRecord &R) { + return writeMetadata<9u>(OS, R.pid()); +} + +Error FDRTraceWriter::visit(NewBufferRecord &R) { + return writeMetadata<0u>(OS, R.tid()); +} + +Error FDRTraceWriter::visit(EndBufferRecord &R) { + return writeMetadata<1u>(OS, 0); +} + +Error FDRTraceWriter::visit(FunctionRecord &R) { + FunctionDeltaBlob B; + B.Type = 0; + B.RecordKind = static_cast(R.recordType()); + B.FuncId = R.functionId(); + B.TSCDelta = R.delta(); + OS.write(reinterpret_cast(&B), sizeof(FunctionDeltaBlob)); + return Error::success(); +} + +} // namespace xray +} // namespace llvm Index: llvm/trunk/lib/XRay/RecordInitializer.cpp =================================================================== --- llvm/trunk/lib/XRay/RecordInitializer.cpp +++ llvm/trunk/lib/XRay/RecordInitializer.cpp @@ -0,0 +1,247 @@ +//===- FDRRecordProducer.cpp - XRay FDR Mode Record Producer --------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +#include "llvm/XRay/FDRRecords.h" + +namespace llvm { +namespace xray { + +Error RecordInitializer::visit(BufferExtents &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, sizeof(uint64_t))) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a buffer extent (%d).", + OffsetPtr); + + auto PreReadOffset = OffsetPtr; + R.Size = E.getU64(&OffsetPtr); + if (PreReadOffset == OffsetPtr) + return createStringError(std::make_error_code(std::errc::bad_message), + "Cannot read buffer extent at offset %d.", + OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - PreReadOffset); + return Error::success(); +} + +Error RecordInitializer::visit(WallclockRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a wallclock record (%d).", + OffsetPtr); + auto BeginOffset = OffsetPtr; + auto PreReadOffset = OffsetPtr; + R.Seconds = E.getU64(&OffsetPtr); + if (OffsetPtr == PreReadOffset) + return createStringError( + std::make_error_code(std::errc::bad_message), + "Cannot read wall clock 'seconds' field at offset %d.", OffsetPtr); + + PreReadOffset = OffsetPtr; + R.Nanos = E.getU32(&OffsetPtr); + if (OffsetPtr == PreReadOffset) + return createStringError( + std::make_error_code(std::errc::bad_message), + "Cannot read wall clock 'nanos' field at offset %d.", OffsetPtr); + + // Align to metadata record size boundary. + assert(OffsetPtr - BeginOffset <= MetadataRecord::kMetadataBodySize); + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - BeginOffset); + return Error::success(); +} + +Error RecordInitializer::visit(NewCPUIDRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a new cpu id record (%d).", + OffsetPtr); + auto PreReadOffset = OffsetPtr; + R.CPUId = E.getU16(&OffsetPtr); + if (OffsetPtr == PreReadOffset) + return createStringError(std::make_error_code(std::errc::bad_message), + "Cannot read CPU id at offset %d.", OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - PreReadOffset); + return Error::success(); +} + +Error RecordInitializer::visit(TSCWrapRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a new TSC wrap record (%d).", + OffsetPtr); + + auto PreReadOffset = OffsetPtr; + R.BaseTSC = E.getU64(&OffsetPtr); + if (PreReadOffset == OffsetPtr) + return createStringError(std::make_error_code(std::errc::bad_message), + "Cannot read TSC wrap record at offset %d.", + OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - PreReadOffset); + return Error::success(); +} + +Error RecordInitializer::visit(CustomEventRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a custom event record (%d).", + OffsetPtr); + + auto BeginOffset = OffsetPtr; + auto PreReadOffset = OffsetPtr; + R.Size = E.getSigned(&OffsetPtr, sizeof(int32_t)); + if (PreReadOffset == OffsetPtr) + return createStringError( + std::make_error_code(std::errc::bad_message), + "Cannot read a custom event record size field offset %d.", OffsetPtr); + + PreReadOffset = OffsetPtr; + R.TSC = E.getU64(&OffsetPtr); + if (PreReadOffset == OffsetPtr) + return createStringError( + std::make_error_code(std::errc::bad_message), + "Cannot read a custom event TSC field at offset %d.", OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - BeginOffset); + + // Next we read in a fixed chunk of data from the given offset. + if (!E.isValidOffsetForDataOfSize(OffsetPtr, R.Size)) + return createStringError( + std::make_error_code(std::errc::bad_address), + "Cannot read %d bytes of custom event data from offset %d.", R.Size, + OffsetPtr); + + std::vector Buffer; + Buffer.resize(R.Size); + if (E.getU8(&OffsetPtr, Buffer.data(), R.Size) != Buffer.data()) + return createStringError( + std::make_error_code(std::errc::bad_message), + "Failed reading data into buffer of size %d at offset %d.", R.Size, + OffsetPtr); + R.Data.assign(Buffer.begin(), Buffer.end()); + return Error::success(); +} + +Error RecordInitializer::visit(CallArgRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a call argument record (%d).", + OffsetPtr); + + auto PreReadOffset = OffsetPtr; + R.Arg = E.getU64(&OffsetPtr); + if (PreReadOffset == OffsetPtr) + return createStringError(std::make_error_code(std::errc::bad_message), + "Cannot read a call arg record at offset %d.", + OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - PreReadOffset); + return Error::success(); +} + +Error RecordInitializer::visit(PIDRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a process ID record (%d).", + OffsetPtr); + + auto PreReadOffset = OffsetPtr; + R.PID = E.getU64(&OffsetPtr); + if (PreReadOffset == OffsetPtr) + return createStringError(std::make_error_code(std::errc::bad_message), + "Cannot read a process ID record at offset %d.", + OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - PreReadOffset); + return Error::success(); +} + +Error RecordInitializer::visit(NewBufferRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a new buffer record (%d).", + OffsetPtr); + + auto PreReadOffset = OffsetPtr; + R.TID = E.getSigned(&OffsetPtr, sizeof(int32_t)); + if (PreReadOffset == OffsetPtr) + return createStringError(std::make_error_code(std::errc::bad_message), + "Cannot read a new buffer record at offset %d.", + OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize - (OffsetPtr - PreReadOffset); + return Error::success(); +} + +Error RecordInitializer::visit(EndBufferRecord &R) { + if (!E.isValidOffsetForDataOfSize(OffsetPtr, + MetadataRecord::kMetadataBodySize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for an end-of-buffer record (%d).", + OffsetPtr); + + OffsetPtr += MetadataRecord::kMetadataBodySize; + return Error::success(); +} + +Error RecordInitializer::visit(FunctionRecord &R) { + // For function records, we need to retreat one byte back to read a full + // unsigned 32-bit value. The first four bytes will have the following + // layout: + // + // bit 0 : function record indicator (must be 0) + // bits 1..3 : function record type + // bits 4..32 : function id + // + if (OffsetPtr == 0 || !E.isValidOffsetForDataOfSize( + --OffsetPtr, FunctionRecord::kFunctionRecordSize)) + return createStringError(std::make_error_code(std::errc::bad_address), + "Invalid offset for a function record (%d).", + OffsetPtr); + + auto BeginOffset = OffsetPtr; + auto PreReadOffset = BeginOffset; + uint32_t Buffer = E.getU32(&OffsetPtr); + if (PreReadOffset == OffsetPtr) + return createStringError(std::make_error_code(std::errc::bad_address), + "Cannot read function id field from offset %d.", + OffsetPtr); + unsigned FunctionType = (Buffer >> 1) & 0x07; + switch (FunctionType) { + case static_cast(RecordTypes::ENTER): + case static_cast(RecordTypes::ENTER_ARG): + case static_cast(RecordTypes::EXIT): + case static_cast(RecordTypes::TAIL_EXIT): + R.Kind = static_cast(FunctionType); + break; + default: + return createStringError(std::make_error_code(std::errc::bad_message), + "Unknown function record type '%d' at offset %d.", + FunctionType, BeginOffset); + } + + R.FuncId = Buffer >> 4; + PreReadOffset = OffsetPtr; + R.Delta = E.getU32(&OffsetPtr); + if (OffsetPtr == PreReadOffset) + return createStringError(std::make_error_code(std::errc::bad_message), + "Failed reading TSC delta from offset %d.", + OffsetPtr); + assert(FunctionRecord::kFunctionRecordSize == (OffsetPtr - BeginOffset)); + return Error::success(); +} + +} // namespace xray +} // namespace llvm Index: llvm/trunk/unittests/XRay/CMakeLists.txt =================================================================== --- llvm/trunk/unittests/XRay/CMakeLists.txt +++ llvm/trunk/unittests/XRay/CMakeLists.txt @@ -1,9 +1,10 @@ set(LLVM_LINK_COMPONENTS Support - XRay + XRay ) add_llvm_unittest(XRayTests + FDRTraceWriterTest.cpp GraphTest.cpp ProfileTest.cpp ) Index: llvm/trunk/unittests/XRay/FDRTraceWriterTest.cpp =================================================================== --- llvm/trunk/unittests/XRay/FDRTraceWriterTest.cpp +++ llvm/trunk/unittests/XRay/FDRTraceWriterTest.cpp @@ -0,0 +1,175 @@ +//===- llvm/unittest/XRay/FDRTraceWriterTest.cpp ----------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// Test a utility that can write out XRay FDR Mode formatted trace files. +// +//===----------------------------------------------------------------------===// +#include "llvm/XRay/FDRTraceWriter.h" +#include "llvm/Support/raw_ostream.h" +#include "llvm/XRay/FDRLogBuilder.h" +#include "llvm/XRay/FDRRecords.h" +#include "llvm/XRay/Trace.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include + +namespace llvm { +namespace xray { +namespace { + +using testing::ElementsAre; +using testing::Eq; +using testing::Field; +using testing::IsEmpty; +using testing::Not; + +// We want to be able to create an instance of an FDRTraceWriter and associate +// it with a stream, which could be loaded and turned into a Trace instance. +// This test writes out version 3 trace logs. +TEST(FDRTraceWriterTest, WriteToStringBufferVersion3) { + std::string Data; + raw_string_ostream OS(Data); + XRayFileHeader H; + H.Version = 3; + H.Type = 1; + H.ConstantTSC = true; + H.NonstopTSC = true; + H.CycleFrequency = 3e9; + FDRTraceWriter Writer(OS, H); + auto L = LogBuilder() + .add(80) + .add(1) + .add(1, 1) + .add(1) + .add(1) + .add(RecordTypes::ENTER, 1, 1) + .add(RecordTypes::EXIT, 1, 100) + .consume(); + for (auto &P : L) + ASSERT_FALSE(errorToBool(P->apply(Writer))); + OS.flush(); + + // Then from here we load the Trace file. + DataExtractor DE(Data, true, 8); + auto TraceOrErr = loadTrace(DE, true); + if (!TraceOrErr) + FAIL() << TraceOrErr.takeError(); + auto &Trace = TraceOrErr.get(); + + ASSERT_THAT(Trace, Not(IsEmpty())); + ASSERT_THAT( + Trace, + ElementsAre(AllOf(Field(&XRayRecord::FuncId, Eq(1)), + Field(&XRayRecord::TId, Eq(1u)), + Field(&XRayRecord::CPU, Eq(1u)), + Field(&XRayRecord::Type, Eq(RecordTypes::ENTER))), + AllOf(Field(&XRayRecord::FuncId, Eq(1)), + Field(&XRayRecord::TId, Eq(1u)), + Field(&XRayRecord::CPU, Eq(1u)), + Field(&XRayRecord::Type, Eq(RecordTypes::EXIT))))); +} + +// This version is almost exactly the same as above, except writing version 2 +// logs, without the PID records. +TEST(FDRTraceWriterTest, WriteToStringBufferVersion2) { + std::string Data; + raw_string_ostream OS(Data); + XRayFileHeader H; + H.Version = 2; + H.Type = 1; + H.ConstantTSC = true; + H.NonstopTSC = true; + H.CycleFrequency = 3e9; + FDRTraceWriter Writer(OS, H); + auto L = LogBuilder() + .add(64) + .add(1) + .add(1, 1) + .add(1) + .add(RecordTypes::ENTER, 1, 1) + .add(RecordTypes::EXIT, 1, 100) + .consume(); + for (auto &P : L) + ASSERT_FALSE(errorToBool(P->apply(Writer))); + OS.flush(); + + // Then from here we load the Trace file. + DataExtractor DE(Data, true, 8); + auto TraceOrErr = loadTrace(DE, true); + if (!TraceOrErr) + FAIL() << TraceOrErr.takeError(); + auto &Trace = TraceOrErr.get(); + + ASSERT_THAT(Trace, Not(IsEmpty())); + ASSERT_THAT( + Trace, + ElementsAre(AllOf(Field(&XRayRecord::FuncId, Eq(1)), + Field(&XRayRecord::TId, Eq(1u)), + Field(&XRayRecord::CPU, Eq(1u)), + Field(&XRayRecord::Type, Eq(RecordTypes::ENTER))), + AllOf(Field(&XRayRecord::FuncId, Eq(1)), + Field(&XRayRecord::TId, Eq(1u)), + Field(&XRayRecord::CPU, Eq(1u)), + Field(&XRayRecord::Type, Eq(RecordTypes::EXIT))))); +} + +// This covers version 1 of the log, without a BufferExtents record but has an +// explicit EndOfBuffer record. +TEST(FDRTraceWriterTest, WriteToStringBufferVersion1) { + std::string Data; + raw_string_ostream OS(Data); + XRayFileHeader H; + H.Version = 1; + H.Type = 1; + H.ConstantTSC = true; + H.NonstopTSC = true; + H.CycleFrequency = 3e9; + // Write the size of buffers out, arbitrarily it's 4k. + constexpr uint64_t BufferSize = 4096; + std::memcpy(H.FreeFormData, reinterpret_cast(&BufferSize), + sizeof(BufferSize)); + FDRTraceWriter Writer(OS, H); + auto L = LogBuilder() + .add(1) + .add(1, 1) + .add(1) + .add(RecordTypes::ENTER, 1, 1) + .add(RecordTypes::EXIT, 1, 100) + .add() + .consume(); + for (auto &P : L) + ASSERT_FALSE(errorToBool(P->apply(Writer))); + + // We need to pad the buffer with 4016 (4096 - 80) bytes of zeros. + OS.write_zeros(4016); + OS.flush(); + + // Then from here we load the Trace file. + DataExtractor DE(Data, true, 8); + auto TraceOrErr = loadTrace(DE, true); + if (!TraceOrErr) + FAIL() << TraceOrErr.takeError(); + auto &Trace = TraceOrErr.get(); + + ASSERT_THAT(Trace, Not(IsEmpty())); + ASSERT_THAT( + Trace, + ElementsAre(AllOf(Field(&XRayRecord::FuncId, Eq(1)), + Field(&XRayRecord::TId, Eq(1u)), + Field(&XRayRecord::CPU, Eq(1u)), + Field(&XRayRecord::Type, Eq(RecordTypes::ENTER))), + AllOf(Field(&XRayRecord::FuncId, Eq(1)), + Field(&XRayRecord::TId, Eq(1u)), + Field(&XRayRecord::CPU, Eq(1u)), + Field(&XRayRecord::Type, Eq(RecordTypes::EXIT))))); +} + +} // namespace +} // namespace xray +} // namespace llvm