1 //===-- profile_collector_test.cpp ----------------------------------------===// 2 // 3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. 4 // See https://llvm.org/LICENSE.txt for license information. 5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 // 7 //===----------------------------------------------------------------------===// 8 // 9 // This file is a part of XRay, a function call tracing system. 10 // 11 //===----------------------------------------------------------------------===// 12 #include "gtest/gtest.h" 13 14 #include "xray_profile_collector.h" 15 #include "xray_profiling_flags.h" 16 #include <cstdint> 17 #include <cstring> 18 #include <memory> 19 #include <thread> 20 #include <utility> 21 #include <vector> 22 23 namespace __xray { 24 namespace { 25 26 static constexpr auto kHeaderSize = 16u; 27 28 constexpr uptr ExpectedProfilingVersion = 0x20180424; 29 30 struct ExpectedProfilingFileHeader { 31 const u64 MagicBytes = 0x7872617970726f66; // Identifier for XRay profiling 32 // files 'xrayprof' in hex. 33 const u64 Version = ExpectedProfilingVersion; 34 u64 Timestamp = 0; 35 u64 PID = 0; 36 }; 37 38 void ValidateFileHeaderBlock(XRayBuffer B) { 39 ASSERT_NE(static_cast<const void *>(B.Data), nullptr); 40 ASSERT_EQ(B.Size, sizeof(ExpectedProfilingFileHeader)); 41 typename std::aligned_storage<sizeof(ExpectedProfilingFileHeader)>::type 42 FileHeaderStorage; 43 ExpectedProfilingFileHeader ExpectedHeader; 44 std::memcpy(&FileHeaderStorage, B.Data, B.Size); 45 auto &FileHeader = 46 *reinterpret_cast<ExpectedProfilingFileHeader *>(&FileHeaderStorage); 47 ASSERT_EQ(ExpectedHeader.MagicBytes, FileHeader.MagicBytes); 48 ASSERT_EQ(ExpectedHeader.Version, FileHeader.Version); 49 } 50 51 void ValidateBlock(XRayBuffer B) { 52 profilingFlags()->setDefaults(); 53 ASSERT_NE(static_cast<const void *>(B.Data), nullptr); 54 ASSERT_NE(B.Size, 0u); 55 ASSERT_GE(B.Size, kHeaderSize); 56 // We look at the block size, the block number, and the thread ID to ensure 57 // that none of them are zero (or that the header data is laid out as we 58 // expect). 59 char LocalBuffer[kHeaderSize] = {}; 60 internal_memcpy(LocalBuffer, B.Data, kHeaderSize); 61 u32 BlockSize = 0; 62 u32 BlockNumber = 0; 63 u64 ThreadId = 0; 64 internal_memcpy(&BlockSize, LocalBuffer, sizeof(u32)); 65 internal_memcpy(&BlockNumber, LocalBuffer + sizeof(u32), sizeof(u32)); 66 internal_memcpy(&ThreadId, LocalBuffer + (2 * sizeof(u32)), sizeof(u64)); 67 ASSERT_NE(BlockSize, 0u); 68 ASSERT_GE(BlockNumber, 0u); 69 ASSERT_NE(ThreadId, 0u); 70 } 71 72 std::tuple<u32, u32, u64> ParseBlockHeader(XRayBuffer B) { 73 char LocalBuffer[kHeaderSize] = {}; 74 internal_memcpy(LocalBuffer, B.Data, kHeaderSize); 75 u32 BlockSize = 0; 76 u32 BlockNumber = 0; 77 u64 ThreadId = 0; 78 internal_memcpy(&BlockSize, LocalBuffer, sizeof(u32)); 79 internal_memcpy(&BlockNumber, LocalBuffer + sizeof(u32), sizeof(u32)); 80 internal_memcpy(&ThreadId, LocalBuffer + (2 * sizeof(u32)), sizeof(u64)); 81 return std::make_tuple(BlockSize, BlockNumber, ThreadId); 82 } 83 84 struct Profile { 85 int64_t CallCount; 86 int64_t CumulativeLocalTime; 87 std::vector<int32_t> Path; 88 }; 89 90 std::tuple<Profile, const char *> ParseProfile(const char *P) { 91 Profile Result; 92 // Read the path first, until we find a sentinel 0. 93 int32_t F; 94 do { 95 internal_memcpy(&F, P, sizeof(int32_t)); 96 P += sizeof(int32_t); 97 Result.Path.push_back(F); 98 } while (F != 0); 99 100 // Then read the CallCount. 101 internal_memcpy(&Result.CallCount, P, sizeof(int64_t)); 102 P += sizeof(int64_t); 103 104 // Then read the CumulativeLocalTime. 105 internal_memcpy(&Result.CumulativeLocalTime, P, sizeof(int64_t)); 106 P += sizeof(int64_t); 107 return std::make_tuple(std::move(Result), P); 108 } 109 110 TEST(profileCollectorServiceTest, PostSerializeCollect) { 111 profilingFlags()->setDefaults(); 112 bool Success = false; 113 BufferQueue BQ(profilingFlags()->per_thread_allocator_max, 114 profilingFlags()->buffers_max, Success); 115 ASSERT_EQ(Success, true); 116 FunctionCallTrie::Allocators::Buffers Buffers; 117 ASSERT_EQ(BQ.getBuffer(Buffers.NodeBuffer), BufferQueue::ErrorCode::Ok); 118 ASSERT_EQ(BQ.getBuffer(Buffers.RootsBuffer), BufferQueue::ErrorCode::Ok); 119 ASSERT_EQ(BQ.getBuffer(Buffers.ShadowStackBuffer), 120 BufferQueue::ErrorCode::Ok); 121 ASSERT_EQ(BQ.getBuffer(Buffers.NodeIdPairBuffer), BufferQueue::ErrorCode::Ok); 122 auto Allocators = FunctionCallTrie::InitAllocatorsFromBuffers(Buffers); 123 FunctionCallTrie T(Allocators); 124 125 // Populate the trie with some data. 126 T.enterFunction(1, 1, 0); 127 T.enterFunction(2, 2, 0); 128 T.exitFunction(2, 3, 0); 129 T.exitFunction(1, 4, 0); 130 131 // Reset the collector data structures. 132 profileCollectorService::reset(); 133 134 // Then we post the data to the global profile collector service. 135 profileCollectorService::post(&BQ, std::move(T), std::move(Allocators), 136 std::move(Buffers), 1); 137 138 // Then we serialize the data. 139 profileCollectorService::serialize(); 140 141 // Then we go through two buffers to see whether we're getting the data we 142 // expect. The first block must always be as large as a file header, which 143 // will have a fixed size. 144 auto B = profileCollectorService::nextBuffer({nullptr, 0}); 145 ValidateFileHeaderBlock(B); 146 147 B = profileCollectorService::nextBuffer(B); 148 ValidateBlock(B); 149 u32 BlockSize; 150 u32 BlockNum; 151 u64 ThreadId; 152 std::tie(BlockSize, BlockNum, ThreadId) = ParseBlockHeader(B); 153 154 // We look at the serialized buffer to see whether the Trie we're expecting 155 // to see is there. 156 auto DStart = static_cast<const char *>(B.Data) + kHeaderSize; 157 std::vector<char> D(DStart, DStart + BlockSize); 158 B = profileCollectorService::nextBuffer(B); 159 ASSERT_EQ(B.Data, nullptr); 160 ASSERT_EQ(B.Size, 0u); 161 162 Profile Profile1, Profile2; 163 auto P = static_cast<const char *>(D.data()); 164 std::tie(Profile1, P) = ParseProfile(P); 165 std::tie(Profile2, P) = ParseProfile(P); 166 167 ASSERT_NE(Profile1.Path.size(), Profile2.Path.size()); 168 auto &P1 = Profile1.Path.size() < Profile2.Path.size() ? Profile2 : Profile1; 169 auto &P2 = Profile1.Path.size() < Profile2.Path.size() ? Profile1 : Profile2; 170 std::vector<int32_t> P1Expected = {2, 1, 0}; 171 std::vector<int32_t> P2Expected = {1, 0}; 172 ASSERT_EQ(P1.Path.size(), P1Expected.size()); 173 ASSERT_EQ(P2.Path.size(), P2Expected.size()); 174 ASSERT_EQ(P1.Path, P1Expected); 175 ASSERT_EQ(P2.Path, P2Expected); 176 } 177 178 // We break out a function that will be run in multiple threads, one that will 179 // use a thread local allocator, and will post the FunctionCallTrie to the 180 // profileCollectorService. This simulates what the threads being profiled would 181 // be doing anyway, but through the XRay logging implementation. 182 void threadProcessing() { 183 static bool Success = false; 184 static BufferQueue BQ(profilingFlags()->per_thread_allocator_max, 185 profilingFlags()->buffers_max, Success); 186 thread_local FunctionCallTrie::Allocators::Buffers Buffers = [] { 187 FunctionCallTrie::Allocators::Buffers B; 188 BQ.getBuffer(B.NodeBuffer); 189 BQ.getBuffer(B.RootsBuffer); 190 BQ.getBuffer(B.ShadowStackBuffer); 191 BQ.getBuffer(B.NodeIdPairBuffer); 192 return B; 193 }(); 194 195 thread_local auto Allocators = 196 FunctionCallTrie::InitAllocatorsFromBuffers(Buffers); 197 198 FunctionCallTrie T(Allocators); 199 200 T.enterFunction(1, 1, 0); 201 T.enterFunction(2, 2, 0); 202 T.exitFunction(2, 3, 0); 203 T.exitFunction(1, 4, 0); 204 205 profileCollectorService::post(&BQ, std::move(T), std::move(Allocators), 206 std::move(Buffers), GetTid()); 207 } 208 209 TEST(profileCollectorServiceTest, PostSerializeCollectMultipleThread) { 210 profilingFlags()->setDefaults(); 211 212 profileCollectorService::reset(); 213 214 std::thread t1(threadProcessing); 215 std::thread t2(threadProcessing); 216 217 t1.join(); 218 t2.join(); 219 220 // At this point, t1 and t2 are already done with what they were doing. 221 profileCollectorService::serialize(); 222 223 // Ensure that we see two buffers. 224 auto B = profileCollectorService::nextBuffer({nullptr, 0}); 225 ValidateFileHeaderBlock(B); 226 227 B = profileCollectorService::nextBuffer(B); 228 ValidateBlock(B); 229 230 B = profileCollectorService::nextBuffer(B); 231 ValidateBlock(B); 232 } 233 234 } // namespace 235 } // namespace __xray 236