xref: /llvm-project/llvm/unittests/ProfileData/SampleProfTest.cpp (revision adcd02683856c30ba6f349279509acecd90063df)
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