1 //===- unittest/ProfileData/SampleProfTest.cpp ------------------*- C++ -*-===//
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
7 //===----------------------------------------------------------------------===//
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"
26 using namespace sampleprof
;
28 static ::testing::AssertionResult
NoError(std::error_code EC
) {
30 return ::testing::AssertionSuccess();
31 return ::testing::AssertionFailure() << "error " << EC
.value() << ": "
37 struct SampleProfTest
: ::testing::Test
{
39 std::unique_ptr
<SampleProfileWriter
> Writer
;
40 std::unique_ptr
<SampleProfileReader
> Reader
;
42 SampleProfTest() : Writer(), Reader() {}
44 void createWriter(SampleProfileFormat Format
, StringRef Profile
) {
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());
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
);
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
);
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);
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);
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
);
95 StringMap
<FunctionSamples
> Profiles
;
96 Profiles
[FooName
] = std::move(FooSamples
);
97 Profiles
[BarName
] = std::move(BarSamples
);
99 ProfileSymbolList List
;
100 if (Format
== SampleProfileFormat::SPF_Ext_Binary
) {
101 List
.add("zoo", true);
102 List
.add("moo", true);
104 Writer
->setProfileSymbolList(&List
);
107 EC
= Writer
->write(Profiles
);
108 ASSERT_TRUE(NoError(EC
));
110 Writer
->getOutputStream().flush();
112 readProfile(M
, Profile
);
115 ASSERT_TRUE(NoError(EC
));
117 if (Format
== SampleProfileFormat::SPF_Ext_Binary
) {
118 std::unique_ptr
<ProfileSymbolList
> ReaderList
=
119 Reader
->getProfileSymbolList();
120 ReaderList
->contains("zoo");
121 ReaderList
->contains("moo");
125 auto MemBuffer
= llvm::MemoryBuffer::getMemBuffer(R
"(
126 # Types 'int' and 'long' are equivalent
128 # Function names 'foo' and 'faux' are equivalent
131 Reader
.reset(new SampleProfileReaderItaniumRemapper(
132 std::move(MemBuffer
), Context
, std::move(Reader
)));
133 FooName
= "_Z4fauxi";
137 ASSERT_TRUE(NoError(EC
));
140 ASSERT_EQ(2u, Reader
->getProfiles().size());
142 FunctionSamples
*ReadFooSamples
= Reader
->getSamplesFor(FooName
);
143 ASSERT_TRUE(ReadFooSamples
!= nullptr);
144 if (Format
!= SampleProfileFormat::SPF_Compact_Binary
) {
145 ASSERT_EQ("_Z3fooi", ReadFooSamples
->getName());
147 ASSERT_EQ(7711u, ReadFooSamples
->getTotalSamples());
148 ASSERT_EQ(610u, ReadFooSamples
->getHeadSamples());
150 FunctionSamples
*ReadBarSamples
= Reader
->getSamplesFor(BarName
);
151 ASSERT_TRUE(ReadBarSamples
!= nullptr);
152 if (Format
!= SampleProfileFormat::SPF_Compact_Binary
) {
153 ASSERT_EQ("_Z3bari", ReadBarSamples
->getName());
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());
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
]);
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());
178 uint32_t Cutoff
= 800000;
179 auto Predicate
= [&Cutoff
](const ProfileSummaryEntry
&PE
) {
180 return PE
.Cutoff
== Cutoff
;
182 std::vector
<ProfileSummaryEntry
> &Details
= Summary
.getDetailedSummary();
183 auto EightyPerc
= find_if(Details
, Predicate
);
185 auto NinetyPerc
= find_if(Details
, Predicate
);
187 auto NinetyFivePerc
= find_if(Details
, Predicate
);
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
);
196 ProfileSummary
&Summary
= Reader
->getSummary();
197 VerifySummary(Summary
);
199 // Test that conversion of summary to and from Metadata works.
200 Metadata
*MD
= Summary
.getMD(Context
);
202 ProfileSummary
*PS
= ProfileSummary::getFromMD(MD
);
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);
211 PS
= ProfileSummary::getFromMD(MD
);
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
;
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")
233 addFunctionSamples(&Smap
, "foo.bar", uint64_t(20303), uint64_t(1439));
234 if (Policy
== "selected")
236 addFunctionSamples(&Smap
, "foo.llvm.2465", uint64_t(20305), uint64_t(1441));
240 void createFunctionWithSampleProfileElisionPolicy(Module
*M
,
243 FunctionType
*FnType
=
244 FunctionType::get(Type::getVoidTy(Context
), {}, false);
245 auto Inserted
= M
->getOrInsertFunction(Fname
, FnType
);
246 auto Fcn
= cast
<Function
>(Inserted
.getCallee());
248 Fcn
->addFnAttr("sample-profile-suffix-elision-policy", Policy
);
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
);
257 void testSuffixElisionPolicy(SampleProfileFormat Format
, StringRef Policy
,
258 const StringMap
<uint64_t> &Expected
) {
259 SmallVector
<char, 128> ProfilePath
;
261 EC
= llvm::sys::fs::createTemporaryFile("profile", "", ProfilePath
);
262 ASSERT_TRUE(NoError(EC
));
263 StringRef
ProfileFile(ProfilePath
.data(), ProfilePath
.size());
265 Module
M("my_module", Context
);
266 setupModuleForElisionTest(&M
, Policy
);
267 StringMap
<FunctionSamples
> ProfMap
= setupFcnSamplesForElisionTest(Policy
);
270 createWriter(Format
, ProfileFile
);
271 EC
= Writer
->write(ProfMap
);
272 ASSERT_TRUE(NoError(EC
));
273 Writer
->getOutputStream().flush();
276 readProfile(M
, ProfileFile
);
278 ASSERT_TRUE(NoError(EC
));
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
);
290 TEST_F(SampleProfTest
, roundtrip_text_profile
) {
291 testRoundTrip(SampleProfileFormat::SPF_Text
, false);
294 TEST_F(SampleProfTest
, roundtrip_raw_binary_profile
) {
295 testRoundTrip(SampleProfileFormat::SPF_Binary
, false);
298 TEST_F(SampleProfTest
, roundtrip_compact_binary_profile
) {
299 testRoundTrip(SampleProfileFormat::SPF_Compact_Binary
, false);
302 TEST_F(SampleProfTest
, roundtrip_ext_binary_profile
) {
303 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, false);
306 TEST_F(SampleProfTest
, remap_text_profile
) {
307 testRoundTrip(SampleProfileFormat::SPF_Text
, true);
310 TEST_F(SampleProfTest
, remap_raw_binary_profile
) {
311 testRoundTrip(SampleProfileFormat::SPF_Binary
, true);
314 TEST_F(SampleProfTest
, remap_ext_binary_profile
) {
315 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, true);
318 TEST_F(SampleProfTest
, sample_overflow_saturation
) {
319 const uint64_t Max
= std::numeric_limits
<uint64_t>::max();
320 sampleprof_error Result
;
322 FunctionSamples FooSamples
;
323 Result
= FooSamples
.addTotalSamples(1);
324 ASSERT_EQ(Result
, sampleprof_error::success
);
326 Result
= FooSamples
.addHeadSamples(1);
327 ASSERT_EQ(Result
, sampleprof_error::success
);
329 Result
= FooSamples
.addBodySamples(10, 0, 1);
330 ASSERT_EQ(Result
, sampleprof_error::success
);
332 Result
= FooSamples
.addTotalSamples(Max
);
333 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
334 ASSERT_EQ(FooSamples
.getTotalSamples(), Max
);
336 Result
= FooSamples
.addHeadSamples(Max
);
337 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
338 ASSERT_EQ(FooSamples
.getHeadSamples(), Max
);
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
);
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
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
);
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
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
, "",
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
);
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",
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
);
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",
420 } // end anonymous namespace