1 | //===-- xray_fdr_controller.h ---------------------------------------------===// |
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 | // This file is a part of XRay, a function call tracing system. |
10 | // |
11 | //===----------------------------------------------------------------------===// |
12 | #ifndef COMPILER_RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_ |
13 | #define COMPILER_RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_ |
14 | |
15 | #include <limits> |
16 | #include <time.h> |
17 | |
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" |
22 | |
23 | namespace __xray { |
24 | |
25 | template <size_t Version = 5> class FDRController { |
26 | BufferQueue *BQ; |
27 | BufferQueue::Buffer &B; |
28 | FDRLogWriter &W; |
29 | int (*WallClockReader)(clockid_t, struct timespec *) = 0; |
30 | uint64_t CycleThreshold = 0; |
31 | |
32 | uint64_t LastFunctionEntryTSC = 0; |
33 | uint64_t LatestTSC = 0; |
34 | uint16_t LatestCPU = 0; |
35 | tid_t TId = 0; |
36 | pid_t PId = 0; |
37 | bool First = true; |
38 | |
39 | uint32_t UndoableFunctionEnters = 0; |
40 | uint32_t UndoableTailExits = 0; |
41 | |
42 | bool finalized() const XRAY_NEVER_INSTRUMENT { |
43 | return BQ == nullptr || BQ->finalizing(); |
44 | } |
45 | |
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; |
49 | } |
50 | |
51 | constexpr int32_t mask(int32_t FuncId) const XRAY_NEVER_INSTRUMENT { |
52 | return FuncId & ((1 << 29) - 1); |
53 | } |
54 | |
55 | bool getNewBuffer() XRAY_NEVER_INSTRUMENT { |
56 | if (BQ->getBuffer(Buf&: B) != BufferQueue::ErrorCode::Ok) |
57 | return false; |
58 | |
59 | W.resetRecord(); |
60 | DCHECK_EQ(W.getNextRecord(), B.Data); |
61 | LatestTSC = 0; |
62 | LatestCPU = 0; |
63 | First = true; |
64 | UndoableFunctionEnters = 0; |
65 | UndoableTailExits = 0; |
66 | atomic_store(a: B.Extents, v: 0, mo: memory_order_release); |
67 | return true; |
68 | } |
69 | |
70 | bool setupNewBuffer() XRAY_NEVER_INSTRUMENT { |
71 | if (finalized()) |
72 | return false; |
73 | |
74 | DCHECK(hasSpace(sizeof(MetadataRecord) * 3)); |
75 | TId = GetTid(); |
76 | PId = internal_getpid(); |
77 | struct timespec TS { |
78 | .tv_sec: 0, .tv_nsec: 0 |
79 | }; |
80 | WallClockReader(CLOCK_MONOTONIC, &TS); |
81 | |
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 |
88 | // bytes) |
89 | createMetadataRecord<MetadataRecord::RecordKinds::NewBuffer>( |
90 | Ds: static_cast<int32_t>(TId)), |
91 | |
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 | Ds: static_cast<int64_t>(TS.tv_sec), |
97 | Ds: static_cast<int32_t>(TS.tv_nsec / 1000)), |
98 | |
99 | // Also write the Pid record. |
100 | createMetadataRecord<MetadataRecord::RecordKinds::Pid>( |
101 | Ds: static_cast<int32_t>(PId)), |
102 | }; |
103 | |
104 | if (finalized()) |
105 | return false; |
106 | return W.writeMetadataRecords(Recs&: Metadata); |
107 | } |
108 | |
109 | bool prepareBuffer(size_t S) XRAY_NEVER_INSTRUMENT { |
110 | if (finalized()) |
111 | return returnBuffer(); |
112 | |
113 | if (UNLIKELY(!hasSpace(S))) { |
114 | if (!returnBuffer()) |
115 | return false; |
116 | if (!getNewBuffer()) |
117 | return false; |
118 | if (!setupNewBuffer()) |
119 | return false; |
120 | } |
121 | |
122 | if (First) { |
123 | First = false; |
124 | W.resetRecord(); |
125 | atomic_store(a: B.Extents, v: 0, mo: memory_order_release); |
126 | return setupNewBuffer(); |
127 | } |
128 | |
129 | return true; |
130 | } |
131 | |
132 | bool returnBuffer() XRAY_NEVER_INSTRUMENT { |
133 | if (BQ == nullptr) |
134 | return false; |
135 | |
136 | First = true; |
137 | if (finalized()) { |
138 | BQ->releaseBuffer(Buf&: B); // ignore result. |
139 | return false; |
140 | } |
141 | |
142 | return BQ->releaseBuffer(Buf&: B) == BufferQueue::ErrorCode::Ok; |
143 | } |
144 | |
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. |
151 | LatestTSC = TSC; |
152 | LatestCPU = CPU; |
153 | |
154 | if (B.Generation != BQ->generation()) |
155 | return PreambleResult::InvalidBuffer; |
156 | |
157 | W.writeMetadata<MetadataRecord::RecordKinds::NewCPUId>(Ds&: CPU, Ds&: TSC); |
158 | return PreambleResult::WroteMetadata; |
159 | } |
160 | |
161 | DCHECK_EQ(LatestCPU, CPU); |
162 | |
163 | if (UNLIKELY(LatestTSC > TSC || |
164 | TSC - LatestTSC > |
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. |
169 | LatestTSC = TSC; |
170 | |
171 | if (B.Generation != BQ->generation()) |
172 | return PreambleResult::InvalidBuffer; |
173 | |
174 | W.writeMetadata<MetadataRecord::RecordKinds::TSCWrap>(Ds&: TSC); |
175 | return PreambleResult::WroteMetadata; |
176 | } |
177 | |
178 | return PreambleResult::NoChange; |
179 | } |
180 | |
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 |
184 | // of: |
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. |
188 | // |
189 | FunctionRecord F; |
190 | W.undoWrites(B: sizeof(FunctionRecord)); |
191 | if (B.Generation != BQ->generation()) |
192 | return false; |
193 | internal_memcpy(dest: &F, src: W.getNextRecord(), n: sizeof(FunctionRecord)); |
194 | |
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)); |
199 | |
200 | LatestTSC -= F.TSCDelta; |
201 | if (--UndoableFunctionEnters != 0) { |
202 | LastFunctionEntryTSC -= F.TSCDelta; |
203 | return true; |
204 | } |
205 | |
206 | LastFunctionEntryTSC = 0; |
207 | auto RewindingTSC = LatestTSC; |
208 | auto RewindingRecordPtr = W.getNextRecord() - sizeof(FunctionRecord); |
209 | while (UndoableTailExits) { |
210 | if (B.Generation != BQ->generation()) |
211 | return false; |
212 | internal_memcpy(dest: &F, src: RewindingRecordPtr, n: 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()) |
218 | return false; |
219 | internal_memcpy(dest: &F, src: RewindingRecordPtr, n: sizeof(FunctionRecord)); |
220 | |
221 | // This tail call exceeded the threshold duration. It will not be erased. |
222 | if ((TSC - RewindingTSC) >= CycleThreshold) { |
223 | UndoableTailExits = 0; |
224 | return true; |
225 | } |
226 | |
227 | --UndoableTailExits; |
228 | W.undoWrites(B: sizeof(FunctionRecord) * 2); |
229 | LatestTSC = RewindingTSC; |
230 | } |
231 | return true; |
232 | } |
233 | |
234 | public: |
235 | template <class WallClockFunc> |
236 | FDRController(BufferQueue *BQ, BufferQueue::Buffer &B, FDRLogWriter &W, |
237 | WallClockFunc R, uint64_t C) XRAY_NEVER_INSTRUMENT |
238 | : BQ(BQ), |
239 | B(B), |
240 | W(W), |
241 | WallClockReader(R), |
242 | CycleThreshold(C) {} |
243 | |
244 | bool functionEnter(int32_t FuncId, uint64_t TSC, |
245 | uint16_t CPU) XRAY_NEVER_INSTRUMENT { |
246 | if (finalized() || |
247 | !prepareBuffer(S: sizeof(MetadataRecord) + sizeof(FunctionRecord))) |
248 | return returnBuffer(); |
249 | |
250 | auto PreambleStatus = recordPreamble(TSC, CPU); |
251 | if (PreambleStatus == PreambleResult::InvalidBuffer) |
252 | return returnBuffer(); |
253 | |
254 | if (PreambleStatus == PreambleResult::WroteMetadata) { |
255 | UndoableFunctionEnters = 1; |
256 | UndoableTailExits = 0; |
257 | } else { |
258 | ++UndoableFunctionEnters; |
259 | } |
260 | |
261 | auto Delta = TSC - LatestTSC; |
262 | LastFunctionEntryTSC = TSC; |
263 | LatestTSC = TSC; |
264 | return W.writeFunction(Kind: FDRLogWriter::FunctionRecordKind::Enter, |
265 | FuncId: mask(FuncId), Delta); |
266 | } |
267 | |
268 | bool functionTailExit(int32_t FuncId, uint64_t TSC, |
269 | uint16_t CPU) XRAY_NEVER_INSTRUMENT { |
270 | if (finalized()) |
271 | return returnBuffer(); |
272 | |
273 | if (!prepareBuffer(S: sizeof(MetadataRecord) + sizeof(FunctionRecord))) |
274 | return returnBuffer(); |
275 | |
276 | auto PreambleStatus = recordPreamble(TSC, CPU); |
277 | if (PreambleStatus == PreambleResult::InvalidBuffer) |
278 | return returnBuffer(); |
279 | |
280 | if (PreambleStatus == PreambleResult::NoChange && |
281 | UndoableFunctionEnters != 0 && |
282 | TSC - LastFunctionEntryTSC < CycleThreshold) |
283 | return rewindRecords(FuncId, TSC, CPU); |
284 | |
285 | UndoableTailExits = UndoableFunctionEnters ? UndoableTailExits + 1 : 0; |
286 | UndoableFunctionEnters = 0; |
287 | auto Delta = TSC - LatestTSC; |
288 | LatestTSC = TSC; |
289 | return W.writeFunction(Kind: FDRLogWriter::FunctionRecordKind::TailExit, |
290 | FuncId: mask(FuncId), Delta); |
291 | } |
292 | |
293 | bool functionEnterArg(int32_t FuncId, uint64_t TSC, uint16_t CPU, |
294 | uint64_t Arg) XRAY_NEVER_INSTRUMENT { |
295 | if (finalized() || |
296 | !prepareBuffer(S: (2 * sizeof(MetadataRecord)) + sizeof(FunctionRecord)) || |
297 | recordPreamble(TSC, CPU) == PreambleResult::InvalidBuffer) |
298 | return returnBuffer(); |
299 | |
300 | auto Delta = TSC - LatestTSC; |
301 | LatestTSC = TSC; |
302 | LastFunctionEntryTSC = 0; |
303 | UndoableFunctionEnters = 0; |
304 | UndoableTailExits = 0; |
305 | |
306 | return W.writeFunctionWithArg(Kind: FDRLogWriter::FunctionRecordKind::EnterArg, |
307 | FuncId: mask(FuncId), Delta, Arg); |
308 | } |
309 | |
310 | bool functionExit(int32_t FuncId, uint64_t TSC, |
311 | uint16_t CPU) XRAY_NEVER_INSTRUMENT { |
312 | if (finalized() || |
313 | !prepareBuffer(S: sizeof(MetadataRecord) + sizeof(FunctionRecord))) |
314 | return returnBuffer(); |
315 | |
316 | auto PreambleStatus = recordPreamble(TSC, CPU); |
317 | if (PreambleStatus == PreambleResult::InvalidBuffer) |
318 | return returnBuffer(); |
319 | |
320 | if (PreambleStatus == PreambleResult::NoChange && |
321 | UndoableFunctionEnters != 0 && |
322 | TSC - LastFunctionEntryTSC < CycleThreshold) |
323 | return rewindRecords(FuncId, TSC, CPU); |
324 | |
325 | auto Delta = TSC - LatestTSC; |
326 | LatestTSC = TSC; |
327 | UndoableFunctionEnters = 0; |
328 | UndoableTailExits = 0; |
329 | return W.writeFunction(Kind: FDRLogWriter::FunctionRecordKind::Exit, FuncId: mask(FuncId), |
330 | Delta); |
331 | } |
332 | |
333 | bool customEvent(uint64_t TSC, uint16_t CPU, const void *Event, |
334 | int32_t EventSize) XRAY_NEVER_INSTRUMENT { |
335 | if (finalized() || |
336 | !prepareBuffer(S: (2 * sizeof(MetadataRecord)) + EventSize) || |
337 | recordPreamble(TSC, CPU) == PreambleResult::InvalidBuffer) |
338 | return returnBuffer(); |
339 | |
340 | auto Delta = TSC - LatestTSC; |
341 | LatestTSC = TSC; |
342 | UndoableFunctionEnters = 0; |
343 | UndoableTailExits = 0; |
344 | return W.writeCustomEvent(Delta, Event, EventSize); |
345 | } |
346 | |
347 | bool typedEvent(uint64_t TSC, uint16_t CPU, uint16_t EventType, |
348 | const void *Event, int32_t EventSize) XRAY_NEVER_INSTRUMENT { |
349 | if (finalized() || |
350 | !prepareBuffer(S: (2 * sizeof(MetadataRecord)) + EventSize) || |
351 | recordPreamble(TSC, CPU) == PreambleResult::InvalidBuffer) |
352 | return returnBuffer(); |
353 | |
354 | auto Delta = TSC - LatestTSC; |
355 | LatestTSC = TSC; |
356 | UndoableFunctionEnters = 0; |
357 | UndoableTailExits = 0; |
358 | return W.writeTypedEvent(Delta, EventType, Event, EventSize); |
359 | } |
360 | |
361 | bool flush() XRAY_NEVER_INSTRUMENT { |
362 | if (finalized()) { |
363 | returnBuffer(); // ignore result. |
364 | return true; |
365 | } |
366 | return returnBuffer(); |
367 | } |
368 | }; |
369 | |
370 | } // namespace __xray |
371 | |
372 | #endif // COMPILER-RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_ |
373 | |