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