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