|  | //===-- 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 <limits> | 
|  | #include <time.h> | 
|  |  | 
|  | #include "xray/xray_interface.h" | 
|  | #include "xray/xray_records.h" | 
|  | #include "xray_buffer_queue.h" | 
|  | #include "xray_fdr_log_writer.h" | 
|  |  | 
|  | namespace __xray { | 
|  |  | 
|  | template <size_t Version = 5> 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<char *>(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<MetadataRecord::RecordKinds::NewBuffer>( | 
|  | static_cast<int32_t>(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<MetadataRecord::RecordKinds::WalltimeMarker>( | 
|  | static_cast<int64_t>(TS.tv_sec), | 
|  | static_cast<int32_t>(TS.tv_nsec / 1000)), | 
|  |  | 
|  | // Also write the Pid record. | 
|  | createMetadataRecord<MetadataRecord::RecordKinds::Pid>( | 
|  | static_cast<int32_t>(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<MetadataRecord::RecordKinds::NewCPUId>(CPU, TSC); | 
|  | return PreambleResult::WroteMetadata; | 
|  | } | 
|  |  | 
|  | DCHECK_EQ(LatestCPU, CPU); | 
|  |  | 
|  | if (UNLIKELY(LatestTSC > TSC || | 
|  | TSC - LatestTSC > | 
|  | uint64_t{std::numeric_limits<int32_t>::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<MetadataRecord::RecordKinds::TSCWrap>(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 <class WallClockFunc> | 
|  | 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_ |