1 //===- unittest/ProfileData/SampleProfTest.cpp ------------------*- 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 9 #include "llvm/ProfileData/SampleProf.h" 10 #include "llvm/ADT/StringMap.h" 11 #include "llvm/ADT/StringRef.h" 12 #include "llvm/IR/LLVMContext.h" 13 #include "llvm/IR/Metadata.h" 14 #include "llvm/IR/Module.h" 15 #include "llvm/ProfileData/SampleProfReader.h" 16 #include "llvm/ProfileData/SampleProfWriter.h" 17 #include "llvm/Support/Casting.h" 18 #include "llvm/Support/ErrorOr.h" 19 #include "llvm/Support/MemoryBuffer.h" 20 #include "llvm/Support/raw_ostream.h" 21 #include "gtest/gtest.h" 22 #include <string> 23 #include <vector> 24 25 using namespace llvm; 26 using namespace sampleprof; 27 28 static ::testing::AssertionResult NoError(std::error_code EC) { 29 if (!EC) 30 return ::testing::AssertionSuccess(); 31 return ::testing::AssertionFailure() << "error " << EC.value() << ": " 32 << EC.message(); 33 } 34 35 namespace { 36 37 struct SampleProfTest : ::testing::Test { 38 LLVMContext Context; 39 std::unique_ptr<SampleProfileWriter> Writer; 40 std::unique_ptr<SampleProfileReader> Reader; 41 42 SampleProfTest() : Writer(), Reader() {} 43 44 void createWriter(SampleProfileFormat Format, StringRef Profile) { 45 std::error_code EC; 46 std::unique_ptr<raw_ostream> OS( 47 new raw_fd_ostream(Profile, EC, sys::fs::OF_None)); 48 auto WriterOrErr = SampleProfileWriter::create(OS, Format); 49 ASSERT_TRUE(NoError(WriterOrErr.getError())); 50 Writer = std::move(WriterOrErr.get()); 51 } 52 53 void readProfile(const Module &M, StringRef Profile, 54 StringRef RemapFile = "") { 55 auto ReaderOrErr = SampleProfileReader::create(Profile, Context, RemapFile); 56 ASSERT_TRUE(NoError(ReaderOrErr.getError())); 57 Reader = std::move(ReaderOrErr.get()); 58 Reader->collectFuncsFrom(M); 59 } 60 61 void createRemapFile(SmallVectorImpl<char> &RemapPath, StringRef &RemapFile) { 62 std::error_code EC = 63 llvm::sys::fs::createTemporaryFile("remapfile", "", RemapPath); 64 ASSERT_TRUE(NoError(EC)); 65 RemapFile = StringRef(RemapPath.data(), RemapPath.size()); 66 67 std::unique_ptr<raw_fd_ostream> OS( 68 new raw_fd_ostream(RemapFile, EC, sys::fs::OF_None)); 69 *OS << R"( 70 # Types 'int' and 'long' are equivalent 71 type i l 72 # Function names 'foo' and 'faux' are equivalent 73 name 3foo 4faux 74 )"; 75 OS->close(); 76 } 77 78 void testRoundTrip(SampleProfileFormat Format, bool Remap) { 79 SmallVector<char, 128> ProfilePath; 80 ASSERT_TRUE(NoError(llvm::sys::fs::createTemporaryFile("profile", "", ProfilePath))); 81 StringRef Profile(ProfilePath.data(), ProfilePath.size()); 82 createWriter(Format, Profile); 83 84 StringRef FooName("_Z3fooi"); 85 FunctionSamples FooSamples; 86 FooSamples.setName(FooName); 87 FooSamples.addTotalSamples(7711); 88 FooSamples.addHeadSamples(610); 89 FooSamples.addBodySamples(1, 0, 610); 90 FooSamples.addBodySamples(2, 0, 600); 91 FooSamples.addBodySamples(4, 0, 60000); 92 FooSamples.addBodySamples(8, 0, 60351); 93 FooSamples.addBodySamples(10, 0, 605); 94 95 StringRef BarName("_Z3bari"); 96 FunctionSamples BarSamples; 97 BarSamples.setName(BarName); 98 BarSamples.addTotalSamples(20301); 99 BarSamples.addHeadSamples(1437); 100 BarSamples.addBodySamples(1, 0, 1437); 101 // Test how reader/writer handles unmangled names. 102 StringRef MconstructName("_M_construct<char *>"); 103 StringRef StringviewName("string_view<std::allocator<char> >"); 104 BarSamples.addCalledTargetSamples(1, 0, MconstructName, 1000); 105 BarSamples.addCalledTargetSamples(1, 0, StringviewName, 437); 106 107 StringRef BazName("_Z3bazi"); 108 FunctionSamples BazSamples; 109 BazSamples.setName(BazName); 110 BazSamples.addTotalSamples(12557); 111 BazSamples.addHeadSamples(1257); 112 BazSamples.addBodySamples(1, 0, 12557); 113 114 StringRef BooName("_Z3booi"); 115 FunctionSamples BooSamples; 116 BooSamples.setName(BooName); 117 BooSamples.addTotalSamples(1232); 118 BooSamples.addHeadSamples(1); 119 BooSamples.addBodySamples(1, 0, 1232); 120 121 StringMap<FunctionSamples> Profiles; 122 Profiles[FooName] = std::move(FooSamples); 123 Profiles[BarName] = std::move(BarSamples); 124 Profiles[BazName] = std::move(BazSamples); 125 Profiles[BooName] = std::move(BooSamples); 126 127 Module M("my_module", Context); 128 FunctionType *fn_type = 129 FunctionType::get(Type::getVoidTy(Context), {}, false); 130 131 SmallVector<char, 128> RemapPath; 132 StringRef RemapFile; 133 if (Remap) { 134 createRemapFile(RemapPath, RemapFile); 135 FooName = "_Z4fauxi"; 136 BarName = "_Z3barl"; 137 } 138 139 M.getOrInsertFunction(FooName, fn_type); 140 M.getOrInsertFunction(BarName, fn_type); 141 M.getOrInsertFunction(BooName, fn_type); 142 143 ProfileSymbolList List; 144 if (Format == SampleProfileFormat::SPF_Ext_Binary) { 145 List.add("zoo", true); 146 List.add("moo", true); 147 } 148 Writer->setProfileSymbolList(&List); 149 150 std::error_code EC; 151 EC = Writer->write(Profiles); 152 ASSERT_TRUE(NoError(EC)); 153 154 Writer->getOutputStream().flush(); 155 156 readProfile(M, Profile, RemapFile); 157 EC = Reader->read(); 158 ASSERT_TRUE(NoError(EC)); 159 160 if (Format == SampleProfileFormat::SPF_Ext_Binary) { 161 std::unique_ptr<ProfileSymbolList> ReaderList = 162 Reader->getProfileSymbolList(); 163 ReaderList->contains("zoo"); 164 ReaderList->contains("moo"); 165 } 166 167 FunctionSamples *ReadFooSamples = Reader->getSamplesFor(FooName); 168 ASSERT_TRUE(ReadFooSamples != nullptr); 169 if (Format != SampleProfileFormat::SPF_Compact_Binary) { 170 ASSERT_EQ("_Z3fooi", ReadFooSamples->getName()); 171 } 172 ASSERT_EQ(7711u, ReadFooSamples->getTotalSamples()); 173 ASSERT_EQ(610u, ReadFooSamples->getHeadSamples()); 174 175 FunctionSamples *ReadBarSamples = Reader->getSamplesFor(BarName); 176 ASSERT_TRUE(ReadBarSamples != nullptr); 177 if (Format != SampleProfileFormat::SPF_Compact_Binary) { 178 ASSERT_EQ("_Z3bari", ReadBarSamples->getName()); 179 } 180 ASSERT_EQ(20301u, ReadBarSamples->getTotalSamples()); 181 ASSERT_EQ(1437u, ReadBarSamples->getHeadSamples()); 182 ErrorOr<SampleRecord::CallTargetMap> CTMap = 183 ReadBarSamples->findCallTargetMapAt(1, 0); 184 ASSERT_FALSE(CTMap.getError()); 185 186 // Because _Z3bazi is not defined in module M, expect _Z3bazi's profile 187 // is not loaded when the profile is ExtBinary or Compact format because 188 // these formats support loading function profiles on demand. 189 FunctionSamples *ReadBazSamples = Reader->getSamplesFor(BazName); 190 if (Format == SampleProfileFormat::SPF_Ext_Binary || 191 Format == SampleProfileFormat::SPF_Compact_Binary) { 192 ASSERT_TRUE(ReadBazSamples == nullptr); 193 ASSERT_EQ(3u, Reader->getProfiles().size()); 194 } else { 195 ASSERT_TRUE(ReadBazSamples != nullptr); 196 ASSERT_EQ(12557u, ReadBazSamples->getTotalSamples()); 197 ASSERT_EQ(4u, Reader->getProfiles().size()); 198 } 199 200 FunctionSamples *ReadBooSamples = Reader->getSamplesFor(BooName); 201 ASSERT_TRUE(ReadBooSamples != nullptr); 202 ASSERT_EQ(1232u, ReadBooSamples->getTotalSamples()); 203 204 std::string MconstructGUID; 205 StringRef MconstructRep = 206 getRepInFormat(MconstructName, Format, MconstructGUID); 207 std::string StringviewGUID; 208 StringRef StringviewRep = 209 getRepInFormat(StringviewName, Format, StringviewGUID); 210 ASSERT_EQ(1000u, CTMap.get()[MconstructRep]); 211 ASSERT_EQ(437u, CTMap.get()[StringviewRep]); 212 213 auto VerifySummary = [](ProfileSummary &Summary) mutable { 214 ASSERT_EQ(ProfileSummary::PSK_Sample, Summary.getKind()); 215 ASSERT_EQ(137392u, Summary.getTotalCount()); 216 ASSERT_EQ(8u, Summary.getNumCounts()); 217 ASSERT_EQ(4u, Summary.getNumFunctions()); 218 ASSERT_EQ(1437u, Summary.getMaxFunctionCount()); 219 ASSERT_EQ(60351u, Summary.getMaxCount()); 220 221 uint32_t Cutoff = 800000; 222 auto Predicate = [&Cutoff](const ProfileSummaryEntry &PE) { 223 return PE.Cutoff == Cutoff; 224 }; 225 std::vector<ProfileSummaryEntry> &Details = Summary.getDetailedSummary(); 226 auto EightyPerc = find_if(Details, Predicate); 227 Cutoff = 900000; 228 auto NinetyPerc = find_if(Details, Predicate); 229 Cutoff = 950000; 230 auto NinetyFivePerc = find_if(Details, Predicate); 231 Cutoff = 990000; 232 auto NinetyNinePerc = find_if(Details, Predicate); 233 ASSERT_EQ(60000u, EightyPerc->MinCount); 234 ASSERT_EQ(12557u, NinetyPerc->MinCount); 235 ASSERT_EQ(12557u, NinetyFivePerc->MinCount); 236 ASSERT_EQ(610u, NinetyNinePerc->MinCount); 237 }; 238 239 ProfileSummary &Summary = Reader->getSummary(); 240 VerifySummary(Summary); 241 242 // Test that conversion of summary to and from Metadata works. 243 Metadata *MD = Summary.getMD(Context); 244 ASSERT_TRUE(MD); 245 ProfileSummary *PS = ProfileSummary::getFromMD(MD); 246 ASSERT_TRUE(PS); 247 VerifySummary(*PS); 248 delete PS; 249 250 // Test that summary can be attached to and read back from module. 251 M.setProfileSummary(MD, ProfileSummary::PSK_Sample); 252 MD = M.getProfileSummary(/* IsCS */ false); 253 ASSERT_TRUE(MD); 254 PS = ProfileSummary::getFromMD(MD); 255 ASSERT_TRUE(PS); 256 VerifySummary(*PS); 257 delete PS; 258 } 259 260 void addFunctionSamples(StringMap<FunctionSamples> *Smap, const char *Fname, 261 uint64_t TotalSamples, uint64_t HeadSamples) { 262 StringRef Name(Fname); 263 FunctionSamples FcnSamples; 264 FcnSamples.setName(Name); 265 FcnSamples.addTotalSamples(TotalSamples); 266 FcnSamples.addHeadSamples(HeadSamples); 267 FcnSamples.addBodySamples(1, 0, HeadSamples); 268 (*Smap)[Name] = FcnSamples; 269 } 270 271 StringMap<FunctionSamples> setupFcnSamplesForElisionTest(StringRef Policy) { 272 StringMap<FunctionSamples> Smap; 273 addFunctionSamples(&Smap, "foo", uint64_t(20301), uint64_t(1437)); 274 if (Policy == "" || Policy == "all") 275 return Smap; 276 addFunctionSamples(&Smap, "foo.bar", uint64_t(20303), uint64_t(1439)); 277 if (Policy == "selected") 278 return Smap; 279 addFunctionSamples(&Smap, "foo.llvm.2465", uint64_t(20305), uint64_t(1441)); 280 return Smap; 281 } 282 283 void createFunctionWithSampleProfileElisionPolicy(Module *M, 284 const char *Fname, 285 StringRef Policy) { 286 FunctionType *FnType = 287 FunctionType::get(Type::getVoidTy(Context), {}, false); 288 auto Inserted = M->getOrInsertFunction(Fname, FnType); 289 auto Fcn = cast<Function>(Inserted.getCallee()); 290 if (Policy != "") 291 Fcn->addFnAttr("sample-profile-suffix-elision-policy", Policy); 292 } 293 294 void setupModuleForElisionTest(Module *M, StringRef Policy) { 295 createFunctionWithSampleProfileElisionPolicy(M, "foo", Policy); 296 createFunctionWithSampleProfileElisionPolicy(M, "foo.bar", Policy); 297 createFunctionWithSampleProfileElisionPolicy(M, "foo.llvm.2465", Policy); 298 } 299 300 void testSuffixElisionPolicy(SampleProfileFormat Format, StringRef Policy, 301 const StringMap<uint64_t> &Expected) { 302 SmallVector<char, 128> ProfilePath; 303 std::error_code EC; 304 EC = llvm::sys::fs::createTemporaryFile("profile", "", ProfilePath); 305 ASSERT_TRUE(NoError(EC)); 306 StringRef ProfileFile(ProfilePath.data(), ProfilePath.size()); 307 308 Module M("my_module", Context); 309 setupModuleForElisionTest(&M, Policy); 310 StringMap<FunctionSamples> ProfMap = setupFcnSamplesForElisionTest(Policy); 311 312 // write profile 313 createWriter(Format, ProfileFile); 314 EC = Writer->write(ProfMap); 315 ASSERT_TRUE(NoError(EC)); 316 Writer->getOutputStream().flush(); 317 318 // read profile 319 readProfile(M, ProfileFile); 320 EC = Reader->read(); 321 ASSERT_TRUE(NoError(EC)); 322 323 for (auto I = Expected.begin(); I != Expected.end(); ++I) { 324 uint64_t Esamples = uint64_t(-1); 325 FunctionSamples *Samples = Reader->getSamplesFor(I->getKey()); 326 if (Samples != nullptr) 327 Esamples = Samples->getTotalSamples(); 328 ASSERT_EQ(I->getValue(), Esamples); 329 } 330 } 331 }; 332 333 TEST_F(SampleProfTest, roundtrip_text_profile) { 334 testRoundTrip(SampleProfileFormat::SPF_Text, false); 335 } 336 337 TEST_F(SampleProfTest, roundtrip_raw_binary_profile) { 338 testRoundTrip(SampleProfileFormat::SPF_Binary, false); 339 } 340 341 TEST_F(SampleProfTest, roundtrip_compact_binary_profile) { 342 testRoundTrip(SampleProfileFormat::SPF_Compact_Binary, false); 343 } 344 345 TEST_F(SampleProfTest, roundtrip_ext_binary_profile) { 346 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, false); 347 } 348 349 TEST_F(SampleProfTest, remap_text_profile) { 350 testRoundTrip(SampleProfileFormat::SPF_Text, true); 351 } 352 353 TEST_F(SampleProfTest, remap_raw_binary_profile) { 354 testRoundTrip(SampleProfileFormat::SPF_Binary, true); 355 } 356 357 TEST_F(SampleProfTest, remap_ext_binary_profile) { 358 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary, true); 359 } 360 361 TEST_F(SampleProfTest, sample_overflow_saturation) { 362 const uint64_t Max = std::numeric_limits<uint64_t>::max(); 363 sampleprof_error Result; 364 365 FunctionSamples FooSamples; 366 Result = FooSamples.addTotalSamples(1); 367 ASSERT_EQ(Result, sampleprof_error::success); 368 369 Result = FooSamples.addHeadSamples(1); 370 ASSERT_EQ(Result, sampleprof_error::success); 371 372 Result = FooSamples.addBodySamples(10, 0, 1); 373 ASSERT_EQ(Result, sampleprof_error::success); 374 375 Result = FooSamples.addTotalSamples(Max); 376 ASSERT_EQ(Result, sampleprof_error::counter_overflow); 377 ASSERT_EQ(FooSamples.getTotalSamples(), Max); 378 379 Result = FooSamples.addHeadSamples(Max); 380 ASSERT_EQ(Result, sampleprof_error::counter_overflow); 381 ASSERT_EQ(FooSamples.getHeadSamples(), Max); 382 383 Result = FooSamples.addBodySamples(10, 0, Max); 384 ASSERT_EQ(Result, sampleprof_error::counter_overflow); 385 ErrorOr<uint64_t> BodySamples = FooSamples.findSamplesAt(10, 0); 386 ASSERT_FALSE(BodySamples.getError()); 387 ASSERT_EQ(BodySamples.get(), Max); 388 } 389 390 TEST_F(SampleProfTest, default_suffix_elision_text) { 391 // Default suffix elision policy: strip everything after first dot. 392 // This implies that all suffix variants will map to "foo", so 393 // we don't expect to see any entries for them in the sample 394 // profile. 395 StringMap<uint64_t> Expected; 396 Expected["foo"] = uint64_t(20301); 397 Expected["foo.bar"] = uint64_t(-1); 398 Expected["foo.llvm.2465"] = uint64_t(-1); 399 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, "", Expected); 400 } 401 402 TEST_F(SampleProfTest, default_suffix_elision_compact_binary) { 403 // Default suffix elision policy: strip everything after first dot. 404 // This implies that all suffix variants will map to "foo", so 405 // we don't expect to see any entries for them in the sample 406 // profile. 407 StringMap<uint64_t> Expected; 408 Expected["foo"] = uint64_t(20301); 409 Expected["foo.bar"] = uint64_t(-1); 410 Expected["foo.llvm.2465"] = uint64_t(-1); 411 testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary, "", 412 Expected); 413 } 414 415 TEST_F(SampleProfTest, selected_suffix_elision_text) { 416 // Profile is created and searched using the "selected" 417 // suffix elision policy: we only strip a .XXX suffix if 418 // it matches a pattern known to be generated by the compiler 419 // (e.g. ".llvm.<digits>"). 420 StringMap<uint64_t> Expected; 421 Expected["foo"] = uint64_t(20301); 422 Expected["foo.bar"] = uint64_t(20303); 423 Expected["foo.llvm.2465"] = uint64_t(-1); 424 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, "selected", Expected); 425 } 426 427 TEST_F(SampleProfTest, selected_suffix_elision_compact_binary) { 428 // Profile is created and searched using the "selected" 429 // suffix elision policy: we only strip a .XXX suffix if 430 // it matches a pattern known to be generated by the compiler 431 // (e.g. ".llvm.<digits>"). 432 StringMap<uint64_t> Expected; 433 Expected["foo"] = uint64_t(20301); 434 Expected["foo.bar"] = uint64_t(20303); 435 Expected["foo.llvm.2465"] = uint64_t(-1); 436 testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary, "selected", 437 Expected); 438 } 439 440 TEST_F(SampleProfTest, none_suffix_elision_text) { 441 // Profile is created and searched using the "none" 442 // suffix elision policy: no stripping of suffixes at all. 443 // Here we expect to see all variants in the profile. 444 StringMap<uint64_t> Expected; 445 Expected["foo"] = uint64_t(20301); 446 Expected["foo.bar"] = uint64_t(20303); 447 Expected["foo.llvm.2465"] = uint64_t(20305); 448 testSuffixElisionPolicy(SampleProfileFormat::SPF_Text, "none", Expected); 449 } 450 451 TEST_F(SampleProfTest, none_suffix_elision_compact_binary) { 452 // Profile is created and searched using the "none" 453 // suffix elision policy: no stripping of suffixes at all. 454 // Here we expect to see all variants in the profile. 455 StringMap<uint64_t> Expected; 456 Expected["foo"] = uint64_t(20301); 457 Expected["foo.bar"] = uint64_t(20303); 458 Expected["foo.llvm.2465"] = uint64_t(20305); 459 testSuffixElisionPolicy(SampleProfileFormat::SPF_Compact_Binary, "none", 460 Expected); 461 } 462 463 } // end anonymous namespace 464