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 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
);
61 void createRemapFile(SmallVectorImpl
<char> &RemapPath
, StringRef
&RemapFile
) {
63 llvm::sys::fs::createTemporaryFile("remapfile", "", RemapPath
);
64 ASSERT_TRUE(NoError(EC
));
65 RemapFile
= StringRef(RemapPath
.data(), RemapPath
.size());
67 std::unique_ptr
<raw_fd_ostream
> OS(
68 new raw_fd_ostream(RemapFile
, EC
, sys::fs::OF_None
));
70 # Types 'int' and 'long' are equivalent
72 # Function names 'foo' and 'faux' are equivalent
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
);
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);
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);
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);
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);
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
);
127 Module
M("my_module", Context
);
128 FunctionType
*fn_type
=
129 FunctionType::get(Type::getVoidTy(Context
), {}, false);
131 SmallVector
<char, 128> RemapPath
;
134 createRemapFile(RemapPath
, RemapFile
);
135 FooName
= "_Z4fauxi";
139 M
.getOrInsertFunction(FooName
, fn_type
);
140 M
.getOrInsertFunction(BarName
, fn_type
);
141 M
.getOrInsertFunction(BooName
, fn_type
);
143 ProfileSymbolList List
;
144 if (Format
== SampleProfileFormat::SPF_Ext_Binary
) {
145 List
.add("zoo", true);
146 List
.add("moo", true);
148 Writer
->setProfileSymbolList(&List
);
151 EC
= Writer
->write(Profiles
);
152 ASSERT_TRUE(NoError(EC
));
154 Writer
->getOutputStream().flush();
156 readProfile(M
, Profile
, RemapFile
);
158 ASSERT_TRUE(NoError(EC
));
160 if (Format
== SampleProfileFormat::SPF_Ext_Binary
) {
161 std::unique_ptr
<ProfileSymbolList
> ReaderList
=
162 Reader
->getProfileSymbolList();
163 ReaderList
->contains("zoo");
164 ReaderList
->contains("moo");
167 FunctionSamples
*ReadFooSamples
= Reader
->getSamplesFor(FooName
);
168 ASSERT_TRUE(ReadFooSamples
!= nullptr);
169 if (Format
!= SampleProfileFormat::SPF_Compact_Binary
) {
170 ASSERT_EQ("_Z3fooi", ReadFooSamples
->getName());
172 ASSERT_EQ(7711u, ReadFooSamples
->getTotalSamples());
173 ASSERT_EQ(610u, ReadFooSamples
->getHeadSamples());
175 FunctionSamples
*ReadBarSamples
= Reader
->getSamplesFor(BarName
);
176 ASSERT_TRUE(ReadBarSamples
!= nullptr);
177 if (Format
!= SampleProfileFormat::SPF_Compact_Binary
) {
178 ASSERT_EQ("_Z3bari", ReadBarSamples
->getName());
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());
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());
195 ASSERT_TRUE(ReadBazSamples
!= nullptr);
196 ASSERT_EQ(12557u, ReadBazSamples
->getTotalSamples());
197 ASSERT_EQ(4u, Reader
->getProfiles().size());
200 FunctionSamples
*ReadBooSamples
= Reader
->getSamplesFor(BooName
);
201 ASSERT_TRUE(ReadBooSamples
!= nullptr);
202 ASSERT_EQ(1232u, ReadBooSamples
->getTotalSamples());
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
]);
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());
221 uint32_t Cutoff
= 800000;
222 auto Predicate
= [&Cutoff
](const ProfileSummaryEntry
&PE
) {
223 return PE
.Cutoff
== Cutoff
;
225 std::vector
<ProfileSummaryEntry
> &Details
= Summary
.getDetailedSummary();
226 auto EightyPerc
= find_if(Details
, Predicate
);
228 auto NinetyPerc
= find_if(Details
, Predicate
);
230 auto NinetyFivePerc
= find_if(Details
, Predicate
);
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
);
239 ProfileSummary
&Summary
= Reader
->getSummary();
240 VerifySummary(Summary
);
242 // Test that conversion of summary to and from Metadata works.
243 Metadata
*MD
= Summary
.getMD(Context
);
245 ProfileSummary
*PS
= ProfileSummary::getFromMD(MD
);
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);
254 PS
= ProfileSummary::getFromMD(MD
);
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
;
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")
276 addFunctionSamples(&Smap
, "foo.bar", uint64_t(20303), uint64_t(1439));
277 if (Policy
== "selected")
279 addFunctionSamples(&Smap
, "foo.llvm.2465", uint64_t(20305), uint64_t(1441));
283 void createFunctionWithSampleProfileElisionPolicy(Module
*M
,
286 FunctionType
*FnType
=
287 FunctionType::get(Type::getVoidTy(Context
), {}, false);
288 auto Inserted
= M
->getOrInsertFunction(Fname
, FnType
);
289 auto Fcn
= cast
<Function
>(Inserted
.getCallee());
291 Fcn
->addFnAttr("sample-profile-suffix-elision-policy", Policy
);
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
);
300 void testSuffixElisionPolicy(SampleProfileFormat Format
, StringRef Policy
,
301 const StringMap
<uint64_t> &Expected
) {
302 SmallVector
<char, 128> ProfilePath
;
304 EC
= llvm::sys::fs::createTemporaryFile("profile", "", ProfilePath
);
305 ASSERT_TRUE(NoError(EC
));
306 StringRef
ProfileFile(ProfilePath
.data(), ProfilePath
.size());
308 Module
M("my_module", Context
);
309 setupModuleForElisionTest(&M
, Policy
);
310 StringMap
<FunctionSamples
> ProfMap
= setupFcnSamplesForElisionTest(Policy
);
313 createWriter(Format
, ProfileFile
);
314 EC
= Writer
->write(ProfMap
);
315 ASSERT_TRUE(NoError(EC
));
316 Writer
->getOutputStream().flush();
319 readProfile(M
, ProfileFile
);
321 ASSERT_TRUE(NoError(EC
));
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
);
333 TEST_F(SampleProfTest
, roundtrip_text_profile
) {
334 testRoundTrip(SampleProfileFormat::SPF_Text
, false);
337 TEST_F(SampleProfTest
, roundtrip_raw_binary_profile
) {
338 testRoundTrip(SampleProfileFormat::SPF_Binary
, false);
341 TEST_F(SampleProfTest
, roundtrip_compact_binary_profile
) {
342 testRoundTrip(SampleProfileFormat::SPF_Compact_Binary
, false);
345 TEST_F(SampleProfTest
, roundtrip_ext_binary_profile
) {
346 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, false);
349 TEST_F(SampleProfTest
, remap_text_profile
) {
350 testRoundTrip(SampleProfileFormat::SPF_Text
, true);
353 TEST_F(SampleProfTest
, remap_raw_binary_profile
) {
354 testRoundTrip(SampleProfileFormat::SPF_Binary
, true);
357 TEST_F(SampleProfTest
, remap_ext_binary_profile
) {
358 testRoundTrip(SampleProfileFormat::SPF_Ext_Binary
, true);
361 TEST_F(SampleProfTest
, sample_overflow_saturation
) {
362 const uint64_t Max
= std::numeric_limits
<uint64_t>::max();
363 sampleprof_error Result
;
365 FunctionSamples FooSamples
;
366 Result
= FooSamples
.addTotalSamples(1);
367 ASSERT_EQ(Result
, sampleprof_error::success
);
369 Result
= FooSamples
.addHeadSamples(1);
370 ASSERT_EQ(Result
, sampleprof_error::success
);
372 Result
= FooSamples
.addBodySamples(10, 0, 1);
373 ASSERT_EQ(Result
, sampleprof_error::success
);
375 Result
= FooSamples
.addTotalSamples(Max
);
376 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
377 ASSERT_EQ(FooSamples
.getTotalSamples(), Max
);
379 Result
= FooSamples
.addHeadSamples(Max
);
380 ASSERT_EQ(Result
, sampleprof_error::counter_overflow
);
381 ASSERT_EQ(FooSamples
.getHeadSamples(), Max
);
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
);
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
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
);
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
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
, "",
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
);
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",
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
);
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",
463 } // end anonymous namespace