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