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