public:
void SetUp() override {
bool Success;
- BQ = llvm::make_unique<BufferQueue>(sizeof(MetadataRecord) * 4 +
+ BQ = llvm::make_unique<BufferQueue>(sizeof(MetadataRecord) * 5 +
sizeof(FunctionRecord) * 2,
kBuffers, Success);
ASSERT_TRUE(Success);
TEST_F(BufferManagementTest, HandlesOverflow) {
uint64_t TSC = 1;
uint16_t CPU = 1;
- for (size_t I = 0; I < kBuffers; ++I) {
+ for (size_t I = 0; I < kBuffers + 1; ++I) {
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
}
- C->flush();
- ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
+ ASSERT_TRUE(C->flush());
+ ASSERT_THAT(BQ->finalize(), Eq(BufferQueue::ErrorCode::Ok));
std::string Serialized = serialize(*BQ, 3);
llvm::DataExtractor DE(Serialized, true, 8);
FuncId(1), RecordType(llvm::xray::RecordTypes::ENTER)))));
}
+TEST_F(BufferManagementTest, HandlesGenerationalBufferQueue) {
+ uint64_t TSC = 1;
+ uint16_t CPU = 1;
+
+ ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
+ ASSERT_THAT(BQ->finalize(), Eq(BufferQueue::ErrorCode::Ok));
+ ASSERT_THAT(BQ->init(sizeof(MetadataRecord) * 4 + sizeof(FunctionRecord) * 2,
+ kBuffers),
+ Eq(BufferQueue::ErrorCode::Ok));
+ EXPECT_TRUE(C->functionExit(1, TSC++, CPU));
+ ASSERT_TRUE(C->flush());
+
+ // We expect that we will only be able to find the function exit event, but
+ // not the function enter event, since we only have information about the new
+ // generation of the buffers.
+ std::string Serialized = serialize(*BQ, 3);
+ llvm::DataExtractor DE(Serialized, true, 8);
+ auto TraceOrErr = llvm::xray::loadTrace(DE);
+ EXPECT_THAT_EXPECTED(
+ TraceOrErr, HasValue(ElementsAre(AllOf(
+ FuncId(1), RecordType(llvm::xray::RecordTypes::EXIT)))));
+}
+
} // namespace
} // namespace __xray
bool finalized() const { return BQ == nullptr || BQ->finalizing(); }
bool hasSpace(size_t S) {
- return B.Data != nullptr &&
+ return B.Data != nullptr && B.Generation == BQ->generation() &&
W.getNextRecord() + S <= reinterpret_cast<char *>(B.Data) + B.Size;
}
}
bool getNewBuffer() {
- if (!returnBuffer())
- return false;
if (BQ->getBuffer(B) != BufferQueue::ErrorCode::Ok)
return false;
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;
}
return returnBuffer();
if (UNLIKELY(!hasSpace(S))) {
+ if (!returnBuffer())
+ return false;
if (!getNewBuffer())
return false;
if (!setupNewBuffer())
if (BQ == nullptr)
return false;
+ First = true;
if (finalized()) {
BQ->releaseBuffer(B); // ignore result.
return false;
}
- First = true;
- if (BQ->releaseBuffer(B) != BufferQueue::ErrorCode::Ok)
- return false;
- return true;
+ return BQ->releaseBuffer(B) == BufferQueue::ErrorCode::Ok;
}
- enum class PreambleResult { NoChange, WroteMetadata };
+ enum class PreambleResult { NoChange, WroteMetadata, InvalidBuffer };
PreambleResult functionPreamble(uint64_t TSC, uint16_t CPU) {
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;
}
if (UNLIKELY(LatestCPU == LatestCPU && LatestTSC > TSC)) {
// The TSC has wrapped around, from the last TSC we've seen.
LatestTSC = TSC;
+
+ if (B.Generation != BQ->generation())
+ return PreambleResult::InvalidBuffer;
+
W.writeMetadata<MetadataRecord::RecordKinds::TSCWrap>(TSC);
return PreambleResult::WroteMetadata;
}
return PreambleResult::NoChange;
}
- void rewindRecords(int32_t FuncId, uint64_t TSC, uint16_t CPU) {
+ bool rewindRecords(int32_t FuncId, uint64_t TSC, uint16_t CPU) {
// Undo one enter record, because at this point we are either at the state
// of:
// - We are exiting a function that we recently entered.
//
FunctionRecord F;
W.undoWrites(sizeof(FunctionRecord));
+ if (B.Generation != BQ->generation())
+ return false;
internal_memcpy(&F, W.getNextRecord(), sizeof(FunctionRecord));
DCHECK(F.RecordKind ==
LatestTSC -= F.TSCDelta;
if (--UndoableFunctionEnters != 0) {
LastFunctionEntryTSC -= F.TSCDelta;
- return;
+ 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;
+ return true;
}
--UndoableTailExits;
W.undoWrites(sizeof(FunctionRecord) * 2);
LatestTSC = RewindingTSC;
}
+ return true;
}
public:
: BQ(BQ), B(B), W(W), WallClockReader(R), CycleThreshold(C) {}
bool functionEnter(int32_t FuncId, uint64_t TSC, uint16_t CPU) {
- if (finalized())
+ if (finalized() ||
+ !prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord)))
return returnBuffer();
- if (!prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord)))
+ auto PreambleStatus = functionPreamble(TSC, CPU);
+ if (PreambleStatus == PreambleResult::InvalidBuffer)
return returnBuffer();
- if (functionPreamble(TSC, CPU) == PreambleResult::WroteMetadata) {
- UndoableFunctionEnters = 1;
- } else {
- ++UndoableFunctionEnters;
- }
-
+ UndoableFunctionEnters = (PreambleStatus == PreambleResult::WroteMetadata)
+ ? 1
+ : UndoableFunctionEnters + 1;
LastFunctionEntryTSC = TSC;
LatestTSC = TSC;
return W.writeFunction(FDRLogWriter::FunctionRecordKind::Enter,
if (!prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord)))
return returnBuffer();
- if (functionPreamble(TSC, CPU) == PreambleResult::NoChange &&
+ auto PreambleStatus = functionPreamble(TSC, CPU);
+ if (PreambleStatus == PreambleResult::InvalidBuffer)
+ return returnBuffer();
+
+ if (PreambleStatus == PreambleResult::NoChange &&
UndoableFunctionEnters != 0 &&
- TSC - LastFunctionEntryTSC < CycleThreshold) {
- rewindRecords(FuncId, TSC, CPU);
- return true;
- }
+ TSC - LastFunctionEntryTSC < CycleThreshold)
+ return rewindRecords(FuncId, TSC, CPU);
UndoableTailExits = UndoableFunctionEnters ? UndoableTailExits + 1 : 0;
UndoableFunctionEnters = 0;
bool functionEnterArg(int32_t FuncId, uint64_t TSC, uint16_t CPU,
uint64_t Arg) {
- if (finalized())
+ if (finalized() ||
+ !prepareBuffer((2 * sizeof(MetadataRecord)) + sizeof(FunctionRecord)) ||
+ functionPreamble(TSC, CPU) == PreambleResult::InvalidBuffer)
return returnBuffer();
- if (!prepareBuffer((2 * sizeof(MetadataRecord)) + sizeof(FunctionRecord)))
- return returnBuffer();
-
- // Ignore the result of writing out the preamble.
- functionPreamble(TSC, CPU);
-
LatestTSC = TSC;
LastFunctionEntryTSC = 0;
UndoableFunctionEnters = 0;
}
bool functionExit(int32_t FuncId, uint64_t TSC, uint16_t CPU) {
- if (finalized())
+ if (finalized() ||
+ !prepareBuffer(sizeof(MetadataRecord) + sizeof(FunctionRecord)))
return returnBuffer();
- if (functionPreamble(TSC, CPU) == PreambleResult::NoChange &&
+ auto PreambleStatus = functionPreamble(TSC, CPU);
+ if (PreambleStatus == PreambleResult::InvalidBuffer)
+ return returnBuffer();
+
+ if (PreambleStatus == PreambleResult::NoChange &&
UndoableFunctionEnters != 0 &&
- TSC - LastFunctionEntryTSC < CycleThreshold) {
- rewindRecords(FuncId, TSC, CPU);
- return true;
- }
+ TSC - LastFunctionEntryTSC < CycleThreshold)
+ return rewindRecords(FuncId, TSC, CPU);
LatestTSC = TSC;
UndoableFunctionEnters = 0;