1 #include "llvm/ProfileData/MemProf.h"
2 #include "llvm/ADT/DenseMap.h"
3 #include "llvm/ADT/MapVector.h"
4 #include "llvm/DebugInfo/DIContext.h"
5 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
6 #include "llvm/IR/Function.h"
7 #include "llvm/IR/Value.h"
8 #include "llvm/Object/ObjectFile.h"
9 #include "llvm/ProfileData/InstrProf.h"
10 #include "llvm/ProfileData/MemProfData.inc"
11 #include "llvm/ProfileData/RawMemProfReader.h"
12 #include "llvm/Support/Error.h"
13 #include "llvm/Support/MD5.h"
14 #include "llvm/Support/raw_ostream.h"
15 #include "gmock/gmock.h"
16 #include "gtest/gtest.h"
18 #include <initializer_list>
22 using ::llvm::DIGlobal
;
23 using ::llvm::DIInliningInfo
;
24 using ::llvm::DILineInfo
;
25 using ::llvm::DILineInfoSpecifier
;
26 using ::llvm::DILocal
;
27 using ::llvm::memprof::CallStackMap
;
28 using ::llvm::memprof::Frame
;
29 using ::llvm::memprof::FrameId
;
30 using ::llvm::memprof::IndexedMemProfRecord
;
31 using ::llvm::memprof::MemInfoBlock
;
32 using ::llvm::memprof::MemProfRecord
;
33 using ::llvm::memprof::MemProfSchema
;
34 using ::llvm::memprof::Meta
;
35 using ::llvm::memprof::PortableMemInfoBlock
;
36 using ::llvm::memprof::RawMemProfReader
;
37 using ::llvm::memprof::SegmentEntry
;
38 using ::llvm::object::SectionedAddress
;
39 using ::llvm::symbolize::SymbolizableModule
;
40 using ::testing::Return
;
42 class MockSymbolizer
: public SymbolizableModule
{
44 MOCK_CONST_METHOD3(symbolizeInlinedCode
,
45 DIInliningInfo(SectionedAddress
, DILineInfoSpecifier
,
47 // Most of the methods in the interface are unused. We only mock the
48 // method that we expect to be called from the memprof reader.
49 virtual DILineInfo
symbolizeCode(SectionedAddress
, DILineInfoSpecifier
,
51 llvm_unreachable("unused");
53 virtual DIGlobal
symbolizeData(SectionedAddress
) const {
54 llvm_unreachable("unused");
56 virtual std::vector
<DILocal
> symbolizeFrame(SectionedAddress
) const {
57 llvm_unreachable("unused");
59 virtual bool isWin32Module() const { llvm_unreachable("unused"); }
60 virtual uint64_t getModulePreferredBase() const {
61 llvm_unreachable("unused");
66 std::string FunctionName
;
70 std::string FileName
= "valid/path.cc";
72 DIInliningInfo
makeInliningInfo(std::initializer_list
<MockInfo
> MockFrames
) {
73 DIInliningInfo Result
;
74 for (const auto &Item
: MockFrames
) {
76 Frame
.FunctionName
= Item
.FunctionName
;
77 Frame
.Line
= Item
.Line
;
78 Frame
.StartLine
= Item
.StartLine
;
79 Frame
.Column
= Item
.Column
;
80 Frame
.FileName
= Item
.FileName
;
81 Result
.addFrame(Frame
);
86 llvm::SmallVector
<SegmentEntry
, 4> makeSegments() {
87 llvm::SmallVector
<SegmentEntry
, 4> Result
;
88 // Mimic an entry for a non position independent executable.
89 Result
.emplace_back(0x0, 0x40000, 0x0);
93 const DILineInfoSpecifier
specifier() {
94 return DILineInfoSpecifier(
95 DILineInfoSpecifier::FileLineInfoKind::RawValue
,
96 DILineInfoSpecifier::FunctionNameKind::LinkageName
);
99 MATCHER_P4(FrameContains
, FunctionName
, LineOffset
, Column
, Inline
, "") {
100 const Frame
&F
= arg
;
102 const uint64_t ExpectedHash
= llvm::Function::getGUID(FunctionName
);
103 if (F
.Function
!= ExpectedHash
) {
104 *result_listener
<< "Hash mismatch";
107 if (F
.SymbolName
&& F
.SymbolName
.value() != FunctionName
) {
108 *result_listener
<< "SymbolName mismatch\nWant: " << FunctionName
109 << "\nGot: " << F
.SymbolName
.value();
112 if (F
.LineOffset
== LineOffset
&& F
.Column
== Column
&&
113 F
.IsInlineFrame
== Inline
) {
116 *result_listener
<< "LineOffset, Column or Inline mismatch";
120 MemProfSchema
getFullSchema() {
121 MemProfSchema Schema
;
122 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name);
123 #include "llvm/ProfileData/MIBEntryDef.inc"
128 TEST(MemProf
, FillsValue
) {
129 std::unique_ptr
<MockSymbolizer
> Symbolizer(new MockSymbolizer());
131 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x1000},
133 .Times(1) // Only once since we remember invalid PCs.
134 .WillRepeatedly(Return(makeInliningInfo({
135 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
138 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x2000},
140 .Times(1) // Only once since we cache the result for future lookups.
141 .WillRepeatedly(Return(makeInliningInfo({
143 {"bar", 201, 150, 20},
146 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x3000},
149 .WillRepeatedly(Return(makeInliningInfo({
155 CSM
[0x1] = {0x1000, 0x2000, 0x3000};
157 llvm::MapVector
<uint64_t, MemInfoBlock
> Prof
;
158 Prof
[0x1].AllocCount
= 1;
160 auto Seg
= makeSegments();
162 RawMemProfReader
Reader(std::move(Symbolizer
), Seg
, Prof
, CSM
,
165 llvm::DenseMap
<llvm::GlobalValue::GUID
, MemProfRecord
> Records
;
166 for (const auto &Pair
: Reader
) {
167 Records
.insert({Pair
.first
, Pair
.second
});
170 // Mock program psuedocode and expected memprof record contents.
172 // AllocSite CallSite
173 // inline foo() { new(); } Y N
174 // bar() { foo(); } Y Y
175 // inline xyz() { bar(); } N Y
176 // abc() { xyz(); } N Y
178 // We expect 4 records. We attach alloc site data to foo and bar, i.e.
179 // all frames bottom up until we find a non-inline frame. We attach call site
180 // data to bar, xyz and abc.
181 ASSERT_EQ(Records
.size(), 4U);
183 // Check the memprof record for foo.
184 const llvm::GlobalValue::GUID FooId
= IndexedMemProfRecord::getGUID("foo");
185 ASSERT_EQ(Records
.count(FooId
), 1U);
186 const MemProfRecord
&Foo
= Records
[FooId
];
187 ASSERT_EQ(Foo
.AllocSites
.size(), 1U);
188 EXPECT_EQ(Foo
.AllocSites
[0].Info
.getAllocCount(), 1U);
189 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[0],
190 FrameContains("foo", 5U, 30U, true));
191 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[1],
192 FrameContains("bar", 51U, 20U, false));
193 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[2],
194 FrameContains("xyz", 5U, 30U, true));
195 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[3],
196 FrameContains("abc", 5U, 30U, false));
197 EXPECT_TRUE(Foo
.CallSites
.empty());
199 // Check the memprof record for bar.
200 const llvm::GlobalValue::GUID BarId
= IndexedMemProfRecord::getGUID("bar");
201 ASSERT_EQ(Records
.count(BarId
), 1U);
202 const MemProfRecord
&Bar
= Records
[BarId
];
203 ASSERT_EQ(Bar
.AllocSites
.size(), 1U);
204 EXPECT_EQ(Bar
.AllocSites
[0].Info
.getAllocCount(), 1U);
205 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[0],
206 FrameContains("foo", 5U, 30U, true));
207 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[1],
208 FrameContains("bar", 51U, 20U, false));
209 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[2],
210 FrameContains("xyz", 5U, 30U, true));
211 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[3],
212 FrameContains("abc", 5U, 30U, false));
214 ASSERT_EQ(Bar
.CallSites
.size(), 1U);
215 ASSERT_EQ(Bar
.CallSites
[0].size(), 2U);
216 EXPECT_THAT(Bar
.CallSites
[0][0], FrameContains("foo", 5U, 30U, true));
217 EXPECT_THAT(Bar
.CallSites
[0][1], FrameContains("bar", 51U, 20U, false));
219 // Check the memprof record for xyz.
220 const llvm::GlobalValue::GUID XyzId
= IndexedMemProfRecord::getGUID("xyz");
221 ASSERT_EQ(Records
.count(XyzId
), 1U);
222 const MemProfRecord
&Xyz
= Records
[XyzId
];
223 ASSERT_EQ(Xyz
.CallSites
.size(), 1U);
224 ASSERT_EQ(Xyz
.CallSites
[0].size(), 2U);
225 // Expect the entire frame even though in practice we only need the first
227 EXPECT_THAT(Xyz
.CallSites
[0][0], FrameContains("xyz", 5U, 30U, true));
228 EXPECT_THAT(Xyz
.CallSites
[0][1], FrameContains("abc", 5U, 30U, false));
230 // Check the memprof record for abc.
231 const llvm::GlobalValue::GUID AbcId
= IndexedMemProfRecord::getGUID("abc");
232 ASSERT_EQ(Records
.count(AbcId
), 1U);
233 const MemProfRecord
&Abc
= Records
[AbcId
];
234 EXPECT_TRUE(Abc
.AllocSites
.empty());
235 ASSERT_EQ(Abc
.CallSites
.size(), 1U);
236 ASSERT_EQ(Abc
.CallSites
[0].size(), 2U);
237 EXPECT_THAT(Abc
.CallSites
[0][0], FrameContains("xyz", 5U, 30U, true));
238 EXPECT_THAT(Abc
.CallSites
[0][1], FrameContains("abc", 5U, 30U, false));
241 TEST(MemProf
, PortableWrapper
) {
242 MemInfoBlock
Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
243 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
246 const auto Schema
= getFullSchema();
247 PortableMemInfoBlock
WriteBlock(Info
);
250 llvm::raw_string_ostream
OS(Buffer
);
251 WriteBlock
.serialize(Schema
, OS
);
254 PortableMemInfoBlock
ReadBlock(
255 Schema
, reinterpret_cast<const unsigned char *>(Buffer
.data()));
257 EXPECT_EQ(ReadBlock
, WriteBlock
);
258 // Here we compare directly with the actual counts instead of MemInfoBlock
259 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros
260 // take a reference to the params, this results in unaligned accesses.
261 EXPECT_EQ(1UL, ReadBlock
.getAllocCount());
262 EXPECT_EQ(7ULL, ReadBlock
.getTotalAccessCount());
263 EXPECT_EQ(3UL, ReadBlock
.getAllocCpuId());
266 TEST(MemProf
, RecordSerializationRoundTrip
) {
267 const MemProfSchema Schema
= getFullSchema();
269 MemInfoBlock
Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
270 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
273 llvm::SmallVector
<llvm::SmallVector
<FrameId
>> AllocCallStacks
= {
274 {0x123, 0x345}, {0x123, 0x567}};
276 llvm::SmallVector
<llvm::SmallVector
<FrameId
>> CallSites
= {{0x333, 0x777}};
278 IndexedMemProfRecord Record
;
279 for (const auto &ACS
: AllocCallStacks
) {
280 // Use the same info block for both allocation sites.
281 Record
.AllocSites
.emplace_back(ACS
, Info
);
283 Record
.CallSites
.assign(CallSites
);
286 llvm::raw_string_ostream
OS(Buffer
);
287 Record
.serialize(Schema
, OS
);
290 const IndexedMemProfRecord GotRecord
= IndexedMemProfRecord::deserialize(
291 Schema
, reinterpret_cast<const unsigned char *>(Buffer
.data()));
293 EXPECT_EQ(Record
, GotRecord
);
296 TEST(MemProf
, SymbolizationFilter
) {
297 std::unique_ptr
<MockSymbolizer
> Symbolizer(new MockSymbolizer());
299 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x1000},
301 .Times(1) // once since we don't lookup invalid PCs repeatedly.
302 .WillRepeatedly(Return(makeInliningInfo({
303 {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
306 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x2000},
308 .Times(1) // once since we don't lookup invalid PCs repeatedly.
309 .WillRepeatedly(Return(makeInliningInfo({
310 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
313 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x3000},
315 .Times(1) // once since we don't lookup invalid PCs repeatedly.
316 .WillRepeatedly(Return(makeInliningInfo({
317 {DILineInfo::BadString
, 0, 0, 0},
320 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x4000},
323 .WillRepeatedly(Return(makeInliningInfo({
328 CSM
[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
329 // This entry should be dropped since all PCs are either not
330 // symbolizable or belong to the runtime.
331 CSM
[0x2] = {0x1000, 0x2000};
333 llvm::MapVector
<uint64_t, MemInfoBlock
> Prof
;
334 Prof
[0x1].AllocCount
= 1;
335 Prof
[0x2].AllocCount
= 1;
337 auto Seg
= makeSegments();
339 RawMemProfReader
Reader(std::move(Symbolizer
), Seg
, Prof
, CSM
);
341 llvm::SmallVector
<MemProfRecord
, 1> Records
;
342 for (const auto &KeyRecordPair
: Reader
) {
343 Records
.push_back(KeyRecordPair
.second
);
346 ASSERT_EQ(Records
.size(), 1U);
347 ASSERT_EQ(Records
[0].AllocSites
.size(), 1U);
348 ASSERT_EQ(Records
[0].AllocSites
[0].CallStack
.size(), 1U);
349 EXPECT_THAT(Records
[0].AllocSites
[0].CallStack
[0],
350 FrameContains("foo", 5U, 30U, false));