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({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 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 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(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 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 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 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