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