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(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(B.Extents, 0, 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 0, 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 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 static_cast<int64_t>(TS.tv_sec), 97 static_cast<int32_t>(TS.tv_nsec / 1000)), 98 99 // Also write the Pid record. 100 createMetadataRecord<MetadataRecord::RecordKinds::Pid>( 101 static_cast<int32_t>(PId)), 102 }; 103 104 if (finalized()) 105 return false; 106 return W.writeMetadataRecords(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(B.Extents, 0, 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(B); // ignore result. 139 return false; 140 } 141 142 return BQ->releaseBuffer(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>(CPU, 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>(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(sizeof(FunctionRecord)); 191 if (B.Generation != BQ->generation()) 192 return false; 193 internal_memcpy(&F, W.getNextRecord(), 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(&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()) 218 return false; 219 internal_memcpy(&F, RewindingRecordPtr, 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(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(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(FDRLogWriter::FunctionRecordKind::Enter, 265 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(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(FDRLogWriter::FunctionRecordKind::TailExit, 290 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((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(FDRLogWriter::FunctionRecordKind::EnterArg, 307 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(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(FDRLogWriter::FunctionRecordKind::Exit, 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((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((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