LLVM 22.0.0git
Trace.cpp
Go to the documentation of this file.
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"
15#include "llvm/Support/Error.h"
25#include <memory>
26#include <vector>
27
28using namespace llvm;
29using namespace llvm::xray;
31
32static Error loadNaiveFormatLog(StringRef Data, bool IsLittleEndian,
33 XRayFileHeader &FileHeader,
34 std::vector<XRayRecord> &Records) {
35 if (Data.size() < 32)
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)
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 type
58 // (1) uint8 : cpu id
59 // (1) uint8 : type
60 // (4) sint32 : function id
61 // (8) uint64 : tsc
62 // (4) uint32 : thread id
63 // (4) uint32 : process id
64 // (8) - : padding
65 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:
102 break;
103 case 1:
105 break;
106 case 2:
108 break;
109 case 3:
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 the
153 // 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 field
179 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 for
206 // 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 is
213/// defined as part of the compiler-rt project in xray_fdr_logging.h, and such
214/// a log consists of the familiar 32 bit XRayHeader, followed by sequences of
215/// 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 is
218/// parsed by this function for little-endian machines. Since the format makes
219/// use of BitFields, when we support big-endian architectures, we will need to
220/// adjust not only the endianness parameter to llvm's RecordExtractor, but also
221/// the bit twiddling logic, which is consistent with the little-endian
222/// convention that BitFields within a struct will first be packed into the
223/// least significant bits the address they belong to.
224///
225/// We expect a format complying with the grammar in the following pseudo-EBNF
226/// 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 BufferSize
231/// ThreadBuffer: NewBuffer WallClockTime NewCPUId FunctionSequence EOB
232/// 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 Pid
236/// 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 | FunctionRecord
239/// 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 NewCPUId
245/// FunctionSequence
246/// BufferExtents: 16 byte metdata record describing how many usable bytes are
247/// in the buffer. This is measured from the start of the buffer
248/// 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 NewCPUId
253/// FunctionSequence
254/// 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 to
263/// what FunctionRecord instances use, and we no longer need to include the CPU
264/// 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.
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 to
323 // the Walltime record in each of the blocks for the same thread. This allows
324 // us to more consistently recreate the execution trace in temporal order.
325 // After the sort, we then reconstitute `Trace` records using a stateful
326 // 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
351 std::vector<XRayRecord> &Records) {
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)
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
381 if (!FdOrErr)
382 return FdOrErr.takeError();
383
384 uint64_t FileSize;
385 if (auto EC = sys::fs::file_size(Filename, FileSize)) {
387 Twine("Cannot read log from '") + Filename + "'", EC);
388 }
389 if (FileSize < 4) {
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;
399 EC);
400 sys::fs::closeFile(*FdOrErr);
401 if (EC) {
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
419 // Attempt to detect the file type using file magic. We have a slight bias
420 // towards the binary format, and we do this by making sure that the first 4
421 // bytes of the binary file is some combination of the following byte
422 // patterns: (observe the code loading them assumes they're little endian)
423 //
424 // 0x01 0x00 0x00 0x00 - version 1, "naive" format
425 // 0x01 0x00 0x01 0x00 - version 1, "flight data recorder" format
426 // 0x02 0x00 0x01 0x00 - version 2, "flight data recorder" format
427 //
428 // YAML files don't typically have those first four bytes as valid text so we
429 // 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 an
432 // 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 {
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 {
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}
static GCRegistry::Add< CoreCLRGC > E("coreclr", "CoreCLR-compatible GC")
static GCRegistry::Add< OcamlGC > B("ocaml", "ocaml 3.10-compatible GC")
#define T
#define P(N)
This file contains some templates that are useful if you are working with the STL at all.
FunctionLoweringInfo::StatepointRelocationRecord RecordType
static Error loadFDRLog(StringRef Data, bool IsLittleEndian, XRayFileHeader &FileHeader, std::vector< XRayRecord > &Records)
Reads a log in FDR mode for version 1 of this binary format.
Definition Trace.cpp:266
static Error loadNaiveFormatLog(StringRef Data, bool IsLittleEndian, XRayFileHeader &FileHeader, std::vector< XRayRecord > &Records)
Definition Trace.cpp:32
static Error loadYAMLLog(StringRef Data, XRayFileHeader &FileHeader, std::vector< XRayRecord > &Records)
Definition Trace.cpp:350
LLVM_ABI uint32_t getU32(uint64_t *offset_ptr, Error *Err=nullptr) const
Extract a uint32_t value from *offset_ptr.
size_t size() const
Return the number of bytes in the underlying buffer.
LLVM_ABI uint8_t getU8(uint64_t *offset_ptr, Error *Err=nullptr) const
Extract a uint8_t value from *offset_ptr.
LLVM_ABI int64_t getSigned(uint64_t *offset_ptr, uint32_t size) const
Extract an signed integer of size byte_size from *offset_ptr.
StringRef getData() const
Get the data pointed to by this extractor.
LLVM_ABI uint16_t getU16(uint64_t *offset_ptr, Error *Err=nullptr) const
Extract a uint16_t value from *offset_ptr.
LLVM_ABI uint64_t getU64(uint64_t *offset_ptr, Error *Err=nullptr) const
Extract a uint64_t value from *offset_ptr.
bool isValidOffset(uint64_t offset) const
Test the validity of offset.
bool isValidOffsetForDataOfSize(uint64_t offset, uint64_t length) const
Test the availability of length bytes of data from offset.
bool isLittleEndian() const
Get the endianness for this extractor.
Lightweight error class with error context and mandatory checking.
Definition Error.h:159
static ErrorSuccess success()
Create a success value.
Definition Error.h:336
Tagged union holding either a T or a Error.
Definition Error.h:485
Error takeError()
Take ownership of the stored error.
Definition Error.h:612
reference emplace_back(ArgTypes &&... Args)
void reserve(size_type N)
void push_back(const T &Elt)
StringRef - Represent a constant reference to a string, i.e.
Definition StringRef.h:55
iterator begin()
Definition Trace.h:85
iterator end()
Definition Trace.h:87
Twine - A lightweight data structure for efficiently representing the concatenation of temporary valu...
Definition Twine.h:82
The instances of the Type class are immutable: once they are created, they are never changed.
Definition Type.h:45
This class represents a memory mapped file.
LLVM_ABI size_t size() const
Definition Path.cpp:1159
@ readonly
May only access map via const_data as read only.
LLVM_ABI char * data() const
Definition Path.cpp:1164
DenseMap< std::pair< uint64_t, int32_t >, std::vector< Block > > Index
Error flush()
The flush() function will clear out the current state of the visitor, to allow for explicitly flushin...
A Trace object represents the records that have been loaded from XRay log files generated by instrume...
Definition Trace.h:46
The Input class is used to parse a yaml document into in-memory structs and vectors.
@ C
The default llvm calling convention, compatible with C.
Definition CallingConv.h:34
LLVM_ABI std::error_code closeFile(file_t &F)
Close the file object.
LLVM_ABI Expected< file_t > openNativeFileForRead(const Twine &Name, OpenFlags Flags=OF_None, SmallVectorImpl< char > *RealPath=nullptr)
Opens the file with the given name in a read-only mode, returning its open file descriptor.
std::error_code file_size(const Twine &Path, uint64_t &Result)
Get file size.
Definition FileSystem.h:684
LLVM_ABI Expected< Trace > loadTrace(const DataExtractor &Extractor, bool Sort=false)
This function will attempt to load XRay trace records from the provided DataExtractor.
Definition Trace.cpp:418
LLVM_ABI Expected< XRayFileHeader > readBinaryFormatHeader(DataExtractor &HeaderExtractor, uint64_t &OffsetPtr)
Convenience function for loading the file header given a data extractor at a specified offset.
LLVM_ABI Expected< Trace > loadTraceFile(StringRef Filename, bool Sort=false)
This function will attempt to load XRay trace records from the provided |Filename|.
Definition Trace.cpp:379
This is an optimization pass for GlobalISel generic memory operations.
void stable_sort(R &&Range)
Definition STLExtras.h:2058
Error createStringError(std::error_code EC, char const *Fmt, const Ts &... Vals)
Create formatted StringError object.
Definition Error.h:1305
FunctionAddr VTableAddr uintptr_t uintptr_t Version
Definition InstrProf.h:302
void sort(IteratorTy Start, IteratorTy End)
Definition STLExtras.h:1622
Error make_error(ArgTs &&... Args)
Make a Error instance representing failure using the given error info type.
Definition Error.h:340
constexpr T divideCeil(U Numerator, V Denominator)
Returns the integer ceil(Numerator / Denominator).
Definition MathExtras.h:405
FunctionAddr VTableAddr uintptr_t uintptr_t Data
Definition InstrProf.h:189
void consumeError(Error Err)
Consume a Error without doing anything.
Definition Error.h:1083
XRay traces all have a header providing some top-matter information useful to help tools determine ho...
Definition XRayRecord.h:26
bool ConstantTSC
Whether the CPU that produced the timestamp counters (TSC) move at a constant rate.
Definition XRayRecord.h:36
uint16_t Type
A numeric identifier for the type of file this is.
Definition XRayRecord.h:32
uint64_t CycleFrequency
The number of cycles per second for the CPU that produced the timestamp counter (TSC) values.
Definition XRayRecord.h:44
bool NonstopTSC
Whether the CPU that produced the timestamp counters (TSC) do not stop.
Definition XRayRecord.h:39
uint16_t Version
Version of the XRay implementation that produced this file.
Definition XRayRecord.h:28
An XRayRecord is the denormalized view of data associated in a trace.
Definition XRayRecord.h:68