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