424 lines
16 KiB
C++
424 lines
16 KiB
C++
//===-- fdr_controller_test.cpp -------------------------------------------===//
|
|
//
|
|
// 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.
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
#include <algorithm>
|
|
#include <memory>
|
|
#include <time.h>
|
|
|
|
#include "test_helpers.h"
|
|
#include "xray/xray_records.h"
|
|
#include "xray_buffer_queue.h"
|
|
#include "xray_fdr_controller.h"
|
|
#include "xray_fdr_log_writer.h"
|
|
#include "llvm/Support/DataExtractor.h"
|
|
#include "llvm/Testing/Support/Error.h"
|
|
#include "llvm/XRay/Trace.h"
|
|
#include "llvm/XRay/XRayRecord.h"
|
|
#include "gmock/gmock.h"
|
|
#include "gtest/gtest.h"
|
|
|
|
namespace __xray {
|
|
namespace {
|
|
|
|
using ::llvm::HasValue;
|
|
using ::llvm::xray::testing::FuncId;
|
|
using ::llvm::xray::testing::HasArg;
|
|
using ::llvm::xray::testing::RecordType;
|
|
using ::llvm::xray::testing::TSCIs;
|
|
using ::testing::AllOf;
|
|
using ::testing::ElementsAre;
|
|
using ::testing::Eq;
|
|
using ::testing::Field;
|
|
using ::testing::Gt;
|
|
using ::testing::IsEmpty;
|
|
using ::testing::SizeIs;
|
|
|
|
class FunctionSequenceTest : public ::testing::Test {
|
|
protected:
|
|
BufferQueue::Buffer B{};
|
|
std::unique_ptr<BufferQueue> BQ;
|
|
std::unique_ptr<FDRLogWriter> W;
|
|
std::unique_ptr<FDRController<>> C;
|
|
|
|
public:
|
|
void SetUp() override {
|
|
bool Success;
|
|
BQ = std::make_unique<BufferQueue>(4096, 1, Success);
|
|
ASSERT_TRUE(Success);
|
|
ASSERT_EQ(BQ->getBuffer(B), BufferQueue::ErrorCode::Ok);
|
|
W = std::make_unique<FDRLogWriter>(B);
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 0);
|
|
}
|
|
};
|
|
|
|
TEST_F(FunctionSequenceTest, DefaultInitFinalizeFlush) {
|
|
ASSERT_TRUE(C->functionEnter(1, 2, 3));
|
|
ASSERT_TRUE(C->functionExit(1, 2, 3));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see we find the expected records.
|
|
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::ENTER)),
|
|
AllOf(FuncId(1), RecordType(llvm::xray::RecordTypes::EXIT)))));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, BoundaryFuncIdEncoding) {
|
|
// We ensure that we can write function id's that are at the boundary of the
|
|
// acceptable function ids.
|
|
int32_t FId = (1 << 28) - 1;
|
|
uint64_t TSC = 2;
|
|
uint16_t CPU = 1;
|
|
ASSERT_TRUE(C->functionEnter(FId, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(FId, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnterArg(FId, TSC++, CPU, 1));
|
|
ASSERT_TRUE(C->functionTailExit(FId, TSC++, CPU));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see we find the expected records.
|
|
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(FId), RecordType(llvm::xray::RecordTypes::ENTER)),
|
|
AllOf(FuncId(FId), RecordType(llvm::xray::RecordTypes::EXIT)),
|
|
AllOf(FuncId(FId), RecordType(llvm::xray::RecordTypes::ENTER_ARG)),
|
|
AllOf(FuncId(FId), RecordType(llvm::xray::RecordTypes::TAIL_EXIT)))));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, ThresholdsAreEnforced) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
ASSERT_TRUE(C->functionEnter(1, 2, 3));
|
|
ASSERT_TRUE(C->functionExit(1, 2, 3));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see we find the *no* records, because
|
|
// the function entry-exit comes under the cycle threshold.
|
|
std::string Serialized = serialize(*BQ, 3);
|
|
llvm::DataExtractor DE(Serialized, true, 8);
|
|
auto TraceOrErr = llvm::xray::loadTrace(DE);
|
|
EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(IsEmpty()));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, ArgsAreHandledAndKept) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
ASSERT_TRUE(C->functionEnterArg(1, 2, 3, 4));
|
|
ASSERT_TRUE(C->functionExit(1, 2, 3));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see we find the function enter arg
|
|
// record with the specified argument.
|
|
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::ENTER_ARG),
|
|
HasArg(4)),
|
|
AllOf(FuncId(1), RecordType(llvm::xray::RecordTypes::EXIT)))));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, PreservedCallsHaveCorrectTSC) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 0;
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC += 1000, CPU));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see if we find the remaining records,
|
|
// because the function entry-exit comes under the cycle threshold.
|
|
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::ENTER),
|
|
TSCIs(Eq(1uL))),
|
|
AllOf(FuncId(1), RecordType(llvm::xray::RecordTypes::EXIT),
|
|
TSCIs(Gt(1000uL))))));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, PreservedCallsSupportLargeDeltas) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 0;
|
|
const auto LargeDelta = uint64_t{std::numeric_limits<int32_t>::max()};
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC += LargeDelta, CPU));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffer then test to see if we find the right TSC with a large
|
|
// delta.
|
|
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::ENTER),
|
|
TSCIs(Eq(1uL))),
|
|
AllOf(FuncId(1), RecordType(llvm::xray::RecordTypes::EXIT),
|
|
TSCIs(Gt(LargeDelta))))));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, RewindingMultipleCalls) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
|
|
// First we construct an arbitrarily deep function enter/call stack.
|
|
// We also ensure that we are in the same CPU.
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(3, TSC++, CPU));
|
|
|
|
// Then we exit them one at a time, in reverse order of entry.
|
|
ASSERT_TRUE(C->functionExit(3, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
|
|
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see we find that all the calls have been
|
|
// unwound because all of them are under the cycle counter threshold.
|
|
std::string Serialized = serialize(*BQ, 3);
|
|
llvm::DataExtractor DE(Serialized, true, 8);
|
|
auto TraceOrErr = llvm::xray::loadTrace(DE);
|
|
EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(IsEmpty()));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, RewindingIntermediaryTailExits) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
|
|
// First we construct an arbitrarily deep function enter/call stack.
|
|
// We also ensure that we are in the same CPU.
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(3, TSC++, CPU));
|
|
|
|
// Next we tail-exit into a new function multiple times.
|
|
ASSERT_TRUE(C->functionTailExit(3, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(4, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionTailExit(4, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(5, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionTailExit(5, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(6, TSC++, CPU));
|
|
|
|
// Then we exit them one at a time, in reverse order of entry.
|
|
ASSERT_TRUE(C->functionExit(6, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize the buffers then test to see we find that all the calls have been
|
|
// unwound because all of them are under the cycle counter threshold.
|
|
std::string Serialized = serialize(*BQ, 3);
|
|
llvm::DataExtractor DE(Serialized, true, 8);
|
|
auto TraceOrErr = llvm::xray::loadTrace(DE);
|
|
EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(IsEmpty()));
|
|
}
|
|
|
|
TEST_F(FunctionSequenceTest, RewindingAfterMigration) {
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 1000);
|
|
|
|
// First we construct an arbitrarily deep function enter/call stack.
|
|
// We also ensure that we are in the same CPU.
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(3, TSC++, CPU));
|
|
|
|
// Next we tail-exit into a new function multiple times.
|
|
ASSERT_TRUE(C->functionTailExit(3, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(4, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionTailExit(4, TSC++, CPU));
|
|
|
|
// But before we enter the next function, we migrate to a different CPU.
|
|
CPU = 2;
|
|
ASSERT_TRUE(C->functionEnter(5, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionTailExit(5, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionEnter(6, TSC++, CPU));
|
|
|
|
// Then we exit them one at a time, in reverse order of entry.
|
|
ASSERT_TRUE(C->functionExit(6, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(2, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
|
|
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// Serialize buffers then test that we can find all the events that span the
|
|
// CPU migration.
|
|
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::ENTER)),
|
|
AllOf(FuncId(2), RecordType(llvm::xray::RecordTypes::ENTER)),
|
|
AllOf(FuncId(2), RecordType(llvm::xray::RecordTypes::EXIT)),
|
|
AllOf(FuncId(1), RecordType(llvm::xray::RecordTypes::EXIT)))));
|
|
}
|
|
|
|
class BufferManagementTest : public ::testing::Test {
|
|
protected:
|
|
BufferQueue::Buffer B{};
|
|
std::unique_ptr<BufferQueue> BQ;
|
|
std::unique_ptr<FDRLogWriter> W;
|
|
std::unique_ptr<FDRController<>> C;
|
|
|
|
static constexpr size_t kBuffers = 10;
|
|
|
|
public:
|
|
void SetUp() override {
|
|
bool Success;
|
|
BQ = std::make_unique<BufferQueue>(sizeof(MetadataRecord) * 5 +
|
|
sizeof(FunctionRecord) * 2,
|
|
kBuffers, Success);
|
|
ASSERT_TRUE(Success);
|
|
ASSERT_EQ(BQ->getBuffer(B), BufferQueue::ErrorCode::Ok);
|
|
W = std::make_unique<FDRLogWriter>(B);
|
|
C = std::make_unique<FDRController<>>(BQ.get(), B, *W, clock_gettime, 0);
|
|
}
|
|
};
|
|
|
|
constexpr size_t BufferManagementTest::kBuffers;
|
|
|
|
TEST_F(BufferManagementTest, HandlesOverflow) {
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
for (size_t I = 0; I < kBuffers + 1; ++I) {
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
|
|
}
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_THAT(BQ->finalize(), Eq(BufferQueue::ErrorCode::Ok));
|
|
|
|
std::string Serialized = serialize(*BQ, 3);
|
|
llvm::DataExtractor DE(Serialized, true, 8);
|
|
auto TraceOrErr = llvm::xray::loadTrace(DE);
|
|
EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(SizeIs(kBuffers * 2)));
|
|
}
|
|
|
|
TEST_F(BufferManagementTest, HandlesOverflowWithArgs) {
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
uint64_t ARG = 1;
|
|
for (size_t I = 0; I < kBuffers + 1; ++I) {
|
|
ASSERT_TRUE(C->functionEnterArg(1, TSC++, CPU, ARG++));
|
|
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
|
|
}
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_THAT(BQ->finalize(), Eq(BufferQueue::ErrorCode::Ok));
|
|
|
|
std::string Serialized = serialize(*BQ, 3);
|
|
llvm::DataExtractor DE(Serialized, true, 8);
|
|
auto TraceOrErr = llvm::xray::loadTrace(DE);
|
|
EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(SizeIs(kBuffers)));
|
|
}
|
|
|
|
TEST_F(BufferManagementTest, HandlesOverflowWithCustomEvents) {
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
int32_t D = 0x9009;
|
|
for (size_t I = 0; I < kBuffers; ++I) {
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->functionExit(1, TSC++, CPU));
|
|
ASSERT_TRUE(C->customEvent(TSC++, CPU, &D, sizeof(D)));
|
|
}
|
|
ASSERT_TRUE(C->flush());
|
|
ASSERT_THAT(BQ->finalize(), Eq(BufferQueue::ErrorCode::Ok));
|
|
|
|
std::string Serialized = serialize(*BQ, 3);
|
|
llvm::DataExtractor DE(Serialized, true, 8);
|
|
auto TraceOrErr = llvm::xray::loadTrace(DE);
|
|
|
|
// We expect to also now count the kBuffers/2 custom event records showing up
|
|
// in the Trace.
|
|
EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(SizeIs(kBuffers + (kBuffers / 2))));
|
|
}
|
|
|
|
TEST_F(BufferManagementTest, HandlesFinalizedBufferQueue) {
|
|
uint64_t TSC = 1;
|
|
uint16_t CPU = 1;
|
|
|
|
// First write one function entry.
|
|
ASSERT_TRUE(C->functionEnter(1, TSC++, CPU));
|
|
|
|
// Then we finalize the buffer queue, simulating the case where the logging
|
|
// has been finalized.
|
|
ASSERT_EQ(BQ->finalize(), BufferQueue::ErrorCode::Ok);
|
|
|
|
// At this point further calls to the controller must fail.
|
|
ASSERT_FALSE(C->functionExit(1, TSC++, CPU));
|
|
|
|
// But flushing should succeed.
|
|
ASSERT_TRUE(C->flush());
|
|
|
|
// We expect that we'll only be able to find the function enter event, but not
|
|
// the function exit event.
|
|
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::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
|