1 | //===- ProfileTest.cpp - XRay Profile unit tests ----------------*- C++ -*-===// |
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 | #include "llvm/XRay/Profile.h" |
9 | #include "gmock/gmock.h" |
10 | #include "gtest/gtest.h" |
11 | |
12 | #include <numeric> |
13 | |
14 | namespace llvm { |
15 | namespace xray { |
16 | namespace { |
17 | |
18 | using ::testing::AllOf; |
19 | using ::testing::ElementsAre; |
20 | using ::testing::Eq; |
21 | using ::testing::Field; |
22 | using ::testing::Not; |
23 | using ::testing::Pair; |
24 | using ::testing::UnorderedElementsAre; |
25 | |
26 | TEST(ProfileTest, CreateProfile) { Profile P; } |
27 | |
28 | TEST(ProfileTest, InternPath) { |
29 | Profile P; |
30 | auto Path0 = P.internPath(P: {3, 2, 1}); |
31 | auto Path1 = P.internPath(P: {3, 2, 1}); |
32 | auto Path2 = P.internPath(P: {2, 1}); |
33 | EXPECT_THAT(Path0, Eq(Path1)); |
34 | EXPECT_THAT(Path0, Not(Eq(Path2))); |
35 | } |
36 | |
37 | TEST(ProfileTest, ExpandPath) { |
38 | Profile P; |
39 | auto PathID = P.internPath(P: {3, 2, 1}); |
40 | auto PathOrError = P.expandPath(P: PathID); |
41 | if (!PathOrError) |
42 | FAIL() << "Error: " << PathOrError.takeError(); |
43 | EXPECT_THAT(PathOrError.get(), ElementsAre(3, 2, 1)); |
44 | } |
45 | |
46 | TEST(ProfileTest, AddBlocks) { |
47 | Profile P; |
48 | // Expect an error on adding empty blocks. |
49 | EXPECT_TRUE(errorToBool(P.addBlock({}))); |
50 | |
51 | // Thread blocks may not be empty. |
52 | EXPECT_TRUE(errorToBool(P.addBlock({1, {}}))); |
53 | |
54 | // Thread blocks with data must succeed. |
55 | EXPECT_FALSE(errorToBool(P.addBlock( |
56 | Profile::Block{Profile::ThreadID{1}, |
57 | { |
58 | {P.internPath({2, 1}), Profile::Data{1, 1000}}, |
59 | {P.internPath({3, 2, 1}), Profile::Data{10, 100}}, |
60 | }}))); |
61 | } |
62 | |
63 | TEST(ProfileTest, CopyProfile) { |
64 | Profile P0, P1; |
65 | EXPECT_FALSE(errorToBool(P0.addBlock( |
66 | Profile::Block{Profile::ThreadID{1}, |
67 | { |
68 | {P0.internPath({2, 1}), Profile::Data{1, 1000}}, |
69 | {P0.internPath({3, 2, 1}), Profile::Data{10, 100}}, |
70 | }}))); |
71 | P1 = P0; |
72 | EXPECT_THAT( |
73 | P1, UnorderedElementsAre(AllOf( |
74 | Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
75 | Field(&Profile::Block::PathData, |
76 | UnorderedElementsAre( |
77 | Pair(P1.internPath({2, 1}), |
78 | AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
79 | Field(&Profile::Data::CumulativeLocalTime, |
80 | Eq(1000u)))), |
81 | Pair(P1.internPath({3, 2, 1}), |
82 | AllOf(Field(&Profile::Data::CallCount, Eq(10u)), |
83 | Field(&Profile::Data::CumulativeLocalTime, |
84 | Eq(100u))))))))); |
85 | } |
86 | |
87 | TEST(ProfileTest, MoveProfile) { |
88 | Profile P0, P1; |
89 | EXPECT_FALSE(errorToBool(P0.addBlock( |
90 | Profile::Block{Profile::ThreadID{1}, |
91 | { |
92 | {P0.internPath({2, 1}), Profile::Data{1, 1000}}, |
93 | {P0.internPath({3, 2, 1}), Profile::Data{10, 100}}, |
94 | }}))); |
95 | P1 = std::move(P0); |
96 | EXPECT_THAT( |
97 | P1, UnorderedElementsAre(AllOf( |
98 | Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
99 | Field(&Profile::Block::PathData, |
100 | UnorderedElementsAre( |
101 | Pair(P1.internPath({2, 1}), |
102 | AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
103 | Field(&Profile::Data::CumulativeLocalTime, |
104 | Eq(1000u)))), |
105 | Pair(P1.internPath({3, 2, 1}), |
106 | AllOf(Field(&Profile::Data::CallCount, Eq(10u)), |
107 | Field(&Profile::Data::CumulativeLocalTime, |
108 | Eq(100u))))))))); |
109 | EXPECT_THAT(P0, UnorderedElementsAre()); |
110 | } |
111 | |
112 | TEST(ProfileTest, MergeProfilesByThread) { |
113 | Profile P0, P1; |
114 | |
115 | // Set up the blocks for two different threads in P0. |
116 | EXPECT_FALSE(errorToBool(P0.addBlock( |
117 | Profile::Block{Profile::ThreadID{1}, |
118 | {{P0.internPath({2, 1}), Profile::Data{1, 1000}}, |
119 | {P0.internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
120 | EXPECT_FALSE(errorToBool(P0.addBlock( |
121 | Profile::Block{Profile::ThreadID{2}, |
122 | {{P0.internPath({3, 1}), Profile::Data{1, 1000}}}}))); |
123 | |
124 | // Set up the blocks for two different threads in P1. |
125 | EXPECT_FALSE(errorToBool(P1.addBlock( |
126 | Profile::Block{Profile::ThreadID{1}, |
127 | {{P1.internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
128 | EXPECT_FALSE(errorToBool(P1.addBlock( |
129 | Profile::Block{Profile::ThreadID{2}, |
130 | {{P1.internPath({3, 1}), Profile::Data{1, 1000}}, |
131 | {P1.internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
132 | |
133 | Profile Merged = mergeProfilesByThread(L: P0, R: P1); |
134 | EXPECT_THAT( |
135 | Merged, |
136 | UnorderedElementsAre( |
137 | // We want to see two threads after the merge. |
138 | AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
139 | Field(&Profile::Block::PathData, |
140 | UnorderedElementsAre( |
141 | Pair(Merged.internPath({2, 1}), |
142 | AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
143 | Field(&Profile::Data::CumulativeLocalTime, |
144 | Eq(2000u)))), |
145 | Pair(Merged.internPath({4, 1}), |
146 | AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
147 | Field(&Profile::Data::CumulativeLocalTime, |
148 | Eq(1000u))))))), |
149 | AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{2})), |
150 | Field(&Profile::Block::PathData, |
151 | UnorderedElementsAre( |
152 | Pair(Merged.internPath({3, 1}), |
153 | AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
154 | Field(&Profile::Data::CumulativeLocalTime, |
155 | Eq(2000u)))), |
156 | Pair(Merged.internPath({4, 1}), |
157 | AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
158 | Field(&Profile::Data::CumulativeLocalTime, |
159 | Eq(1000u))))))))); |
160 | } |
161 | |
162 | TEST(ProfileTest, MergeProfilesByStack) { |
163 | Profile P0, P1; |
164 | EXPECT_FALSE(errorToBool(P0.addBlock( |
165 | Profile::Block{Profile::ThreadID{1}, |
166 | {{P0.internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
167 | EXPECT_FALSE(errorToBool(P1.addBlock( |
168 | Profile::Block{Profile::ThreadID{2}, |
169 | {{P1.internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
170 | |
171 | Profile Merged = mergeProfilesByStack(L: P0, R: P1); |
172 | EXPECT_THAT(Merged, |
173 | ElementsAre(AllOf( |
174 | // We expect that we lose the ThreadID dimension in this |
175 | // algorithm. |
176 | Field(&Profile::Block::Thread, Eq(Profile::ThreadID{0})), |
177 | Field(&Profile::Block::PathData, |
178 | ElementsAre(Pair( |
179 | Merged.internPath({2, 1}), |
180 | AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
181 | Field(&Profile::Data::CumulativeLocalTime, |
182 | Eq(2000u))))))))); |
183 | } |
184 | |
185 | TEST(ProfileTest, MergeProfilesByStackAccumulate) { |
186 | std::vector<Profile> Profiles(3); |
187 | EXPECT_FALSE(errorToBool(Profiles[0].addBlock(Profile::Block{ |
188 | Profile::ThreadID{1}, |
189 | {{Profiles[0].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
190 | EXPECT_FALSE(errorToBool(Profiles[1].addBlock(Profile::Block{ |
191 | Profile::ThreadID{2}, |
192 | {{Profiles[1].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
193 | EXPECT_FALSE(errorToBool(Profiles[2].addBlock(Profile::Block{ |
194 | Profile::ThreadID{3}, |
195 | {{Profiles[2].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
196 | Profile Merged = std::accumulate(first: Profiles.begin(), last: Profiles.end(), init: Profile(), |
197 | binary_op: mergeProfilesByStack); |
198 | EXPECT_THAT(Merged, |
199 | ElementsAre(AllOf( |
200 | // We expect that we lose the ThreadID dimension in this |
201 | // algorithm. |
202 | Field(&Profile::Block::Thread, Eq(Profile::ThreadID{0})), |
203 | Field(&Profile::Block::PathData, |
204 | ElementsAre(Pair( |
205 | Merged.internPath({2, 1}), |
206 | AllOf(Field(&Profile::Data::CallCount, Eq(3u)), |
207 | Field(&Profile::Data::CumulativeLocalTime, |
208 | Eq(3000u))))))))); |
209 | } |
210 | |
211 | TEST(ProfileTest, MergeProfilesByThreadAccumulate) { |
212 | std::vector<Profile> Profiles(2); |
213 | |
214 | // Set up the blocks for two different threads in Profiles[0]. |
215 | EXPECT_FALSE(errorToBool(Profiles[0].addBlock(Profile::Block{ |
216 | Profile::ThreadID{1}, |
217 | {{Profiles[0].internPath({2, 1}), Profile::Data{1, 1000}}, |
218 | {Profiles[0].internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
219 | EXPECT_FALSE(errorToBool(Profiles[0].addBlock(Profile::Block{ |
220 | Profile::ThreadID{2}, |
221 | {{Profiles[0].internPath({3, 1}), Profile::Data{1, 1000}}}}))); |
222 | |
223 | // Set up the blocks for two different threads in Profiles[1]. |
224 | EXPECT_FALSE(errorToBool(Profiles[1].addBlock(Profile::Block{ |
225 | Profile::ThreadID{1}, |
226 | {{Profiles[1].internPath({2, 1}), Profile::Data{1, 1000}}}}))); |
227 | EXPECT_FALSE(errorToBool(Profiles[1].addBlock(Profile::Block{ |
228 | Profile::ThreadID{2}, |
229 | {{Profiles[1].internPath({3, 1}), Profile::Data{1, 1000}}, |
230 | {Profiles[1].internPath({4, 1}), Profile::Data{1, 1000}}}}))); |
231 | |
232 | Profile Merged = std::accumulate(first: Profiles.begin(), last: Profiles.end(), init: Profile(), |
233 | binary_op: mergeProfilesByThread); |
234 | EXPECT_THAT( |
235 | Merged, |
236 | UnorderedElementsAre( |
237 | // We want to see two threads after the merge. |
238 | AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{1})), |
239 | Field(&Profile::Block::PathData, |
240 | UnorderedElementsAre( |
241 | Pair(Merged.internPath({2, 1}), |
242 | AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
243 | Field(&Profile::Data::CumulativeLocalTime, |
244 | Eq(2000u)))), |
245 | Pair(Merged.internPath({4, 1}), |
246 | AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
247 | Field(&Profile::Data::CumulativeLocalTime, |
248 | Eq(1000u))))))), |
249 | AllOf(Field(&Profile::Block::Thread, Eq(Profile::ThreadID{2})), |
250 | Field(&Profile::Block::PathData, |
251 | UnorderedElementsAre( |
252 | Pair(Merged.internPath({3, 1}), |
253 | AllOf(Field(&Profile::Data::CallCount, Eq(2u)), |
254 | Field(&Profile::Data::CumulativeLocalTime, |
255 | Eq(2000u)))), |
256 | Pair(Merged.internPath({4, 1}), |
257 | AllOf(Field(&Profile::Data::CallCount, Eq(1u)), |
258 | Field(&Profile::Data::CumulativeLocalTime, |
259 | Eq(1000u))))))))); |
260 | } |
261 | // FIXME: Add a test creating a Trace and generating a Profile |
262 | // FIXME: Add tests for ranking/sorting profile blocks by dimension |
263 | |
264 | } // namespace |
265 | } // namespace xray |
266 | } // namespace llvm |
267 | |