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