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
23namespace __xray {
24namespace {
25
26static constexpr auto kHeaderSize = 16u;
27
28constexpr uptr ExpectedProfilingVersion = 0x20180424;
29
30struct 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
38void 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(dest: &FileHeaderStorage, src: B.Data, n: 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
51void 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(dest: LocalBuffer, src: B.Data, n: kHeaderSize);
61 u32 BlockSize = 0;
62 u32 BlockNumber = 0;
63 u64 ThreadId = 0;
64 internal_memcpy(dest: &BlockSize, src: LocalBuffer, n: sizeof(u32));
65 internal_memcpy(dest: &BlockNumber, src: LocalBuffer + sizeof(u32), n: sizeof(u32));
66 internal_memcpy(dest: &ThreadId, src: LocalBuffer + (2 * sizeof(u32)), n: sizeof(u64));
67 ASSERT_NE(BlockSize, 0u);
68 ASSERT_GE(BlockNumber, 0u);
69 ASSERT_NE(ThreadId, 0u);
70}
71
72std::tuple<u32, u32, u64> ParseBlockHeader(XRayBuffer B) {
73 char LocalBuffer[kHeaderSize] = {};
74 internal_memcpy(dest: LocalBuffer, src: B.Data, n: kHeaderSize);
75 u32 BlockSize = 0;
76 u32 BlockNumber = 0;
77 u64 ThreadId = 0;
78 internal_memcpy(dest: &BlockSize, src: LocalBuffer, n: sizeof(u32));
79 internal_memcpy(dest: &BlockNumber, src: LocalBuffer + sizeof(u32), n: sizeof(u32));
80 internal_memcpy(dest: &ThreadId, src: LocalBuffer + (2 * sizeof(u32)), n: sizeof(u64));
81 return std::make_tuple(args&: BlockSize, args&: BlockNumber, args&: ThreadId);
82}
83
84struct Profile {
85 int64_t CallCount;
86 int64_t CumulativeLocalTime;
87 std::vector<int32_t> Path;
88};
89
90std::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(dest: &F, src: P, n: sizeof(int32_t));
96 P += sizeof(int32_t);
97 Result.Path.push_back(x: F);
98 } while (F != 0);
99
100 // Then read the CallCount.
101 internal_memcpy(dest: &Result.CallCount, src: P, n: sizeof(int64_t));
102 P += sizeof(int64_t);
103
104 // Then read the CumulativeLocalTime.
105 internal_memcpy(dest: &Result.CumulativeLocalTime, src: P, n: sizeof(int64_t));
106 P += sizeof(int64_t);
107 return std::make_tuple(args: std::move(t&: Result), args&: P);
108}
109
110TEST(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.
182void 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(Buf&: B.NodeBuffer);
189 BQ.getBuffer(Buf&: B.RootsBuffer);
190 BQ.getBuffer(Buf&: B.ShadowStackBuffer);
191 BQ.getBuffer(Buf&: B.NodeIdPairBuffer);
192 return B;
193 }();
194
195 thread_local auto Allocators =
196 FunctionCallTrie::InitAllocatorsFromBuffers(Bufs&: Buffers);
197
198 FunctionCallTrie T(Allocators);
199
200 T.enterFunction(FId: 1, TSC: 1, CPU: 0);
201 T.enterFunction(FId: 2, TSC: 2, CPU: 0);
202 T.exitFunction(FId: 2, TSC: 3, CPU: 0);
203 T.exitFunction(FId: 1, TSC: 4, CPU: 0);
204
205 profileCollectorService::post(Q: &BQ, T: std::move(t&: T), A: std::move(t&: Allocators),
206 B: std::move(t&: Buffers), TId: GetTid());
207}
208
209TEST(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

source code of compiler-rt/lib/xray/tests/unit/profile_collector_test.cpp