1 //===-- xray_fdr_controller.h ---------------------------------------------===//
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
7 //===----------------------------------------------------------------------===//
9 // This file is a part of XRay, a function call tracing system.
11 //===----------------------------------------------------------------------===//
12 #ifndef COMPILER_RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_
13 #define COMPILER_RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_
18 #include "xray/xray_interface.h"
19 #include "xray/xray_records.h"
20 #include "xray_buffer_queue.h"
21 #include "xray_fdr_log_writer.h"
25 template <size_t Version
= 5> class FDRController
{
27 BufferQueue::Buffer
&B
;
29 int (*WallClockReader
)(clockid_t
, struct timespec
*) = 0;
30 uint64_t CycleThreshold
= 0;
32 uint64_t LastFunctionEntryTSC
= 0;
33 uint64_t LatestTSC
= 0;
34 uint16_t LatestCPU
= 0;
39 uint32_t UndoableFunctionEnters
= 0;
40 uint32_t UndoableTailExits
= 0;
42 bool finalized() const XRAY_NEVER_INSTRUMENT
{
43 return BQ
== nullptr || BQ
->finalizing();
46 bool hasSpace(size_t S
) XRAY_NEVER_INSTRUMENT
{
47 return B
.Data
!= nullptr && B
.Generation
== BQ
->generation() &&
48 W
.getNextRecord() + S
<= reinterpret_cast<char *>(B
.Data
) + B
.Size
;
51 constexpr int32_t mask(int32_t FuncId
) const XRAY_NEVER_INSTRUMENT
{
52 return FuncId
& ((1 << 29) - 1);
55 bool getNewBuffer() XRAY_NEVER_INSTRUMENT
{
56 if (BQ
->getBuffer(B
) != BufferQueue::ErrorCode::Ok
)
60 DCHECK_EQ(W
.getNextRecord(), B
.Data
);
64 UndoableFunctionEnters
= 0;
65 UndoableTailExits
= 0;
66 atomic_store(B
.Extents
, 0, memory_order_release
);
70 bool setupNewBuffer() XRAY_NEVER_INSTRUMENT
{
74 DCHECK(hasSpace(sizeof(MetadataRecord
) * 3));
76 PId
= internal_getpid();
80 WallClockReader(CLOCK_MONOTONIC
, &TS
);
82 MetadataRecord Metadata
[] = {
83 // Write out a MetadataRecord to signify that this is the start of a new
84 // buffer, associated with a particular thread, with a new CPU. For the
85 // data, we have 15 bytes to squeeze as much information as we can. At
86 // this point we only write down the following bytes:
87 // - Thread ID (tid_t, cast to 4 bytes type due to Darwin being 8
89 createMetadataRecord
<MetadataRecord::RecordKinds::NewBuffer
>(
90 static_cast<int32_t>(TId
)),
92 // Also write the WalltimeMarker record. We only really need microsecond
93 // precision here, and enforce across platforms that we need 64-bit
94 // seconds and 32-bit microseconds encoded in the Metadata record.
95 createMetadataRecord
<MetadataRecord::RecordKinds::WalltimeMarker
>(
96 static_cast<int64_t>(TS
.tv_sec
),
97 static_cast<int32_t>(TS
.tv_nsec
/ 1000)),
99 // Also write the Pid record.
100 createMetadataRecord
<MetadataRecord::RecordKinds::Pid
>(
101 static_cast<int32_t>(PId
)),
106 return W
.writeMetadataRecords(Metadata
);
109 bool prepareBuffer(size_t S
) XRAY_NEVER_INSTRUMENT
{
111 return returnBuffer();
113 if (UNLIKELY(!hasSpace(S
))) {
118 if (!setupNewBuffer())
125 atomic_store(B
.Extents
, 0, memory_order_release
);
126 return setupNewBuffer();
132 bool returnBuffer() XRAY_NEVER_INSTRUMENT
{
138 BQ
->releaseBuffer(B
); // ignore result.
142 return BQ
->releaseBuffer(B
) == BufferQueue::ErrorCode::Ok
;
145 enum class PreambleResult
{ NoChange
, WroteMetadata
, InvalidBuffer
};
146 PreambleResult
recordPreamble(uint64_t TSC
,
147 uint16_t CPU
) XRAY_NEVER_INSTRUMENT
{
148 if (UNLIKELY(LatestCPU
!= CPU
|| LatestTSC
== 0)) {
149 // We update our internal tracking state for the Latest TSC and CPU we've
150 // seen, then write out the appropriate metadata and function records.
154 if (B
.Generation
!= BQ
->generation())
155 return PreambleResult::InvalidBuffer
;
157 W
.writeMetadata
<MetadataRecord::RecordKinds::NewCPUId
>(CPU
, TSC
);
158 return PreambleResult::WroteMetadata
;
161 DCHECK_EQ(LatestCPU
, CPU
);
163 if (UNLIKELY(LatestTSC
> TSC
||
165 uint64_t{std::numeric_limits
<int32_t>::max()})) {
166 // Either the TSC has wrapped around from the last TSC we've seen or the
167 // delta is too large to fit in a 32-bit signed integer, so we write a
168 // wrap-around record.
171 if (B
.Generation
!= BQ
->generation())
172 return PreambleResult::InvalidBuffer
;
174 W
.writeMetadata
<MetadataRecord::RecordKinds::TSCWrap
>(TSC
);
175 return PreambleResult::WroteMetadata
;
178 return PreambleResult::NoChange
;
181 bool rewindRecords(int32_t FuncId
, uint64_t TSC
,
182 uint16_t CPU
) XRAY_NEVER_INSTRUMENT
{
183 // Undo one enter record, because at this point we are either at the state
185 // - We are exiting a function that we recently entered.
186 // - We are exiting a function that was the result of a sequence of tail
187 // exits, and we can check whether the tail exits can be re-wound.
190 W
.undoWrites(sizeof(FunctionRecord
));
191 if (B
.Generation
!= BQ
->generation())
193 internal_memcpy(&F
, W
.getNextRecord(), sizeof(FunctionRecord
));
195 DCHECK(F
.RecordKind
==
196 uint8_t(FunctionRecord::RecordKinds::FunctionEnter
) &&
197 "Expected to find function entry recording when rewinding.");
198 DCHECK_EQ(F
.FuncId
, FuncId
& ~(0x0F << 28));
200 LatestTSC
-= F
.TSCDelta
;
201 if (--UndoableFunctionEnters
!= 0) {
202 LastFunctionEntryTSC
-= F
.TSCDelta
;
206 LastFunctionEntryTSC
= 0;
207 auto RewindingTSC
= LatestTSC
;
208 auto RewindingRecordPtr
= W
.getNextRecord() - sizeof(FunctionRecord
);
209 while (UndoableTailExits
) {
210 if (B
.Generation
!= BQ
->generation())
212 internal_memcpy(&F
, RewindingRecordPtr
, sizeof(FunctionRecord
));
213 DCHECK_EQ(F
.RecordKind
,
214 uint8_t(FunctionRecord::RecordKinds::FunctionTailExit
));
215 RewindingTSC
-= F
.TSCDelta
;
216 RewindingRecordPtr
-= sizeof(FunctionRecord
);
217 if (B
.Generation
!= BQ
->generation())
219 internal_memcpy(&F
, RewindingRecordPtr
, sizeof(FunctionRecord
));
221 // This tail call exceeded the threshold duration. It will not be erased.
222 if ((TSC
- RewindingTSC
) >= CycleThreshold
) {
223 UndoableTailExits
= 0;
228 W
.undoWrites(sizeof(FunctionRecord
) * 2);
229 LatestTSC
= RewindingTSC
;
235 template <class WallClockFunc
>
236 FDRController(BufferQueue
*BQ
, BufferQueue::Buffer
&B
, FDRLogWriter
&W
,
237 WallClockFunc R
, uint64_t C
) XRAY_NEVER_INSTRUMENT
244 bool functionEnter(int32_t FuncId
, uint64_t TSC
,
245 uint16_t CPU
) XRAY_NEVER_INSTRUMENT
{
247 !prepareBuffer(sizeof(MetadataRecord
) + sizeof(FunctionRecord
)))
248 return returnBuffer();
250 auto PreambleStatus
= recordPreamble(TSC
, CPU
);
251 if (PreambleStatus
== PreambleResult::InvalidBuffer
)
252 return returnBuffer();
254 if (PreambleStatus
== PreambleResult::WroteMetadata
) {
255 UndoableFunctionEnters
= 1;
256 UndoableTailExits
= 0;
258 ++UndoableFunctionEnters
;
261 auto Delta
= TSC
- LatestTSC
;
262 LastFunctionEntryTSC
= TSC
;
264 return W
.writeFunction(FDRLogWriter::FunctionRecordKind::Enter
,
265 mask(FuncId
), Delta
);
268 bool functionTailExit(int32_t FuncId
, uint64_t TSC
,
269 uint16_t CPU
) XRAY_NEVER_INSTRUMENT
{
271 return returnBuffer();
273 if (!prepareBuffer(sizeof(MetadataRecord
) + sizeof(FunctionRecord
)))
274 return returnBuffer();
276 auto PreambleStatus
= recordPreamble(TSC
, CPU
);
277 if (PreambleStatus
== PreambleResult::InvalidBuffer
)
278 return returnBuffer();
280 if (PreambleStatus
== PreambleResult::NoChange
&&
281 UndoableFunctionEnters
!= 0 &&
282 TSC
- LastFunctionEntryTSC
< CycleThreshold
)
283 return rewindRecords(FuncId
, TSC
, CPU
);
285 UndoableTailExits
= UndoableFunctionEnters
? UndoableTailExits
+ 1 : 0;
286 UndoableFunctionEnters
= 0;
287 auto Delta
= TSC
- LatestTSC
;
289 return W
.writeFunction(FDRLogWriter::FunctionRecordKind::TailExit
,
290 mask(FuncId
), Delta
);
293 bool functionEnterArg(int32_t FuncId
, uint64_t TSC
, uint16_t CPU
,
294 uint64_t Arg
) XRAY_NEVER_INSTRUMENT
{
296 !prepareBuffer((2 * sizeof(MetadataRecord
)) + sizeof(FunctionRecord
)) ||
297 recordPreamble(TSC
, CPU
) == PreambleResult::InvalidBuffer
)
298 return returnBuffer();
300 auto Delta
= TSC
- LatestTSC
;
302 LastFunctionEntryTSC
= 0;
303 UndoableFunctionEnters
= 0;
304 UndoableTailExits
= 0;
306 return W
.writeFunctionWithArg(FDRLogWriter::FunctionRecordKind::EnterArg
,
307 mask(FuncId
), Delta
, Arg
);
310 bool functionExit(int32_t FuncId
, uint64_t TSC
,
311 uint16_t CPU
) XRAY_NEVER_INSTRUMENT
{
313 !prepareBuffer(sizeof(MetadataRecord
) + sizeof(FunctionRecord
)))
314 return returnBuffer();
316 auto PreambleStatus
= recordPreamble(TSC
, CPU
);
317 if (PreambleStatus
== PreambleResult::InvalidBuffer
)
318 return returnBuffer();
320 if (PreambleStatus
== PreambleResult::NoChange
&&
321 UndoableFunctionEnters
!= 0 &&
322 TSC
- LastFunctionEntryTSC
< CycleThreshold
)
323 return rewindRecords(FuncId
, TSC
, CPU
);
325 auto Delta
= TSC
- LatestTSC
;
327 UndoableFunctionEnters
= 0;
328 UndoableTailExits
= 0;
329 return W
.writeFunction(FDRLogWriter::FunctionRecordKind::Exit
, mask(FuncId
),
333 bool customEvent(uint64_t TSC
, uint16_t CPU
, const void *Event
,
334 int32_t EventSize
) XRAY_NEVER_INSTRUMENT
{
336 !prepareBuffer((2 * sizeof(MetadataRecord
)) + EventSize
) ||
337 recordPreamble(TSC
, CPU
) == PreambleResult::InvalidBuffer
)
338 return returnBuffer();
340 auto Delta
= TSC
- LatestTSC
;
342 UndoableFunctionEnters
= 0;
343 UndoableTailExits
= 0;
344 return W
.writeCustomEvent(Delta
, Event
, EventSize
);
347 bool typedEvent(uint64_t TSC
, uint16_t CPU
, uint16_t EventType
,
348 const void *Event
, int32_t EventSize
) XRAY_NEVER_INSTRUMENT
{
350 !prepareBuffer((2 * sizeof(MetadataRecord
)) + EventSize
) ||
351 recordPreamble(TSC
, CPU
) == PreambleResult::InvalidBuffer
)
352 return returnBuffer();
354 auto Delta
= TSC
- LatestTSC
;
356 UndoableFunctionEnters
= 0;
357 UndoableTailExits
= 0;
358 return W
.writeTypedEvent(Delta
, EventType
, Event
, EventSize
);
361 bool flush() XRAY_NEVER_INSTRUMENT
{
363 returnBuffer(); // ignore result.
366 return returnBuffer();
370 } // namespace __xray
372 #endif // COMPILER-RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_