]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - lib/xray/tests/unit/profile_collector_test.cc
Vendor import of compiler-rt trunk r351319 (just before the release_80
[FreeBSD/FreeBSD.git] / lib / xray / tests / unit / profile_collector_test.cc
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