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