//===-- 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_