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