//===-- xray_fdr_controller.h ---------------------------------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// // // This file is a part of XRay, a function call tracing system. // //===----------------------------------------------------------------------===// #ifndef COMPILER_RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_ #define COMPILER_RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_ #include #include #include "xray/xray_interface.h" #include "xray/xray_records.h" #include "xray_buffer_queue.h" #include "xray_fdr_log_writer.h" namespace __xray { template class FDRController { BufferQueue *BQ; BufferQueue::Buffer &B; FDRLogWriter &W; int (*WallClockReader)(clockid_t, struct timespec *) = 0; uint64_t CycleThreshold = 0; uint64_t LastFunctionEntryTSC = 0; uint64_t LatestTSC = 0; uint16_t LatestCPU = 0; tid_t TId = 0; pid_t PId = 0; bool First = true; uint32_t UndoableFunctionEnters = 0; uint32_t UndoableTailExits = 0; bool finalized() const XRAY_NEVER_INSTRUMENT { return BQ == nullptr || BQ->finalizing(); } bool hasSpace(size_t S) XRAY_NEVER_INSTRUMENT { return B.Data != nullptr && B.Generation == BQ->generation() && W.getNextRecord() + S <= reinterpret_cast(B.Data) + B.Size; } constexpr int32_t mask(int32_t FuncId) const XRAY_NEVER_INSTRUMENT { return FuncId & ((1 << 29) - 1); } bool getNewBuffer() XRAY_NEVER_INSTRUMENT { if (BQ->getBuffer(B) != BufferQueue::ErrorCode::Ok) return false; W.resetRecord(); DCHECK_EQ(W.getNextRecord(), B.Data); LatestTSC = 0; LatestCPU = 0; First = true; UndoableFunctionEnters = 0; UndoableTailExits = 0; atomic_store(B.Extents, 0, memory_order_release); return true; } bool setupNewBuffer() XRAY_NEVER_INSTRUMENT { if (finalized()) return false; DCHECK(hasSpace(sizeof(MetadataRecord) * 3)); TId = GetTid(); PId = internal_getpid(); struct timespec TS { 0, 0 }; WallClockReader(CLOCK_MONOTONIC, &TS); MetadataRecord Metadata[] = { // Write out a MetadataRecord to signify that this is the start of a new // buffer, associated with a particular thread, with a new CPU. For the // data, we have 15 bytes to squeeze as much information as we can. At // this point we only write down the following bytes: // - Thread ID (tid_t, cast to 4 bytes type due to Darwin being 8 // bytes) createMetadataRecord( static_cast(TId)), // Also write the WalltimeMarker record. We only really need microsecond // precision here, and enforce across platforms that we need 64-bit // seconds and 32-bit microseconds encoded in the Metadata record. createMetadataRecord( static_cast(TS.tv_sec), static_cast(TS.tv_nsec / 1000)), // Also write the Pid record. createMetadataRecord( static_cast(PId)), }; if (finalized()) return false; return W.writeMetadataRecords(Metadata); } bool prepareBuffer(size_t S) XRAY_NEVER_INSTRUMENT { if (finalized()) return returnBuffer(); if (UNLIKELY(!hasSpace(S))) { if (!returnBuffer()) return false; if (!getNewBuffer()) return false; if (!setupNewBuffer()) return false; } if (First) { First = false; W.resetRecord(); atomic_store(B.Extents, 0, memory_order_release); return setupNewBuffer(); } return true; } bool returnBuffer() XRAY_NEVER_INSTRUMENT { if (BQ == nullptr) return false; First = true; if (finalized()) { BQ->releaseBuffer(B); // ignore result. return false; } return BQ->releaseBuffer(B) == BufferQueue::ErrorCode::Ok; } enum class PreambleResult { NoChange, WroteMetadata, InvalidBuffer }; PreambleResult recordPreamble(uint64_t TSC, uint16_t CPU) XRAY_NEVER_INSTRUMENT { if (UNLIKELY(LatestCPU != CPU || LatestTSC == 0)) { // We update our internal tracking state for the Latest TSC and CPU we've // seen, then write out the appropriate metadata and function records. LatestTSC = TSC; LatestCPU = CPU; if (B.Generation != BQ->generation()) return PreambleResult::InvalidBuffer; W.writeMetadata(CPU, TSC); return PreambleResult::WroteMetadata; } DCHECK_EQ(LatestCPU, CPU); if (UNLIKELY(LatestTSC > TSC || TSC - LatestTSC > uint64_t{std::numeric_limits::max()})) { // Either the TSC has wrapped around from the last TSC we've seen or the // delta is too large to fit in a 32-bit signed integer, so we write a // wrap-around record. LatestTSC = TSC; if (B.Generation != BQ->generation()) return PreambleResult::InvalidBuffer; W.writeMetadata(TSC); return PreambleResult::WroteMetadata; } return PreambleResult::NoChange; } bool rewindRecords(int32_t FuncId, uint64_t TSC, uint16_t CPU) XRAY_NEVER_INSTRUMENT { // Undo one enter record, because at this point we are either at the state // of: // - We are exiting a function that we recently entered. // - We are exiting a function that was the result of a sequence of tail // exits, and we can check whether the tail exits can be re-wound. // FunctionRecord F; W.undoWrites(sizeof(FunctionRecord)); if (B.Generation != BQ->generation()) return false; internal_memcpy(&F, W.getNextRecord(), sizeof(FunctionRecord)); DCHECK(F.RecordKind == uint8_t(FunctionRecord::RecordKinds::FunctionEnter) && "Expected to find function entry recording when rewinding."); DCHECK_EQ(F.FuncId, FuncId & ~(0x0F << 28)); LatestTSC -= F.TSCDelta; if (--UndoableFunctionEnters != 0) { LastFunctionEntryTSC -= F.TSCDelta; return true; } LastFunctionEntryTSC = 0; auto RewindingTSC = LatestTSC; auto RewindingRecordPtr = W.getNextRecord() - sizeof(FunctionRecord); while (UndoableTailExits) { if (B.Generation != BQ->generation()) return false; internal_memcpy(&F, RewindingRecordPtr, sizeof(FunctionRecord)); DCHECK_EQ(F.RecordKind, uint8_t(FunctionRecord::RecordKinds::FunctionTailExit)); RewindingTSC -= F.TSCDelta; RewindingRecordPtr -= sizeof(FunctionRecord); if (B.Generation != BQ->generation()) return false; internal_memcpy(&F, RewindingRecordPtr, sizeof(FunctionRecord)); // This tail call exceeded the threshold duration. It will not be erased. if ((TSC - RewindingTSC) >= CycleThreshold) { UndoableTailExits = 0; return true; } --UndoableTailExits; W.undoWrites(sizeof(FunctionRecord) * 2); LatestTSC = RewindingTSC; } return true; } public: template FDRController(BufferQueue *BQ, BufferQueue::Buffer &B, FDRLogWriter &W, WallClockFunc R, uint64_t C) XRAY_NEVER_INSTRUMENT : BQ(BQ), B(B), W(W), WallClockReader(R), CycleThreshold(C) {} bool functionEnter(int32_t FuncId, uint64_t TSC, uint16_t CPU) XRAY_NEVER_INSTRUMENT { if (finalized() || !prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord))) return returnBuffer(); auto PreambleStatus = recordPreamble(TSC, CPU); if (PreambleStatus == PreambleResult::InvalidBuffer) return returnBuffer(); if (PreambleStatus == PreambleResult::WroteMetadata) { UndoableFunctionEnters = 1; UndoableTailExits = 0; } else { ++UndoableFunctionEnters; } auto Delta = TSC - LatestTSC; LastFunctionEntryTSC = TSC; LatestTSC = TSC; return W.writeFunction(FDRLogWriter::FunctionRecordKind::Enter, mask(FuncId), Delta); } bool functionTailExit(int32_t FuncId, uint64_t TSC, uint16_t CPU) XRAY_NEVER_INSTRUMENT { if (finalized()) return returnBuffer(); if (!prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord))) return returnBuffer(); auto PreambleStatus = recordPreamble(TSC, CPU); if (PreambleStatus == PreambleResult::InvalidBuffer) return returnBuffer(); if (PreambleStatus == PreambleResult::NoChange && UndoableFunctionEnters != 0 && TSC - LastFunctionEntryTSC < CycleThreshold) return rewindRecords(FuncId, TSC, CPU); UndoableTailExits = UndoableFunctionEnters ? UndoableTailExits + 1 : 0; UndoableFunctionEnters = 0; auto Delta = TSC - LatestTSC; LatestTSC = TSC; return W.writeFunction(FDRLogWriter::FunctionRecordKind::TailExit, mask(FuncId), Delta); } bool functionEnterArg(int32_t FuncId, uint64_t TSC, uint16_t CPU, uint64_t Arg) XRAY_NEVER_INSTRUMENT { if (finalized() || !prepareBuffer((2 * sizeof(MetadataRecord)) + sizeof(FunctionRecord)) || recordPreamble(TSC, CPU) == PreambleResult::InvalidBuffer) return returnBuffer(); auto Delta = TSC - LatestTSC; LatestTSC = TSC; LastFunctionEntryTSC = 0; UndoableFunctionEnters = 0; UndoableTailExits = 0; return W.writeFunctionWithArg(FDRLogWriter::FunctionRecordKind::EnterArg, mask(FuncId), Delta, Arg); } bool functionExit(int32_t FuncId, uint64_t TSC, uint16_t CPU) XRAY_NEVER_INSTRUMENT { if (finalized() || !prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord))) return returnBuffer(); auto PreambleStatus = recordPreamble(TSC, CPU); if (PreambleStatus == PreambleResult::InvalidBuffer) return returnBuffer(); if (PreambleStatus == PreambleResult::NoChange && UndoableFunctionEnters != 0 && TSC - LastFunctionEntryTSC < CycleThreshold) return rewindRecords(FuncId, TSC, CPU); auto Delta = TSC - LatestTSC; LatestTSC = TSC; UndoableFunctionEnters = 0; UndoableTailExits = 0; return W.writeFunction(FDRLogWriter::FunctionRecordKind::Exit, mask(FuncId), Delta); } bool customEvent(uint64_t TSC, uint16_t CPU, const void *Event, int32_t EventSize) XRAY_NEVER_INSTRUMENT { if (finalized() || !prepareBuffer((2 * sizeof(MetadataRecord)) + EventSize) || recordPreamble(TSC, CPU) == PreambleResult::InvalidBuffer) return returnBuffer(); auto Delta = TSC - LatestTSC; LatestTSC = TSC; UndoableFunctionEnters = 0; UndoableTailExits = 0; return W.writeCustomEvent(Delta, Event, EventSize); } bool typedEvent(uint64_t TSC, uint16_t CPU, uint16_t EventType, const void *Event, int32_t EventSize) XRAY_NEVER_INSTRUMENT { if (finalized() || !prepareBuffer((2 * sizeof(MetadataRecord)) + EventSize) || recordPreamble(TSC, CPU) == PreambleResult::InvalidBuffer) return returnBuffer(); auto Delta = TSC - LatestTSC; LatestTSC = TSC; UndoableFunctionEnters = 0; UndoableTailExits = 0; return W.writeTypedEvent(Delta, EventType, Event, EventSize); } bool flush() XRAY_NEVER_INSTRUMENT { if (finalized()) { returnBuffer(); // ignore result. return true; } return returnBuffer(); } }; } // namespace __xray #endif // COMPILER-RT_LIB_XRAY_XRAY_FDR_CONTROLLER_H_