diff options
Diffstat (limited to 'lib/xray/tests')
-rw-r--r-- | lib/xray/tests/CMakeLists.txt | 47 | ||||
-rw-r--r-- | lib/xray/tests/unit/CMakeLists.txt | 28 | ||||
-rw-r--r-- | lib/xray/tests/unit/allocator_test.cc | 42 | ||||
-rw-r--r-- | lib/xray/tests/unit/buffer_queue_test.cc | 129 | ||||
-rw-r--r-- | lib/xray/tests/unit/fdr_controller_test.cc | 424 | ||||
-rw-r--r-- | lib/xray/tests/unit/fdr_log_writer_test.cc | 162 | ||||
-rw-r--r-- | lib/xray/tests/unit/fdr_logging_test.cc | 202 | ||||
-rw-r--r-- | lib/xray/tests/unit/function_call_trie_test.cc | 168 | ||||
-rw-r--r-- | lib/xray/tests/unit/profile_collector_test.cc | 65 | ||||
-rw-r--r-- | lib/xray/tests/unit/segmented_array_test.cc | 149 | ||||
-rw-r--r-- | lib/xray/tests/unit/test_helpers.cc | 95 | ||||
-rw-r--r-- | lib/xray/tests/unit/test_helpers.h | 78 |
12 files changed, 1288 insertions, 301 deletions
diff --git a/lib/xray/tests/CMakeLists.txt b/lib/xray/tests/CMakeLists.txt index 11f373167d247..89a2b3b01ed8a 100644 --- a/lib/xray/tests/CMakeLists.txt +++ b/lib/xray/tests/CMakeLists.txt @@ -19,9 +19,16 @@ set(XRAY_UNITTEST_CFLAGS ${XRAY_CFLAGS} ${COMPILER_RT_UNITTEST_CFLAGS} ${COMPILER_RT_GTEST_CFLAGS} + ${COMPILER_RT_GMOCK_CFLAGS} -I${COMPILER_RT_SOURCE_DIR}/include -I${COMPILER_RT_SOURCE_DIR}/lib/xray - -I${COMPILER_RT_SOURCE_DIR}/lib) + -I${COMPILER_RT_SOURCE_DIR}/lib + ) + +# We add the include directories one at a time in our CFLAGS. +foreach (DIR ${LLVM_INCLUDE_DIR} ${LLVM_MAIN_INCLUDE_DIR}) + list(APPEND XRAY_UNITTEST_CFLAGS -I${DIR}) +endforeach() function(add_xray_lib library) add_library(${library} STATIC ${ARGN}) @@ -42,10 +49,31 @@ endfunction() set(XRAY_TEST_ARCH ${XRAY_SUPPORTED_ARCH}) set(XRAY_UNITTEST_LINK_FLAGS ${CMAKE_THREAD_LIBS_INIT} - -l${SANITIZER_CXX_ABI_LIBRARY} - -fxray-instrument - ) + -l${SANITIZER_CXX_ABI_LIBRARY}) + if (NOT APPLE) + # Needed by LLVMSupport. + append_list_if( + COMPILER_RT_HAS_TERMINFO + -l${COMPILER_RT_TERMINFO_LIB} XRAY_UNITTEST_LINK_FLAGS) + + if (COMPILER_RT_STANDALONE_BUILD) + append_list_if(COMPILER_RT_HAS_LLVMXRAY ${LLVM_XRAY_LDFLAGS} XRAY_UNITTEST_LINK_FLAGS) + append_list_if(COMPILER_RT_HAS_LLVMXRAY ${LLVM_XRAY_LIBLIST} XRAY_UNITTEST_LINK_FLAGS) + append_list_if(COMPILER_RT_HAS_LLVMTESTINGSUPPORT + ${LLVM_TESTINGSUPPORT_LDFLAGS} XRAY_UNITTEST_LINK_FLAGS) + append_list_if(COMPILER_RT_HAS_LLVMTESTINGSUPPORT + ${LLVM_TESTINGSUPPORT_LIBLIST} XRAY_UNITTEST_LINK_FLAGS) + else() + # We add the library directories one at a time in our CFLAGS. + foreach (DIR ${LLVM_LIBRARY_DIR}) + list(APPEND XRAY_UNITTEST_LINK_FLAGS -L${DIR}) + endforeach() + + # We also add the actual libraries to link as dependencies. + list(APPEND XRAY_UNITTEST_LINK_FLAGS -lLLVMXRay -lLLVMSupport -lLLVMTestingSupport) + endif() + append_list_if(COMPILER_RT_HAS_LIBM -lm XRAY_UNITTEST_LINK_FLAGS) append_list_if(COMPILER_RT_HAS_LIBRT -lrt XRAY_UNITTEST_LINK_FLAGS) append_list_if(COMPILER_RT_HAS_LIBDL -ldl XRAY_UNITTEST_LINK_FLAGS) @@ -62,17 +90,22 @@ macro(add_xray_unittest testname) generate_compiler_rt_tests(TEST_OBJECTS XRayUnitTests "${testname}-${arch}-Test" "${arch}" SOURCES ${TEST_SOURCES} ${COMPILER_RT_GTEST_SOURCE} + ${COMPILER_RT_GMOCK_SOURCE} + # Note that any change in the implementations will cause all the unit # tests to be re-built. This is by design, but may be cumbersome during # the build/test cycle. COMPILE_DEPS ${TEST_SOURCES} ${COMPILER_RT_GTEST_SOURCE} ${XRAY_HEADERS} ${XRAY_ALL_SOURCE_FILES_ABS_PATHS} + "test_helpers.h" RUNTIME "${XRAY_RUNTIME_LIBS}" - DEPS gtest xray llvm-xray + DEPS gtest xray llvm-xray LLVMXRay LLVMTestingSupport CFLAGS ${XRAY_UNITTEST_CFLAGS} - LINK_FLAGS ${TARGET_LINK_FLAGS} ${XRAY_UNITTEST_LINK_FLAGS}) + LINK_FLAGS ${TARGET_LINK_FLAGS} ${XRAY_UNITTEST_LINK_FLAGS} + ) set_target_properties(XRayUnitTests - PROPERTIES RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}) endforeach() endif() endmacro() diff --git a/lib/xray/tests/unit/CMakeLists.txt b/lib/xray/tests/unit/CMakeLists.txt index b42eb50d07907..d10524b8d030a 100644 --- a/lib/xray/tests/unit/CMakeLists.txt +++ b/lib/xray/tests/unit/CMakeLists.txt @@ -1,12 +1,16 @@ -add_xray_unittest(XRayBufferQueueTest SOURCES - buffer_queue_test.cc xray_unit_test_main.cc) -add_xray_unittest(XRayFDRLoggingTest SOURCES - fdr_logging_test.cc xray_unit_test_main.cc) -add_xray_unittest(XRayAllocatorTest SOURCES - allocator_test.cc xray_unit_test_main.cc) -add_xray_unittest(XRaySegmentedArrayTest SOURCES - segmented_array_test.cc xray_unit_test_main.cc) -add_xray_unittest(XRayFunctionCallTrieTest SOURCES - function_call_trie_test.cc xray_unit_test_main.cc) -add_xray_unittest(XRayProfileCollectorTest SOURCES - profile_collector_test.cc xray_unit_test_main.cc) +set(TEST_SOURCES + allocator_test.cc + buffer_queue_test.cc + function_call_trie_test.cc + profile_collector_test.cc + segmented_array_test.cc + test_helpers.cc + xray_unit_test_main.cc) + +if (NOT COMPILER_RT_STANDALONE_BUILD OR COMPILER_RT_HAS_LLVMTESTINGSUPPORT) + list(APPEND TEST_SOURCES + fdr_controller_test.cc + fdr_log_writer_test.cc) +endif() + +add_xray_unittest(XRayTest SOURCES ${TEST_SOURCES}) diff --git a/lib/xray/tests/unit/allocator_test.cc b/lib/xray/tests/unit/allocator_test.cc index be404160e4177..1170741623cbe 100644 --- a/lib/xray/tests/unit/allocator_test.cc +++ b/lib/xray/tests/unit/allocator_test.cc @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "xray_allocator.h" +#include "xray_buffer_queue.h" #include "gtest/gtest.h" namespace __xray { @@ -33,10 +34,49 @@ TEST(AllocatorTest, Allocate) { TEST(AllocatorTest, OverAllocate) { Allocator<sizeof(TestData)> A(sizeof(TestData)); auto B1 = A.Allocate(); - (void)B1; + ASSERT_NE(B1.Data, nullptr); auto B2 = A.Allocate(); ASSERT_EQ(B2.Data, nullptr); } +struct OddSizedData { + s64 A; + s32 B; +}; + +TEST(AllocatorTest, AllocateBoundaries) { + Allocator<sizeof(OddSizedData)> A(GetPageSizeCached()); + + // Keep allocating until we hit a nullptr block. + unsigned C = 0; + auto Expected = + GetPageSizeCached() / RoundUpTo(sizeof(OddSizedData), kCacheLineSize); + for (auto B = A.Allocate(); B.Data != nullptr; B = A.Allocate(), ++C) + ; + + ASSERT_EQ(C, Expected); +} + +TEST(AllocatorTest, AllocateFromNonOwned) { + bool Success = false; + BufferQueue BQ(GetPageSizeCached(), 10, Success); + ASSERT_TRUE(Success); + BufferQueue::Buffer B; + ASSERT_EQ(BQ.getBuffer(B), BufferQueue::ErrorCode::Ok); + { + Allocator<sizeof(OddSizedData)> A(B.Data, B.Size); + + // Keep allocating until we hit a nullptr block. + unsigned C = 0; + auto Expected = + GetPageSizeCached() / RoundUpTo(sizeof(OddSizedData), kCacheLineSize); + for (auto B = A.Allocate(); B.Data != nullptr; B = A.Allocate(), ++C) + ; + + ASSERT_EQ(C, Expected); + } + ASSERT_EQ(BQ.releaseBuffer(B), BufferQueue::ErrorCode::Ok); +} + } // namespace } // namespace __xray diff --git a/lib/xray/tests/unit/buffer_queue_test.cc b/lib/xray/tests/unit/buffer_queue_test.cc index c0d4ccb268d6c..a30343e188f28 100644 --- a/lib/xray/tests/unit/buffer_queue_test.cc +++ b/lib/xray/tests/unit/buffer_queue_test.cc @@ -11,15 +11,21 @@ // //===----------------------------------------------------------------------===// #include "xray_buffer_queue.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include <atomic> #include <future> +#include <thread> #include <unistd.h> namespace __xray { +namespace { static constexpr size_t kSize = 4096; +using ::testing::Eq; + TEST(BufferQueueTest, API) { bool Success = false; BufferQueue Buffers(kSize, 1, Success); @@ -55,8 +61,13 @@ TEST(BufferQueueTest, ReleaseUnknown) { BufferQueue::Buffer Buf; Buf.Data = reinterpret_cast<void *>(0xdeadbeef); Buf.Size = kSize; - EXPECT_EQ(BufferQueue::ErrorCode::UnrecognizedBuffer, - Buffers.releaseBuffer(Buf)); + Buf.Generation = Buffers.generation(); + + BufferQueue::Buffer Known; + EXPECT_THAT(Buffers.getBuffer(Known), Eq(BufferQueue::ErrorCode::Ok)); + EXPECT_THAT(Buffers.releaseBuffer(Buf), + Eq(BufferQueue::ErrorCode::UnrecognizedBuffer)); + EXPECT_THAT(Buffers.releaseBuffer(Known), Eq(BufferQueue::ErrorCode::Ok)); } TEST(BufferQueueTest, ErrorsWhenFinalising) { @@ -70,8 +81,7 @@ TEST(BufferQueueTest, ErrorsWhenFinalising) { BufferQueue::Buffer OtherBuf; ASSERT_EQ(BufferQueue::ErrorCode::QueueFinalizing, Buffers.getBuffer(OtherBuf)); - ASSERT_EQ(BufferQueue::ErrorCode::QueueFinalizing, - Buffers.finalize()); + ASSERT_EQ(BufferQueue::ErrorCode::QueueFinalizing, Buffers.finalize()); ASSERT_EQ(Buffers.releaseBuffer(Buf), BufferQueue::ErrorCode::Ok); } @@ -111,4 +121,115 @@ TEST(BufferQueueTest, Apply) { ASSERT_EQ(Count, 10); } +TEST(BufferQueueTest, GenerationalSupport) { + bool Success = false; + BufferQueue Buffers(kSize, 10, Success); + ASSERT_TRUE(Success); + BufferQueue::Buffer B0; + ASSERT_EQ(Buffers.getBuffer(B0), BufferQueue::ErrorCode::Ok); + ASSERT_EQ(Buffers.finalize(), + BufferQueue::ErrorCode::Ok); // No more new buffers. + + // Re-initialise the queue. + ASSERT_EQ(Buffers.init(kSize, 10), BufferQueue::ErrorCode::Ok); + + BufferQueue::Buffer B1; + ASSERT_EQ(Buffers.getBuffer(B1), BufferQueue::ErrorCode::Ok); + + // Validate that the buffers come from different generations. + ASSERT_NE(B0.Generation, B1.Generation); + + // We stash the current generation, for use later. + auto PrevGen = B1.Generation; + + // At this point, we want to ensure that we can return the buffer from the + // first "generation" would still be accepted in the new generation... + EXPECT_EQ(Buffers.releaseBuffer(B0), BufferQueue::ErrorCode::Ok); + + // ... and that the new buffer is also accepted. + EXPECT_EQ(Buffers.releaseBuffer(B1), BufferQueue::ErrorCode::Ok); + + // A next round will do the same, ensure that we are able to do multiple + // rounds in this case. + ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); + ASSERT_EQ(Buffers.init(kSize, 10), BufferQueue::ErrorCode::Ok); + EXPECT_EQ(Buffers.getBuffer(B0), BufferQueue::ErrorCode::Ok); + EXPECT_EQ(Buffers.getBuffer(B1), BufferQueue::ErrorCode::Ok); + + // Here we ensure that the generation is different from the previous + // generation. + EXPECT_NE(B0.Generation, PrevGen); + EXPECT_EQ(B1.Generation, B1.Generation); + ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); + EXPECT_EQ(Buffers.releaseBuffer(B0), BufferQueue::ErrorCode::Ok); + EXPECT_EQ(Buffers.releaseBuffer(B1), BufferQueue::ErrorCode::Ok); +} + +TEST(BufferQueueTest, GenerationalSupportAcrossThreads) { + bool Success = false; + BufferQueue Buffers(kSize, 10, Success); + ASSERT_TRUE(Success); + + std::atomic<int> Counter{0}; + + // This function allows us to use thread-local storage to isolate the + // instances of the buffers to be used. It also allows us signal the threads + // of a new generation, and allow those to get new buffers. This is + // representative of how we expect the buffer queue to be used by the XRay + // runtime. + auto Process = [&] { + thread_local BufferQueue::Buffer B; + ASSERT_EQ(Buffers.getBuffer(B), BufferQueue::ErrorCode::Ok); + auto FirstGen = B.Generation; + + // Signal that we've gotten a buffer in the thread. + Counter.fetch_add(1, std::memory_order_acq_rel); + while (!Buffers.finalizing()) { + Buffers.releaseBuffer(B); + Buffers.getBuffer(B); + } + + // Signal that we've exited the get/release buffer loop. + Counter.fetch_sub(1, std::memory_order_acq_rel); + if (B.Data != nullptr) + Buffers.releaseBuffer(B); + + // Spin until we find that the Buffer Queue is no longer finalizing. + while (Buffers.getBuffer(B) != BufferQueue::ErrorCode::Ok) + ; + + // Signal that we've successfully gotten a buffer in the thread. + Counter.fetch_add(1, std::memory_order_acq_rel); + + EXPECT_NE(FirstGen, B.Generation); + EXPECT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); + + // Signal that we've successfully exited. + Counter.fetch_sub(1, std::memory_order_acq_rel); + }; + + // Spawn two threads running Process. + std::thread T0(Process), T1(Process); + + // Spin until we find the counter is up to 2. + while (Counter.load(std::memory_order_acquire) != 2) + ; + + // Then we finalize, then re-initialize immediately. + Buffers.finalize(); + + // Spin until we find the counter is down to 0. + while (Counter.load(std::memory_order_acquire) != 0) + ; + + // Then we re-initialize. + EXPECT_EQ(Buffers.init(kSize, 10), BufferQueue::ErrorCode::Ok); + + T0.join(); + T1.join(); + + ASSERT_EQ(Counter.load(std::memory_order_acquire), 0); +} + +} // namespace } // namespace __xray diff --git a/lib/xray/tests/unit/fdr_controller_test.cc b/lib/xray/tests/unit/fdr_controller_test.cc new file mode 100644 index 0000000000000..8967c4919ae67 --- /dev/null +++ b/lib/xray/tests/unit/fdr_controller_test.cc @@ -0,0 +1,424 @@ +//===-- fdr_controller_test.cc --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// 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 = llvm::make_unique<BufferQueue>(4096, 1, Success); + ASSERT_TRUE(Success); + ASSERT_EQ(BQ->getBuffer(B), BufferQueue::ErrorCode::Ok); + W = llvm::make_unique<FDRLogWriter>(B); + C = llvm::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 = llvm::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 = llvm::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 = llvm::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 = llvm::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 = llvm::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 = llvm::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 = llvm::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 = llvm::make_unique<BufferQueue>(sizeof(MetadataRecord) * 5 + + sizeof(FunctionRecord) * 2, + kBuffers, Success); + ASSERT_TRUE(Success); + ASSERT_EQ(BQ->getBuffer(B), BufferQueue::ErrorCode::Ok); + W = llvm::make_unique<FDRLogWriter>(B); + C = llvm::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 diff --git a/lib/xray/tests/unit/fdr_log_writer_test.cc b/lib/xray/tests/unit/fdr_log_writer_test.cc new file mode 100644 index 0000000000000..f2e7a5cba5d15 --- /dev/null +++ b/lib/xray/tests/unit/fdr_log_writer_test.cc @@ -0,0 +1,162 @@ +//===-- fdr_log_writer_test.cc --------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of XRay, a function call tracing system. +// +//===----------------------------------------------------------------------===// +#include <time.h> + +#include "test_helpers.h" +#include "xray/xray_records.h" +#include "xray_fdr_log_writer.h" +#include "llvm/Support/DataExtractor.h" +#include "llvm/Testing/Support/Error.h" +#include "llvm/XRay/Trace.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace __xray { +namespace { + +static constexpr size_t kSize = 4096; + +using ::llvm::HasValue; +using ::llvm::xray::testing::FuncId; +using ::llvm::xray::testing::RecordType; +using ::testing::AllOf; +using ::testing::ElementsAre; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsNull; + +// Exercise the common code path where we initialize a buffer and are able to +// write some records successfully. +TEST(FdrLogWriterTest, WriteSomeRecords) { + bool Success = false; + BufferQueue Buffers(kSize, 1, Success); + BufferQueue::Buffer B; + ASSERT_EQ(Buffers.getBuffer(B), BufferQueue::ErrorCode::Ok); + + FDRLogWriter Writer(B); + MetadataRecord Preamble[] = { + createMetadataRecord<MetadataRecord::RecordKinds::NewBuffer>(int32_t{1}), + createMetadataRecord<MetadataRecord::RecordKinds::WalltimeMarker>( + int64_t{1}, int32_t{2}), + createMetadataRecord<MetadataRecord::RecordKinds::Pid>(int32_t{1}), + }; + ASSERT_THAT(Writer.writeMetadataRecords(Preamble), + Eq(sizeof(MetadataRecord) * 3)); + ASSERT_TRUE(Writer.writeMetadata<MetadataRecord::RecordKinds::NewCPUId>(1)); + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Enter, 1, 1)); + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Exit, 1, 1)); + ASSERT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); + ASSERT_EQ(B.Data, nullptr); + ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); + + // We then need to go through each element of the Buffers, and re-create a + // flat buffer that we would see if they were laid out in a file. This also + // means we need to write out the header manually. + std::string Serialized = serialize(Buffers, 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))))); +} + +// Ensure that we can handle buffer re-use. +TEST(FdrLogWriterTest, ReuseBuffers) { + bool Success = false; + BufferQueue Buffers(kSize, 1, Success); + BufferQueue::Buffer B; + ASSERT_EQ(Buffers.getBuffer(B), BufferQueue::ErrorCode::Ok); + + FDRLogWriter Writer(B); + MetadataRecord Preamble[] = { + createMetadataRecord<MetadataRecord::RecordKinds::NewBuffer>(int32_t{1}), + createMetadataRecord<MetadataRecord::RecordKinds::WalltimeMarker>( + int64_t{1}, int32_t{2}), + createMetadataRecord<MetadataRecord::RecordKinds::Pid>(int32_t{1}), + }; + + // First we write the first set of records into the single buffer in the + // queue which includes one enter and one exit record. + ASSERT_THAT(Writer.writeMetadataRecords(Preamble), + Eq(sizeof(MetadataRecord) * 3)); + ASSERT_TRUE(Writer.writeMetadata<MetadataRecord::RecordKinds::NewCPUId>( + uint16_t{1}, uint64_t{1})); + uint64_t TSC = 1; + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Enter, 1, TSC++)); + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Exit, 1, TSC++)); + ASSERT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); + ASSERT_THAT(B.Data, IsNull()); + + // Then we re-use the buffer, but only write one record. + ASSERT_EQ(Buffers.getBuffer(B), BufferQueue::ErrorCode::Ok); + Writer.resetRecord(); + ASSERT_THAT(Writer.writeMetadataRecords(Preamble), + Eq(sizeof(MetadataRecord) * 3)); + ASSERT_TRUE(Writer.writeMetadata<MetadataRecord::RecordKinds::NewCPUId>( + uint16_t{1}, uint64_t{1})); + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Enter, 1, TSC++)); + ASSERT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); + ASSERT_THAT(B.Data, IsNull()); + ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); + + // Then we validate that we only see the single enter record. + std::string Serialized = serialize(Buffers, 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(FdrLogWriterTest, UnwriteRecords) { + bool Success = false; + BufferQueue Buffers(kSize, 1, Success); + BufferQueue::Buffer B; + ASSERT_EQ(Buffers.getBuffer(B), BufferQueue::ErrorCode::Ok); + + FDRLogWriter Writer(B); + MetadataRecord Preamble[] = { + createMetadataRecord<MetadataRecord::RecordKinds::NewBuffer>(int32_t{1}), + createMetadataRecord<MetadataRecord::RecordKinds::WalltimeMarker>( + int64_t{1}, int32_t{2}), + createMetadataRecord<MetadataRecord::RecordKinds::Pid>(int32_t{1}), + }; + ASSERT_THAT(Writer.writeMetadataRecords(Preamble), + Eq(sizeof(MetadataRecord) * 3)); + ASSERT_TRUE(Writer.writeMetadata<MetadataRecord::RecordKinds::NewCPUId>(1)); + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Enter, 1, 1)); + ASSERT_TRUE( + Writer.writeFunction(FDRLogWriter::FunctionRecordKind::Exit, 1, 1)); + Writer.undoWrites(sizeof(FunctionRecord) * 2); + ASSERT_EQ(Buffers.releaseBuffer(B), BufferQueue::ErrorCode::Ok); + ASSERT_EQ(B.Data, nullptr); + ASSERT_EQ(Buffers.finalize(), BufferQueue::ErrorCode::Ok); + + // We've un-done the two function records we've written, and now we expect + // that we don't have any function records in the trace. + std::string Serialized = serialize(Buffers, 3); + llvm::DataExtractor DE(Serialized, true, 8); + auto TraceOrErr = llvm::xray::loadTrace(DE); + EXPECT_THAT_EXPECTED(TraceOrErr, HasValue(IsEmpty())); +} + +} // namespace +} // namespace __xray diff --git a/lib/xray/tests/unit/fdr_logging_test.cc b/lib/xray/tests/unit/fdr_logging_test.cc deleted file mode 100644 index b6961efbc3517..0000000000000 --- a/lib/xray/tests/unit/fdr_logging_test.cc +++ /dev/null @@ -1,202 +0,0 @@ -//===-- fdr_logging_test.cc -----------------------------------------------===// -// -// The LLVM Compiler Infrastructure -// -// This file is distributed under the University of Illinois Open Source -// License. See LICENSE.TXT for details. -// -//===----------------------------------------------------------------------===// -// -// This file is a part of XRay, a function call tracing system. -// -//===----------------------------------------------------------------------===// -#include "sanitizer_common/sanitizer_common.h" -#include "xray_fdr_logging.h" -#include "gtest/gtest.h" - -#include <array> -#include <fcntl.h> -#include <iostream> -#include <sys/mman.h> -#include <sys/stat.h> -#include <sys/syscall.h> -#include <sys/types.h> -#include <system_error> -#include <thread> -#include <unistd.h> - -#include "xray/xray_records.h" - -namespace __xray { -namespace { - -constexpr auto kBufferSize = 16384; -constexpr auto kBufferMax = 10; - -struct ScopedFileCloserAndDeleter { - explicit ScopedFileCloserAndDeleter(int Fd, const char *Filename) - : Fd(Fd), Filename(Filename) {} - - ~ScopedFileCloserAndDeleter() { - if (Map) - munmap(Map, Size); - if (Fd) { - close(Fd); - unlink(Filename); - } - } - - void registerMap(void *M, size_t S) { - Map = M; - Size = S; - } - - int Fd; - const char *Filename; - void *Map = nullptr; - size_t Size = 0; -}; - -TEST(FDRLoggingTest, Simple) { - FDRLoggingOptions Options; - Options.ReportErrors = true; - char TmpFilename[] = "fdr-logging-test.XXXXXX"; - Options.Fd = mkstemp(TmpFilename); - ASSERT_NE(Options.Fd, -1); - ASSERT_EQ(fdrLoggingInit(kBufferSize, kBufferMax, &Options, - sizeof(FDRLoggingOptions)), - XRayLogInitStatus::XRAY_LOG_INITIALIZED); - fdrLoggingHandleArg0(1, XRayEntryType::ENTRY); - fdrLoggingHandleArg0(1, XRayEntryType::EXIT); - ASSERT_EQ(fdrLoggingFinalize(), XRayLogInitStatus::XRAY_LOG_FINALIZED); - ASSERT_EQ(fdrLoggingFlush(), XRayLogFlushStatus::XRAY_LOG_FLUSHED); - - // To do this properly, we have to close the file descriptor then re-open the - // file for reading this time. - ASSERT_EQ(close(Options.Fd), 0); - int Fd = open(TmpFilename, O_RDONLY); - ASSERT_NE(-1, Fd); - ScopedFileCloserAndDeleter Guard(Fd, TmpFilename); - auto Size = lseek(Fd, 0, SEEK_END); - ASSERT_NE(Size, 0); - // Map the file contents. - void *Map = mmap(NULL, Size, PROT_READ, MAP_PRIVATE, Fd, 0); - const char *Contents = static_cast<const char *>(Map); - Guard.registerMap(Map, Size); - ASSERT_NE(Contents, nullptr); - - XRayFileHeader H; - memcpy(&H, Contents, sizeof(XRayFileHeader)); - ASSERT_EQ(H.Version, 3); - ASSERT_EQ(H.Type, FileTypes::FDR_LOG); - - // We require one buffer at least to have the "extents" metadata record, - // followed by the NewBuffer record. - MetadataRecord MDR0, MDR1; - memcpy(&MDR0, Contents + sizeof(XRayFileHeader), sizeof(MetadataRecord)); - memcpy(&MDR1, Contents + sizeof(XRayFileHeader) + sizeof(MetadataRecord), - sizeof(MetadataRecord)); - ASSERT_EQ(MDR0.RecordKind, - uint8_t(MetadataRecord::RecordKinds::BufferExtents)); - ASSERT_EQ(MDR1.RecordKind, uint8_t(MetadataRecord::RecordKinds::NewBuffer)); -} - -TEST(FDRLoggingTest, Multiple) { - FDRLoggingOptions Options; - char TmpFilename[] = "fdr-logging-test.XXXXXX"; - Options.Fd = mkstemp(TmpFilename); - ASSERT_NE(Options.Fd, -1); - ASSERT_EQ(fdrLoggingInit(kBufferSize, kBufferMax, &Options, - sizeof(FDRLoggingOptions)), - XRayLogInitStatus::XRAY_LOG_INITIALIZED); - for (uint64_t I = 0; I < 100; ++I) { - fdrLoggingHandleArg0(1, XRayEntryType::ENTRY); - fdrLoggingHandleArg0(1, XRayEntryType::EXIT); - } - ASSERT_EQ(fdrLoggingFinalize(), XRayLogInitStatus::XRAY_LOG_FINALIZED); - ASSERT_EQ(fdrLoggingFlush(), XRayLogFlushStatus::XRAY_LOG_FLUSHED); - - // To do this properly, we have to close the file descriptor then re-open the - // file for reading this time. - ASSERT_EQ(close(Options.Fd), 0); - int Fd = open(TmpFilename, O_RDONLY); - ASSERT_NE(-1, Fd); - ScopedFileCloserAndDeleter Guard(Fd, TmpFilename); - auto Size = lseek(Fd, 0, SEEK_END); - ASSERT_NE(Size, 0); - // Map the file contents. - void *Map = mmap(NULL, Size, PROT_READ, MAP_PRIVATE, Fd, 0); - const char *Contents = static_cast<const char *>(Map); - Guard.registerMap(Map, Size); - ASSERT_NE(Contents, nullptr); - - XRayFileHeader H; - memcpy(&H, Contents, sizeof(XRayFileHeader)); - ASSERT_EQ(H.Version, 3); - ASSERT_EQ(H.Type, FileTypes::FDR_LOG); - - MetadataRecord MDR0, MDR1; - memcpy(&MDR0, Contents + sizeof(XRayFileHeader), sizeof(MetadataRecord)); - memcpy(&MDR1, Contents + sizeof(XRayFileHeader) + sizeof(MetadataRecord), - sizeof(MetadataRecord)); - ASSERT_EQ(MDR0.RecordKind, - uint8_t(MetadataRecord::RecordKinds::BufferExtents)); - ASSERT_EQ(MDR1.RecordKind, uint8_t(MetadataRecord::RecordKinds::NewBuffer)); -} - -TEST(FDRLoggingTest, MultiThreadedCycling) { - FDRLoggingOptions Options; - char TmpFilename[] = "fdr-logging-test.XXXXXX"; - Options.Fd = mkstemp(TmpFilename); - ASSERT_NE(Options.Fd, -1); - ASSERT_EQ(fdrLoggingInit(kBufferSize, 1, &Options, sizeof(FDRLoggingOptions)), - XRayLogInitStatus::XRAY_LOG_INITIALIZED); - - // Now we want to create one thread, do some logging, then create another one, - // in succession and making sure that we're able to get thread records from - // the latest thread (effectively being able to recycle buffers). - std::array<tid_t, 2> Threads; - for (uint64_t I = 0; I < 2; ++I) { - std::thread t{[I, &Threads] { - fdrLoggingHandleArg0(I + 1, XRayEntryType::ENTRY); - fdrLoggingHandleArg0(I + 1, XRayEntryType::EXIT); - Threads[I] = GetTid(); - }}; - t.join(); - } - ASSERT_EQ(fdrLoggingFinalize(), XRayLogInitStatus::XRAY_LOG_FINALIZED); - ASSERT_EQ(fdrLoggingFlush(), XRayLogFlushStatus::XRAY_LOG_FLUSHED); - - // To do this properly, we have to close the file descriptor then re-open the - // file for reading this time. - ASSERT_EQ(close(Options.Fd), 0); - int Fd = open(TmpFilename, O_RDONLY); - ASSERT_NE(-1, Fd); - ScopedFileCloserAndDeleter Guard(Fd, TmpFilename); - auto Size = lseek(Fd, 0, SEEK_END); - ASSERT_NE(Size, 0); - // Map the file contents. - void *Map = mmap(NULL, Size, PROT_READ, MAP_PRIVATE, Fd, 0); - const char *Contents = static_cast<const char *>(Map); - Guard.registerMap(Map, Size); - ASSERT_NE(Contents, nullptr); - - XRayFileHeader H; - memcpy(&H, Contents, sizeof(XRayFileHeader)); - ASSERT_EQ(H.Version, 3); - ASSERT_EQ(H.Type, FileTypes::FDR_LOG); - - MetadataRecord MDR0, MDR1; - memcpy(&MDR0, Contents + sizeof(XRayFileHeader), sizeof(MetadataRecord)); - memcpy(&MDR1, Contents + sizeof(XRayFileHeader) + sizeof(MetadataRecord), - sizeof(MetadataRecord)); - ASSERT_EQ(MDR0.RecordKind, - uint8_t(MetadataRecord::RecordKinds::BufferExtents)); - ASSERT_EQ(MDR1.RecordKind, uint8_t(MetadataRecord::RecordKinds::NewBuffer)); - int32_t Latest = 0; - memcpy(&Latest, MDR1.Data, sizeof(int32_t)); - ASSERT_EQ(Latest, static_cast<int32_t>(Threads[1])); -} - -} // namespace -} // namespace __xray diff --git a/lib/xray/tests/unit/function_call_trie_test.cc b/lib/xray/tests/unit/function_call_trie_test.cc index 049ecfb07e017..01be691228f2b 100644 --- a/lib/xray/tests/unit/function_call_trie_test.cc +++ b/lib/xray/tests/unit/function_call_trie_test.cc @@ -10,9 +10,9 @@ // This file is a part of XRay, a function call tracing system. // //===----------------------------------------------------------------------===// -#include "gtest/gtest.h" - #include "xray_function_call_trie.h" +#include "gtest/gtest.h" +#include <cstdint> namespace __xray { @@ -29,26 +29,54 @@ TEST(FunctionCallTrieTest, EnterAndExitFunction) { auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); - Trie.enterFunction(1, 1); - Trie.exitFunction(1, 2); + uint64_t TSC = 1; + uint16_t CPU = 0; + Trie.enterFunction(1, TSC++, CPU++); + Trie.exitFunction(1, TSC++, CPU++); + const auto &R = Trie.getRoots(); - // We need a way to pull the data out. At this point, until we get a data - // collection service implemented, we're going to export the data as a list of - // roots, and manually walk through the structure ourselves. + ASSERT_EQ(R.size(), 1u); + ASSERT_EQ(R.front()->FId, 1); + ASSERT_EQ(R.front()->CallCount, 1u); + ASSERT_EQ(R.front()->CumulativeLocalTime, 1u); +} + +TEST(FunctionCallTrieTest, HandleTSCOverflow) { + profilingFlags()->setDefaults(); + auto A = FunctionCallTrie::InitAllocators(); + FunctionCallTrie Trie(A); + Trie.enterFunction(1, std::numeric_limits<uint64_t>::max(), 0); + Trie.exitFunction(1, 1, 0); const auto &R = Trie.getRoots(); ASSERT_EQ(R.size(), 1u); ASSERT_EQ(R.front()->FId, 1); - ASSERT_EQ(R.front()->CallCount, 1); + ASSERT_EQ(R.front()->CallCount, 1u); ASSERT_EQ(R.front()->CumulativeLocalTime, 1u); } +TEST(FunctionCallTrieTest, MaximalCumulativeTime) { + profilingFlags()->setDefaults(); + auto A = FunctionCallTrie::InitAllocators(); + FunctionCallTrie Trie(A); + + Trie.enterFunction(1, 1, 0); + Trie.exitFunction(1, 0, 0); + const auto &R = Trie.getRoots(); + + ASSERT_EQ(R.size(), 1u); + ASSERT_EQ(R.front()->FId, 1); + ASSERT_EQ(R.front()->CallCount, 1u); + ASSERT_EQ(R.front()->CumulativeLocalTime, + std::numeric_limits<uint64_t>::max() - 1); +} + TEST(FunctionCallTrieTest, MissingFunctionEntry) { profilingFlags()->setDefaults(); auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); - Trie.exitFunction(1, 1); + Trie.exitFunction(1, 1, 0); const auto &R = Trie.getRoots(); ASSERT_TRUE(R.empty()); @@ -58,9 +86,9 @@ TEST(FunctionCallTrieTest, NoMatchingEntersForExit) { profilingFlags()->setDefaults(); auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); - Trie.enterFunction(2, 1); - Trie.enterFunction(3, 3); - Trie.exitFunction(1, 5); + Trie.enterFunction(2, 1, 0); + Trie.enterFunction(3, 3, 0); + Trie.exitFunction(1, 5, 0); const auto &R = Trie.getRoots(); ASSERT_FALSE(R.empty()); @@ -71,7 +99,7 @@ TEST(FunctionCallTrieTest, MissingFunctionExit) { profilingFlags()->setDefaults(); auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); - Trie.enterFunction(1, 1); + Trie.enterFunction(1, 1, 0); const auto &R = Trie.getRoots(); ASSERT_FALSE(R.empty()); @@ -84,12 +112,12 @@ TEST(FunctionCallTrieTest, MultipleRoots) { FunctionCallTrie Trie(A); // Enter and exit FId = 1. - Trie.enterFunction(1, 1); - Trie.exitFunction(1, 2); + Trie.enterFunction(1, 1, 0); + Trie.exitFunction(1, 2, 0); // Enter and exit FId = 2. - Trie.enterFunction(2, 3); - Trie.exitFunction(2, 4); + Trie.enterFunction(2, 3, 0); + Trie.exitFunction(2, 4, 0); const auto &R = Trie.getRoots(); ASSERT_FALSE(R.empty()); @@ -126,11 +154,11 @@ TEST(FunctionCallTrieTest, MissingIntermediaryExit) { auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); - Trie.enterFunction(1, 0); - Trie.enterFunction(2, 100); - Trie.enterFunction(3, 200); - Trie.exitFunction(3, 300); - Trie.exitFunction(1, 400); + Trie.enterFunction(1, 0, 0); + Trie.enterFunction(2, 100, 0); + Trie.enterFunction(3, 200, 0); + Trie.exitFunction(3, 300, 0); + Trie.exitFunction(1, 400, 0); // What we should see at this point is all the functions in the trie in a // specific order (1 -> 2 -> 3) with the appropriate count(s) and local @@ -153,12 +181,12 @@ TEST(FunctionCallTrieTest, MissingIntermediaryExit) { // Now that we've established the preconditions, we check for specific aspects // of the nodes. - EXPECT_EQ(F3.CallCount, 1); - EXPECT_EQ(F2.CallCount, 1); - EXPECT_EQ(F1.CallCount, 1); - EXPECT_EQ(F3.CumulativeLocalTime, 100); - EXPECT_EQ(F2.CumulativeLocalTime, 300); - EXPECT_EQ(F1.CumulativeLocalTime, 100); + EXPECT_EQ(F3.CallCount, 1u); + EXPECT_EQ(F2.CallCount, 1u); + EXPECT_EQ(F1.CallCount, 1u); + EXPECT_EQ(F3.CumulativeLocalTime, 100u); + EXPECT_EQ(F2.CumulativeLocalTime, 300u); + EXPECT_EQ(F1.CumulativeLocalTime, 100u); } TEST(FunctionCallTrieTest, DeepCallStack) { @@ -168,8 +196,8 @@ TEST(FunctionCallTrieTest, DeepCallStack) { auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); for (int i = 0; i < 32; ++i) - Trie.enterFunction(i + 1, i); - Trie.exitFunction(1, 33); + Trie.enterFunction(i + 1, i, 0); + Trie.exitFunction(1, 33, 0); // Here, validate that we have a 32-level deep function call path from the // root (1) down to the leaf (33). @@ -178,7 +206,7 @@ TEST(FunctionCallTrieTest, DeepCallStack) { auto F = R[0]; for (int i = 0; i < 32; ++i) { EXPECT_EQ(F->FId, i + 1); - EXPECT_EQ(F->CallCount, 1); + EXPECT_EQ(F->CallCount, 1u); if (F->Callees.empty() && i != 31) FAIL() << "Empty callees for FId " << F->FId; if (i != 31) @@ -193,12 +221,12 @@ TEST(FunctionCallTrieTest, DeepCopy) { auto A = FunctionCallTrie::InitAllocators(); FunctionCallTrie Trie(A); - Trie.enterFunction(1, 0); - Trie.enterFunction(2, 1); - Trie.exitFunction(2, 2); - Trie.enterFunction(3, 3); - Trie.exitFunction(3, 4); - Trie.exitFunction(1, 5); + Trie.enterFunction(1, 0, 0); + Trie.enterFunction(2, 1, 0); + Trie.exitFunction(2, 2, 0); + Trie.enterFunction(3, 3, 0); + Trie.exitFunction(3, 4, 0); + Trie.exitFunction(1, 5, 0); // We want to make a deep copy and compare notes. auto B = FunctionCallTrie::InitAllocators(); @@ -236,20 +264,20 @@ TEST(FunctionCallTrieTest, MergeInto) { FunctionCallTrie T1(A); // 1 -> 2 -> 3 - T0.enterFunction(1, 0); - T0.enterFunction(2, 1); - T0.enterFunction(3, 2); - T0.exitFunction(3, 3); - T0.exitFunction(2, 4); - T0.exitFunction(1, 5); + T0.enterFunction(1, 0, 0); + T0.enterFunction(2, 1, 0); + T0.enterFunction(3, 2, 0); + T0.exitFunction(3, 3, 0); + T0.exitFunction(2, 4, 0); + T0.exitFunction(1, 5, 0); // 1 -> 2 -> 3 - T1.enterFunction(1, 0); - T1.enterFunction(2, 1); - T1.enterFunction(3, 2); - T1.exitFunction(3, 3); - T1.exitFunction(2, 4); - T1.exitFunction(1, 5); + T1.enterFunction(1, 0, 0); + T1.enterFunction(2, 1, 0); + T1.enterFunction(3, 2, 0); + T1.exitFunction(3, 3, 0); + T1.exitFunction(2, 4, 0); + T1.exitFunction(1, 5, 0); // We use a different allocator here to make sure that we're able to transfer // data into a FunctionCallTrie which uses a different allocator. This @@ -264,23 +292,53 @@ TEST(FunctionCallTrieTest, MergeInto) { ASSERT_EQ(Merged.getRoots().size(), 1u); const auto &R0 = *Merged.getRoots()[0]; EXPECT_EQ(R0.FId, 1); - EXPECT_EQ(R0.CallCount, 2); - EXPECT_EQ(R0.CumulativeLocalTime, 10); + EXPECT_EQ(R0.CallCount, 2u); + EXPECT_EQ(R0.CumulativeLocalTime, 10u); EXPECT_EQ(R0.Callees.size(), 1u); const auto &F1 = *R0.Callees[0].NodePtr; EXPECT_EQ(F1.FId, 2); - EXPECT_EQ(F1.CallCount, 2); - EXPECT_EQ(F1.CumulativeLocalTime, 6); + EXPECT_EQ(F1.CallCount, 2u); + EXPECT_EQ(F1.CumulativeLocalTime, 6u); EXPECT_EQ(F1.Callees.size(), 1u); const auto &F2 = *F1.Callees[0].NodePtr; EXPECT_EQ(F2.FId, 3); - EXPECT_EQ(F2.CallCount, 2); - EXPECT_EQ(F2.CumulativeLocalTime, 2); + EXPECT_EQ(F2.CallCount, 2u); + EXPECT_EQ(F2.CumulativeLocalTime, 2u); EXPECT_EQ(F2.Callees.size(), 0u); } +TEST(FunctionCallTrieTest, PlacementNewOnAlignedStorage) { + profilingFlags()->setDefaults(); + typename std::aligned_storage<sizeof(FunctionCallTrie::Allocators), + alignof(FunctionCallTrie::Allocators)>::type + AllocatorsStorage; + new (&AllocatorsStorage) + FunctionCallTrie::Allocators(FunctionCallTrie::InitAllocators()); + auto *A = + reinterpret_cast<FunctionCallTrie::Allocators *>(&AllocatorsStorage); + + typename std::aligned_storage<sizeof(FunctionCallTrie), + alignof(FunctionCallTrie)>::type FCTStorage; + new (&FCTStorage) FunctionCallTrie(*A); + auto *T = reinterpret_cast<FunctionCallTrie *>(&FCTStorage); + + // Put some data into it. + T->enterFunction(1, 0, 0); + T->exitFunction(1, 1, 0); + + // Re-initialize the objects in storage. + T->~FunctionCallTrie(); + A->~Allocators(); + new (A) FunctionCallTrie::Allocators(FunctionCallTrie::InitAllocators()); + new (T) FunctionCallTrie(*A); + + // Then put some data into it again. + T->enterFunction(1, 0, 0); + T->exitFunction(1, 1, 0); +} + } // namespace } // namespace __xray diff --git a/lib/xray/tests/unit/profile_collector_test.cc b/lib/xray/tests/unit/profile_collector_test.cc index 67049af2cd5f9..df786d46b9d46 100644 --- a/lib/xray/tests/unit/profile_collector_test.cc +++ b/lib/xray/tests/unit/profile_collector_test.cc @@ -110,24 +110,31 @@ std::tuple<Profile, const char *> ParseProfile(const char *P) { TEST(profileCollectorServiceTest, PostSerializeCollect) { profilingFlags()->setDefaults(); - // The most basic use-case (the one we actually only care about) is the one - // where we ensure that we can post FunctionCallTrie instances, which are then - // destroyed but serialized properly. - // - // First, we initialise a set of allocators in the local scope. This ensures - // that we're able to copy the contents of the FunctionCallTrie that uses - // the local allocators. - auto Allocators = FunctionCallTrie::InitAllocators(); + bool Success = false; + BufferQueue BQ(profilingFlags()->per_thread_allocator_max, + profilingFlags()->buffers_max, Success); + ASSERT_EQ(Success, true); + FunctionCallTrie::Allocators::Buffers Buffers; + ASSERT_EQ(BQ.getBuffer(Buffers.NodeBuffer), BufferQueue::ErrorCode::Ok); + ASSERT_EQ(BQ.getBuffer(Buffers.RootsBuffer), BufferQueue::ErrorCode::Ok); + ASSERT_EQ(BQ.getBuffer(Buffers.ShadowStackBuffer), + BufferQueue::ErrorCode::Ok); + ASSERT_EQ(BQ.getBuffer(Buffers.NodeIdPairBuffer), BufferQueue::ErrorCode::Ok); + auto Allocators = FunctionCallTrie::InitAllocatorsFromBuffers(Buffers); FunctionCallTrie T(Allocators); - // Then, we populate the trie with some data. - T.enterFunction(1, 1); - T.enterFunction(2, 2); - T.exitFunction(2, 3); - T.exitFunction(1, 4); + // Populate the trie with some data. + T.enterFunction(1, 1, 0); + T.enterFunction(2, 2, 0); + T.exitFunction(2, 3, 0); + T.exitFunction(1, 4, 0); + + // Reset the collector data structures. + profileCollectorService::reset(); // Then we post the data to the global profile collector service. - profileCollectorService::post(T, 1); + profileCollectorService::post(&BQ, std::move(T), std::move(Allocators), + std::move(Buffers), 1); // Then we serialize the data. profileCollectorService::serialize(); @@ -174,19 +181,37 @@ TEST(profileCollectorServiceTest, PostSerializeCollect) { // profileCollectorService. This simulates what the threads being profiled would // be doing anyway, but through the XRay logging implementation. void threadProcessing() { - thread_local auto Allocators = FunctionCallTrie::InitAllocators(); + static bool Success = false; + static BufferQueue BQ(profilingFlags()->per_thread_allocator_max, + profilingFlags()->buffers_max, Success); + thread_local FunctionCallTrie::Allocators::Buffers Buffers = [] { + FunctionCallTrie::Allocators::Buffers B; + BQ.getBuffer(B.NodeBuffer); + BQ.getBuffer(B.RootsBuffer); + BQ.getBuffer(B.ShadowStackBuffer); + BQ.getBuffer(B.NodeIdPairBuffer); + return B; + }(); + + thread_local auto Allocators = + FunctionCallTrie::InitAllocatorsFromBuffers(Buffers); + FunctionCallTrie T(Allocators); - T.enterFunction(1, 1); - T.enterFunction(2, 2); - T.exitFunction(2, 3); - T.exitFunction(1, 4); + T.enterFunction(1, 1, 0); + T.enterFunction(2, 2, 0); + T.exitFunction(2, 3, 0); + T.exitFunction(1, 4, 0); - profileCollectorService::post(T, GetTid()); + profileCollectorService::post(&BQ, std::move(T), std::move(Allocators), + std::move(Buffers), GetTid()); } TEST(profileCollectorServiceTest, PostSerializeCollectMultipleThread) { profilingFlags()->setDefaults(); + + profileCollectorService::reset(); + std::thread t1(threadProcessing); std::thread t2(threadProcessing); diff --git a/lib/xray/tests/unit/segmented_array_test.cc b/lib/xray/tests/unit/segmented_array_test.cc index 035674ccfaf5e..46aeb88f71b4c 100644 --- a/lib/xray/tests/unit/segmented_array_test.cc +++ b/lib/xray/tests/unit/segmented_array_test.cc @@ -1,9 +1,16 @@ +#include "test_helpers.h" #include "xray_segmented_array.h" +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include <algorithm> +#include <numeric> +#include <vector> namespace __xray { namespace { +using ::testing::SizeIs; + struct TestData { s64 First; s64 Second; @@ -12,6 +19,10 @@ struct TestData { TestData(s64 F, s64 S) : First(F), Second(S) {} }; +void PrintTo(const TestData &D, std::ostream *OS) { + *OS << "{ " << D.First << ", " << D.Second << " }"; +} + TEST(SegmentedArrayTest, ConstructWithAllocators) { using AllocatorType = typename Array<TestData>::AllocatorType; AllocatorType A(1 << 4); @@ -161,6 +172,23 @@ TEST(SegmentedArrayTest, IteratorTrimBehaviour) { EXPECT_EQ(Data.size(), SegmentX2); } +TEST(SegmentedArrayTest, HandleExhaustedAllocator) { + using AllocatorType = typename Array<TestData>::AllocatorType; + constexpr auto Segment = Array<TestData>::SegmentSize; + constexpr auto MaxElements = Array<TestData>::ElementsPerSegment; + AllocatorType A(Segment); + Array<TestData> Data(A); + for (auto i = MaxElements; i > 0u; --i) + EXPECT_NE(Data.AppendEmplace(static_cast<s64>(i), static_cast<s64>(i)), + nullptr); + EXPECT_EQ(Data.AppendEmplace(0, 0), nullptr); + EXPECT_THAT(Data, SizeIs(MaxElements)); + + // Trimming more elements than there are in the container should be fine. + Data.trim(MaxElements + 1); + EXPECT_THAT(Data, SizeIs(0u)); +} + struct ShadowStackEntry { uint64_t EntryTSC = 0; uint64_t *NodePtr = nullptr; @@ -196,5 +224,126 @@ TEST(SegmentedArrayTest, SimulateStackBehaviour) { } } +TEST(SegmentedArrayTest, PlacementNewOnAlignedStorage) { + using AllocatorType = typename Array<ShadowStackEntry>::AllocatorType; + typename std::aligned_storage<sizeof(AllocatorType), + alignof(AllocatorType)>::type AllocatorStorage; + new (&AllocatorStorage) AllocatorType(1 << 10); + auto *A = reinterpret_cast<AllocatorType *>(&AllocatorStorage); + typename std::aligned_storage<sizeof(Array<ShadowStackEntry>), + alignof(Array<ShadowStackEntry>)>::type + ArrayStorage; + new (&ArrayStorage) Array<ShadowStackEntry>(*A); + auto *Data = reinterpret_cast<Array<ShadowStackEntry> *>(&ArrayStorage); + + static uint64_t Dummy = 0; + constexpr uint64_t Max = 9; + + for (uint64_t i = 0; i < Max; ++i) { + auto P = Data->Append({i, &Dummy}); + ASSERT_NE(P, nullptr); + ASSERT_EQ(P->NodePtr, &Dummy); + auto &Back = Data->back(); + ASSERT_EQ(Back.NodePtr, &Dummy); + ASSERT_EQ(Back.EntryTSC, i); + } + + // Simulate a stack by checking the data from the end as we're trimming. + auto Counter = Max; + ASSERT_EQ(Data->size(), size_t(Max)); + while (!Data->empty()) { + const auto &Top = Data->back(); + uint64_t *TopNode = Top.NodePtr; + EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter; + Data->trim(1); + --Counter; + ASSERT_EQ(Data->size(), size_t(Counter)); + } + + // Once the stack is exhausted, we re-use the storage. + for (uint64_t i = 0; i < Max; ++i) { + auto P = Data->Append({i, &Dummy}); + ASSERT_NE(P, nullptr); + ASSERT_EQ(P->NodePtr, &Dummy); + auto &Back = Data->back(); + ASSERT_EQ(Back.NodePtr, &Dummy); + ASSERT_EQ(Back.EntryTSC, i); + } + + // We re-initialize the storage, by calling the destructor and + // placement-new'ing again. + Data->~Array(); + A->~AllocatorType(); + new (A) AllocatorType(1 << 10); + new (Data) Array<ShadowStackEntry>(*A); + + // Then re-do the test. + for (uint64_t i = 0; i < Max; ++i) { + auto P = Data->Append({i, &Dummy}); + ASSERT_NE(P, nullptr); + ASSERT_EQ(P->NodePtr, &Dummy); + auto &Back = Data->back(); + ASSERT_EQ(Back.NodePtr, &Dummy); + ASSERT_EQ(Back.EntryTSC, i); + } + + // Simulate a stack by checking the data from the end as we're trimming. + Counter = Max; + ASSERT_EQ(Data->size(), size_t(Max)); + while (!Data->empty()) { + const auto &Top = Data->back(); + uint64_t *TopNode = Top.NodePtr; + EXPECT_EQ(TopNode, &Dummy) << "Counter = " << Counter; + Data->trim(1); + --Counter; + ASSERT_EQ(Data->size(), size_t(Counter)); + } + + // Once the stack is exhausted, we re-use the storage. + for (uint64_t i = 0; i < Max; ++i) { + auto P = Data->Append({i, &Dummy}); + ASSERT_NE(P, nullptr); + ASSERT_EQ(P->NodePtr, &Dummy); + auto &Back = Data->back(); + ASSERT_EQ(Back.NodePtr, &Dummy); + ASSERT_EQ(Back.EntryTSC, i); + } +} + +TEST(SegmentedArrayTest, ArrayOfPointersIteratorAccess) { + using PtrArray = Array<int *>; + PtrArray::AllocatorType Alloc(16384); + Array<int *> A(Alloc); + static constexpr size_t Count = 100; + std::vector<int> Integers(Count); + std::iota(Integers.begin(), Integers.end(), 0); + for (auto &I : Integers) + ASSERT_NE(A.Append(&I), nullptr); + int V = 0; + ASSERT_EQ(A.size(), Count); + for (auto P : A) { + ASSERT_NE(P, nullptr); + ASSERT_EQ(*P, V++); + } +} + +TEST(SegmentedArrayTest, ArrayOfPointersIteratorAccessExhaustion) { + using PtrArray = Array<int *>; + PtrArray::AllocatorType Alloc(4096); + Array<int *> A(Alloc); + static constexpr size_t Count = 1000; + std::vector<int> Integers(Count); + std::iota(Integers.begin(), Integers.end(), 0); + for (auto &I : Integers) + if (A.Append(&I) == nullptr) + break; + int V = 0; + ASSERT_LT(A.size(), Count); + for (auto P : A) { + ASSERT_NE(P, nullptr); + ASSERT_EQ(*P, V++); + } +} + } // namespace } // namespace __xray diff --git a/lib/xray/tests/unit/test_helpers.cc b/lib/xray/tests/unit/test_helpers.cc new file mode 100644 index 0000000000000..284492d1050b4 --- /dev/null +++ b/lib/xray/tests/unit/test_helpers.cc @@ -0,0 +1,95 @@ +//===-- test_helpers.cc ---------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of XRay, a function call tracing system. +// +//===----------------------------------------------------------------------===// +#include "test_helpers.h" +#include "xray/xray_records.h" +#include "xray_buffer_queue.h" +#include "xray_fdr_log_writer.h" +#include <type_traits> + +// TODO: Move these to llvm/include/Testing/XRay/... +namespace llvm { +namespace xray { + +std::string RecordTypeAsString(RecordTypes T) { + switch (T) { + case RecordTypes::ENTER: + return "llvm::xray::RecordTypes::ENTER"; + case RecordTypes::EXIT: + return "llvm::xray::RecordTypes::EXIT"; + case RecordTypes::TAIL_EXIT: + return "llvm::xray::RecordTypes::TAIL_EXIT"; + case RecordTypes::ENTER_ARG: + return "llvm::xray::RecordTypes::ENTER_ARG"; + case RecordTypes::CUSTOM_EVENT: + return "llvm::xray::RecordTypes::CUSTOM_EVENT"; + case RecordTypes::TYPED_EVENT: + return "llvm::xray::RecordTypes::TYPED_EVENT"; + } + return "<UNKNOWN>"; +} + +void PrintTo(RecordTypes T, std::ostream *OS) { + *OS << RecordTypeAsString(T); +} + +void PrintTo(const XRayRecord &R, std::ostream *OS) { + *OS << "XRayRecord { CPU = " << R.CPU + << "; Type = " << RecordTypeAsString(R.Type) << "; FuncId = " << R.FuncId + << "; TSC = " << R.TSC << "; TId = " << R.TId << "; PId = " << R.PId + << " Args = " << ::testing::PrintToString(R.CallArgs) << " }"; +} + +void PrintTo(const Trace &T, std::ostream *OS) { + const auto &H = T.getFileHeader(); + *OS << "XRay Trace:\nHeader: { Version = " << H.Version + << "; Type = " << H.Type + << "; ConstantTSC = " << ::testing::PrintToString(H.ConstantTSC) + << "; NonstopTSC = " << ::testing::PrintToString(H.NonstopTSC) + << "; CycleFrequency = " << H.CycleFrequency << "; FreeFormData = '" + << ::testing::PrintToString(H.FreeFormData) << "' }\n"; + for (const auto &R : T) { + PrintTo(R, OS); + *OS << "\n"; + } +} + +} // namespace xray +} // namespace llvm + +namespace __xray { + +std::string serialize(BufferQueue &Buffers, int32_t Version) { + std::string Serialized; + std::aligned_storage<sizeof(XRayFileHeader), alignof(XRayFileHeader)>::type + HeaderStorage; + auto *Header = reinterpret_cast<XRayFileHeader *>(&HeaderStorage); + new (Header) XRayFileHeader(); + Header->Version = Version; + Header->Type = FileTypes::FDR_LOG; + Header->CycleFrequency = 3e9; + Header->ConstantTSC = 1; + Header->NonstopTSC = 1; + Serialized.append(reinterpret_cast<const char *>(&HeaderStorage), + sizeof(XRayFileHeader)); + Buffers.apply([&](const BufferQueue::Buffer &B) { + auto Size = atomic_load_relaxed(B.Extents); + auto Extents = + createMetadataRecord<MetadataRecord::RecordKinds::BufferExtents>(Size); + Serialized.append(reinterpret_cast<const char *>(&Extents), + sizeof(Extents)); + Serialized.append(reinterpret_cast<const char *>(B.Data), Size); + }); + return Serialized; +} + +} // namespace __xray diff --git a/lib/xray/tests/unit/test_helpers.h b/lib/xray/tests/unit/test_helpers.h new file mode 100644 index 0000000000000..ff0311e9bd30f --- /dev/null +++ b/lib/xray/tests/unit/test_helpers.h @@ -0,0 +1,78 @@ +//===-- test_helpers.h ----------------------------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// +// +// This file is a part of XRay, a function call tracing system. +// +//===----------------------------------------------------------------------===// +#ifndef COMPILER_RT_LIB_XRAY_TESTS_TEST_HELPERS_H_ +#define COMPILER_RT_LIB_XRAY_TESTS_TEST_HELPERS_H_ + +#include "xray_buffer_queue.h" +#include "xray_segmented_array.h" +#include "llvm/XRay/Trace.h" +#include "llvm/XRay/XRayRecord.h" +#include "gmock/gmock.h" + +// TODO: Move these to llvm/include/Testing/XRay/... +namespace llvm { +namespace xray { + +std::string RecordTypeAsString(RecordTypes T); +void PrintTo(RecordTypes T, std::ostream *OS); +void PrintTo(const XRayRecord &R, std::ostream *OS); +void PrintTo(const Trace &T, std::ostream *OS); + +namespace testing { + +MATCHER_P(FuncId, F, "") { + *result_listener << "where the function id is " << F; + return arg.FuncId == F; +} + +MATCHER_P(RecordType, T, "") { + *result_listener << "where the record type is " << RecordTypeAsString(T); + return arg.Type == T; +} + +MATCHER_P(HasArg, A, "") { + *result_listener << "where args contains " << A; + return !arg.CallArgs.empty() && + std::any_of(arg.CallArgs.begin(), arg.CallArgs.end(), + [this](decltype(A) V) { return V == A; }); +} + +MATCHER_P(TSCIs, M, std::string("TSC is ") + ::testing::PrintToString(M)) { + return ::testing::Matcher<decltype(arg.TSC)>(M).MatchAndExplain( + arg.TSC, result_listener); +} + +} // namespace testing +} // namespace xray +} // namespace llvm + +namespace __xray { + +std::string serialize(BufferQueue &Buffers, int32_t Version); + +template <class T> void PrintTo(const Array<T> &A, std::ostream *OS) { + *OS << "["; + bool first = true; + for (const auto &E : A) { + if (!first) { + *OS << ", "; + } + PrintTo(E, OS); + first = false; + } + *OS << "]"; +} + +} // namespace __xray + +#endif // COMPILER_RT_LIB_XRAY_TESTS_TEST_HELPERS_H_ |