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