1 //===-- profile_collector_test.cc -----------------------------------------===//
3 // The LLVM Compiler Infrastructure
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
8 //===----------------------------------------------------------------------===//
10 // This file is a part of XRay, a function call tracing system.
12 //===----------------------------------------------------------------------===//
13 #include "gtest/gtest.h"
15 #include "xray_profile_collector.h"
16 #include "xray_profiling_flags.h"
27 static constexpr auto kHeaderSize = 16u;
29 constexpr uptr ExpectedProfilingVersion = 0x20180424;
31 struct ExpectedProfilingFileHeader {
32 const u64 MagicBytes = 0x7872617970726f66; // Identifier for XRay profiling
33 // files 'xrayprof' in hex.
34 const u64 Version = ExpectedProfilingVersion;
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
44 ExpectedProfilingFileHeader ExpectedHeader;
45 std::memcpy(&FileHeaderStorage, B.Data, B.Size);
47 *reinterpret_cast<ExpectedProfilingFileHeader *>(&FileHeaderStorage);
48 ASSERT_EQ(ExpectedHeader.MagicBytes, FileHeader.MagicBytes);
49 ASSERT_EQ(ExpectedHeader.Version, FileHeader.Version);
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
60 char LocalBuffer[kHeaderSize] = {};
61 internal_memcpy(LocalBuffer, B.Data, kHeaderSize);
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);
73 std::tuple<u32, u32, u64> ParseBlockHeader(XRayBuffer B) {
74 char LocalBuffer[kHeaderSize] = {};
75 internal_memcpy(LocalBuffer, B.Data, kHeaderSize);
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);
87 int64_t CumulativeLocalTime;
88 std::vector<int32_t> Path;
91 std::tuple<Profile, const char *> ParseProfile(const char *P) {
93 // Read the path first, until we find a sentinel 0.
96 internal_memcpy(&F, P, sizeof(int32_t));
98 Result.Path.push_back(F);
101 // Then read the CallCount.
102 internal_memcpy(&Result.CallCount, P, sizeof(int64_t));
103 P += sizeof(int64_t);
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);
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);
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);
132 // Reset the collector data structures.
133 profileCollectorService::reset();
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);
139 // Then we serialize the data.
140 profileCollectorService::serialize();
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);
148 B = profileCollectorService::nextBuffer(B);
153 std::tie(BlockSize, BlockNum, ThreadId) = ParseBlockHeader(B);
155 // We look at the serialized buffer to see whether the Trie we're expecting
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);
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);
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);
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);
196 thread_local auto Allocators =
197 FunctionCallTrie::InitAllocatorsFromBuffers(Buffers);
199 FunctionCallTrie T(Allocators);
201 T.enterFunction(1, 1, 0);
202 T.enterFunction(2, 2, 0);
203 T.exitFunction(2, 3, 0);
204 T.exitFunction(1, 4, 0);
206 profileCollectorService::post(&BQ, std::move(T), std::move(Allocators),
207 std::move(Buffers), GetTid());
210 TEST(profileCollectorServiceTest, PostSerializeCollectMultipleThread) {
211 profilingFlags()->setDefaults();
213 profileCollectorService::reset();
215 std::thread t1(threadProcessing);
216 std::thread t2(threadProcessing);
221 // At this point, t1 and t2 are already done with what they were doing.
222 profileCollectorService::serialize();
224 // Ensure that we see two buffers.
225 auto B = profileCollectorService::nextBuffer({nullptr, 0});
226 ValidateFileHeaderBlock(B);
228 B = profileCollectorService::nextBuffer(B);
231 B = profileCollectorService::nextBuffer(B);
236 } // namespace __xray