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
TEST(ProfileTest,CreateProfile)26 TEST(ProfileTest, CreateProfile) { Profile P; }
27
TEST(ProfileTest,InternPath)28 TEST(ProfileTest, InternPath) {
29 Profile P;
30 auto Path0 = P.internPath({3, 2, 1});
31 auto Path1 = P.internPath({3, 2, 1});
32 auto Path2 = P.internPath({2, 1});
33 EXPECT_THAT(Path0, Eq(Path1));
34 EXPECT_THAT(Path0, Not(Eq(Path2)));
35 }
36
TEST(ProfileTest,ExpandPath)37 TEST(ProfileTest, ExpandPath) {
38 Profile P;
39 auto PathID = P.internPath({3, 2, 1});
40 auto PathOrError = P.expandPath(PathID);
41 if (!PathOrError)
42 FAIL() << "Error: " << PathOrError.takeError();
43 EXPECT_THAT(PathOrError.get(), ElementsAre(3, 2, 1));
44 }
45
TEST(ProfileTest,AddBlocks)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
TEST(ProfileTest,CopyProfile)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
TEST(ProfileTest,MoveProfile)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
TEST(ProfileTest,MergeProfilesByThread)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(P0, 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
TEST(ProfileTest,MergeProfilesByStack)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(P0, 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
TEST(ProfileTest,MergeProfilesByStackAccumulate)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(Profiles.begin(), Profiles.end(), Profile(),
197 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
TEST(ProfileTest,MergeProfilesByThreadAccumulate)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(Profiles.begin(), Profiles.end(), Profile(),
233 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