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