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/Value.h"
7 #include "llvm/Object/ObjectFile.h"
8 #include "llvm/ProfileData/MemProfData.inc"
9 #include "llvm/ProfileData/RawMemProfReader.h"
10 #include "llvm/Support/raw_ostream.h"
11 #include "gmock/gmock.h"
12 #include "gtest/gtest.h"
14 #include <initializer_list>
18 using ::llvm::DIGlobal
;
19 using ::llvm::DIInliningInfo
;
20 using ::llvm::DILineInfo
;
21 using ::llvm::DILineInfoSpecifier
;
22 using ::llvm::DILocal
;
23 using ::llvm::StringRef
;
24 using ::llvm::memprof::CallStackMap
;
25 using ::llvm::memprof::Frame
;
26 using ::llvm::memprof::FrameId
;
27 using ::llvm::memprof::IndexedMemProfRecord
;
28 using ::llvm::memprof::MemInfoBlock
;
29 using ::llvm::memprof::MemProfReader
;
30 using ::llvm::memprof::MemProfRecord
;
31 using ::llvm::memprof::MemProfSchema
;
32 using ::llvm::memprof::Meta
;
33 using ::llvm::memprof::PortableMemInfoBlock
;
34 using ::llvm::memprof::RawMemProfReader
;
35 using ::llvm::memprof::SegmentEntry
;
36 using ::llvm::object::SectionedAddress
;
37 using ::llvm::symbolize::SymbolizableModule
;
38 using ::testing::Return
;
40 class MockSymbolizer
: public SymbolizableModule
{
42 MOCK_CONST_METHOD3(symbolizeInlinedCode
,
43 DIInliningInfo(SectionedAddress
, DILineInfoSpecifier
,
45 // Most of the methods in the interface are unused. We only mock the
46 // method that we expect to be called from the memprof reader.
47 virtual DILineInfo
symbolizeCode(SectionedAddress
, DILineInfoSpecifier
,
49 llvm_unreachable("unused");
51 virtual DIGlobal
symbolizeData(SectionedAddress
) const {
52 llvm_unreachable("unused");
54 virtual std::vector
<DILocal
> symbolizeFrame(SectionedAddress
) const {
55 llvm_unreachable("unused");
57 virtual std::vector
<SectionedAddress
> findSymbol(StringRef Symbol
) const {
58 llvm_unreachable("unused");
60 virtual bool isWin32Module() const { llvm_unreachable("unused"); }
61 virtual uint64_t getModulePreferredBase() const {
62 llvm_unreachable("unused");
67 std::string FunctionName
;
71 std::string FileName
= "valid/path.cc";
73 DIInliningInfo
makeInliningInfo(std::initializer_list
<MockInfo
> MockFrames
) {
74 DIInliningInfo Result
;
75 for (const auto &Item
: MockFrames
) {
77 Frame
.FunctionName
= Item
.FunctionName
;
78 Frame
.Line
= Item
.Line
;
79 Frame
.StartLine
= Item
.StartLine
;
80 Frame
.Column
= Item
.Column
;
81 Frame
.FileName
= Item
.FileName
;
82 Result
.addFrame(Frame
);
87 llvm::SmallVector
<SegmentEntry
, 4> makeSegments() {
88 llvm::SmallVector
<SegmentEntry
, 4> Result
;
89 // Mimic an entry for a non position independent executable.
90 Result
.emplace_back(0x0, 0x40000, 0x0);
94 const DILineInfoSpecifier
specifier() {
95 return DILineInfoSpecifier(
96 DILineInfoSpecifier::FileLineInfoKind::RawValue
,
97 DILineInfoSpecifier::FunctionNameKind::LinkageName
);
100 MATCHER_P4(FrameContains
, FunctionName
, LineOffset
, Column
, Inline
, "") {
101 const Frame
&F
= arg
;
103 const uint64_t ExpectedHash
= IndexedMemProfRecord::getGUID(FunctionName
);
104 if (F
.Function
!= ExpectedHash
) {
105 *result_listener
<< "Hash mismatch";
108 if (F
.SymbolName
&& *F
.SymbolName
!= FunctionName
) {
109 *result_listener
<< "SymbolName mismatch\nWant: " << FunctionName
110 << "\nGot: " << *F
.SymbolName
;
113 if (F
.LineOffset
== LineOffset
&& F
.Column
== Column
&&
114 F
.IsInlineFrame
== Inline
) {
117 *result_listener
<< "LineOffset, Column or Inline mismatch";
121 MemProfSchema
getFullSchema() {
122 MemProfSchema Schema
;
123 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name);
124 #include "llvm/ProfileData/MIBEntryDef.inc"
129 TEST(MemProf
, FillsValue
) {
130 std::unique_ptr
<MockSymbolizer
> Symbolizer(new MockSymbolizer());
132 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x1000},
134 .Times(1) // Only once since we remember invalid PCs.
135 .WillRepeatedly(Return(makeInliningInfo({
136 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
139 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x2000},
141 .Times(1) // Only once since we cache the result for future lookups.
142 .WillRepeatedly(Return(makeInliningInfo({
144 {"bar", 201, 150, 20},
147 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x3000},
150 .WillRepeatedly(Return(makeInliningInfo({
151 {"xyz.llvm.123", 10, 5, 30},
156 CSM
[0x1] = {0x1000, 0x2000, 0x3000};
158 llvm::MapVector
<uint64_t, MemInfoBlock
> Prof
;
159 Prof
[0x1].AllocCount
= 1;
161 auto Seg
= makeSegments();
163 RawMemProfReader
Reader(std::move(Symbolizer
), Seg
, Prof
, CSM
,
166 llvm::DenseMap
<llvm::GlobalValue::GUID
, MemProfRecord
> Records
;
167 for (const auto &Pair
: Reader
) {
168 Records
.insert({Pair
.first
, Pair
.second
});
171 // Mock program pseudocode and expected memprof record contents.
173 // AllocSite CallSite
174 // inline foo() { new(); } Y N
175 // bar() { foo(); } Y Y
176 // inline xyz() { bar(); } N Y
177 // abc() { xyz(); } N Y
179 // We expect 4 records. We attach alloc site data to foo and bar, i.e.
180 // all frames bottom up until we find a non-inline frame. We attach call site
181 // data to bar, xyz and abc.
182 ASSERT_EQ(Records
.size(), 4U);
184 // Check the memprof record for foo.
185 const llvm::GlobalValue::GUID FooId
= IndexedMemProfRecord::getGUID("foo");
186 ASSERT_EQ(Records
.count(FooId
), 1U);
187 const MemProfRecord
&Foo
= Records
[FooId
];
188 ASSERT_EQ(Foo
.AllocSites
.size(), 1U);
189 EXPECT_EQ(Foo
.AllocSites
[0].Info
.getAllocCount(), 1U);
190 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[0],
191 FrameContains("foo", 5U, 30U, true));
192 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[1],
193 FrameContains("bar", 51U, 20U, false));
194 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[2],
195 FrameContains("xyz", 5U, 30U, true));
196 EXPECT_THAT(Foo
.AllocSites
[0].CallStack
[3],
197 FrameContains("abc", 5U, 30U, false));
198 EXPECT_TRUE(Foo
.CallSites
.empty());
200 // Check the memprof record for bar.
201 const llvm::GlobalValue::GUID BarId
= IndexedMemProfRecord::getGUID("bar");
202 ASSERT_EQ(Records
.count(BarId
), 1U);
203 const MemProfRecord
&Bar
= Records
[BarId
];
204 ASSERT_EQ(Bar
.AllocSites
.size(), 1U);
205 EXPECT_EQ(Bar
.AllocSites
[0].Info
.getAllocCount(), 1U);
206 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[0],
207 FrameContains("foo", 5U, 30U, true));
208 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[1],
209 FrameContains("bar", 51U, 20U, false));
210 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[2],
211 FrameContains("xyz", 5U, 30U, true));
212 EXPECT_THAT(Bar
.AllocSites
[0].CallStack
[3],
213 FrameContains("abc", 5U, 30U, false));
215 ASSERT_EQ(Bar
.CallSites
.size(), 1U);
216 ASSERT_EQ(Bar
.CallSites
[0].size(), 2U);
217 EXPECT_THAT(Bar
.CallSites
[0][0], FrameContains("foo", 5U, 30U, true));
218 EXPECT_THAT(Bar
.CallSites
[0][1], FrameContains("bar", 51U, 20U, false));
220 // Check the memprof record for xyz.
221 const llvm::GlobalValue::GUID XyzId
= IndexedMemProfRecord::getGUID("xyz");
222 ASSERT_EQ(Records
.count(XyzId
), 1U);
223 const MemProfRecord
&Xyz
= Records
[XyzId
];
224 ASSERT_EQ(Xyz
.CallSites
.size(), 1U);
225 ASSERT_EQ(Xyz
.CallSites
[0].size(), 2U);
226 // Expect the entire frame even though in practice we only need the first
228 EXPECT_THAT(Xyz
.CallSites
[0][0], FrameContains("xyz", 5U, 30U, true));
229 EXPECT_THAT(Xyz
.CallSites
[0][1], FrameContains("abc", 5U, 30U, false));
231 // Check the memprof record for abc.
232 const llvm::GlobalValue::GUID AbcId
= IndexedMemProfRecord::getGUID("abc");
233 ASSERT_EQ(Records
.count(AbcId
), 1U);
234 const MemProfRecord
&Abc
= Records
[AbcId
];
235 EXPECT_TRUE(Abc
.AllocSites
.empty());
236 ASSERT_EQ(Abc
.CallSites
.size(), 1U);
237 ASSERT_EQ(Abc
.CallSites
[0].size(), 2U);
238 EXPECT_THAT(Abc
.CallSites
[0][0], FrameContains("xyz", 5U, 30U, true));
239 EXPECT_THAT(Abc
.CallSites
[0][1], FrameContains("abc", 5U, 30U, false));
242 TEST(MemProf
, PortableWrapper
) {
243 MemInfoBlock
Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
244 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
247 const auto Schema
= getFullSchema();
248 PortableMemInfoBlock
WriteBlock(Info
);
251 llvm::raw_string_ostream
OS(Buffer
);
252 WriteBlock
.serialize(Schema
, OS
);
255 PortableMemInfoBlock
ReadBlock(
256 Schema
, reinterpret_cast<const unsigned char *>(Buffer
.data()));
258 EXPECT_EQ(ReadBlock
, WriteBlock
);
259 // Here we compare directly with the actual counts instead of MemInfoBlock
260 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros
261 // take a reference to the params, this results in unaligned accesses.
262 EXPECT_EQ(1UL, ReadBlock
.getAllocCount());
263 EXPECT_EQ(7ULL, ReadBlock
.getTotalAccessCount());
264 EXPECT_EQ(3UL, ReadBlock
.getAllocCpuId());
267 TEST(MemProf
, RecordSerializationRoundTrip
) {
268 const MemProfSchema Schema
= getFullSchema();
270 MemInfoBlock
Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
271 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
274 llvm::SmallVector
<llvm::SmallVector
<FrameId
>> AllocCallStacks
= {
275 {0x123, 0x345}, {0x123, 0x567}};
277 llvm::SmallVector
<llvm::SmallVector
<FrameId
>> CallSites
= {{0x333, 0x777}};
279 IndexedMemProfRecord Record
;
280 for (const auto &ACS
: AllocCallStacks
) {
281 // Use the same info block for both allocation sites.
282 Record
.AllocSites
.emplace_back(ACS
, Info
);
284 Record
.CallSites
.assign(CallSites
);
287 llvm::raw_string_ostream
OS(Buffer
);
288 Record
.serialize(Schema
, OS
);
291 const IndexedMemProfRecord GotRecord
= IndexedMemProfRecord::deserialize(
292 Schema
, reinterpret_cast<const unsigned char *>(Buffer
.data()));
294 EXPECT_EQ(Record
, GotRecord
);
297 TEST(MemProf
, SymbolizationFilter
) {
298 std::unique_ptr
<MockSymbolizer
> Symbolizer(new MockSymbolizer());
300 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x1000},
302 .Times(1) // once since we don't lookup invalid PCs repeatedly.
303 .WillRepeatedly(Return(makeInliningInfo({
304 {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
307 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x2000},
309 .Times(1) // once since we don't lookup invalid PCs repeatedly.
310 .WillRepeatedly(Return(makeInliningInfo({
311 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
314 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x3000},
316 .Times(1) // once since we don't lookup invalid PCs repeatedly.
317 .WillRepeatedly(Return(makeInliningInfo({
318 {DILineInfo::BadString
, 0, 0, 0},
321 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x4000},
324 .WillRepeatedly(Return(makeInliningInfo({
325 {"foo", 10, 5, 30, "memprof/memprof_test_file.cpp"},
328 EXPECT_CALL(*Symbolizer
, symbolizeInlinedCode(SectionedAddress
{0x5000},
331 .WillRepeatedly(Return(makeInliningInfo({
332 // Depending on how the runtime was compiled, only the filename
333 // may be present in the debug information.
334 {"malloc", 70, 57, 3, "memprof_malloc_linux.cpp"},
338 CSM
[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
339 // This entry should be dropped since all PCs are either not
340 // symbolizable or belong to the runtime.
341 CSM
[0x2] = {0x1000, 0x2000, 0x5000};
343 llvm::MapVector
<uint64_t, MemInfoBlock
> Prof
;
344 Prof
[0x1].AllocCount
= 1;
345 Prof
[0x2].AllocCount
= 1;
347 auto Seg
= makeSegments();
349 RawMemProfReader
Reader(std::move(Symbolizer
), Seg
, Prof
, CSM
);
351 llvm::SmallVector
<MemProfRecord
, 1> Records
;
352 for (const auto &KeyRecordPair
: Reader
) {
353 Records
.push_back(KeyRecordPair
.second
);
356 ASSERT_EQ(Records
.size(), 1U);
357 ASSERT_EQ(Records
[0].AllocSites
.size(), 1U);
358 ASSERT_EQ(Records
[0].AllocSites
[0].CallStack
.size(), 1U);
359 EXPECT_THAT(Records
[0].AllocSites
[0].CallStack
[0],
360 FrameContains("foo", 5U, 30U, false));
363 TEST(MemProf
, BaseMemProfReader
) {
364 llvm::DenseMap
<FrameId
, Frame
> FrameIdMap
;
365 Frame
F1(/*Hash=*/IndexedMemProfRecord::getGUID("foo"), /*LineOffset=*/20,
366 /*Column=*/5, /*IsInlineFrame=*/true);
367 Frame
F2(/*Hash=*/IndexedMemProfRecord::getGUID("bar"), /*LineOffset=*/10,
368 /*Column=*/2, /*IsInlineFrame=*/false);
369 FrameIdMap
.insert({F1
.hash(), F1
});
370 FrameIdMap
.insert({F2
.hash(), F2
});
372 llvm::MapVector
<llvm::GlobalValue::GUID
, IndexedMemProfRecord
> ProfData
;
373 IndexedMemProfRecord FakeRecord
;
375 Block
.AllocCount
= 1U, Block
.TotalAccessDensity
= 4,
376 Block
.TotalLifetime
= 200001;
377 std::array
<FrameId
, 2> CallStack
{F1
.hash(), F2
.hash()};
378 FakeRecord
.AllocSites
.emplace_back(/*CS=*/CallStack
, /*MB=*/Block
);
379 ProfData
.insert({F1
.hash(), FakeRecord
});
381 MemProfReader
Reader(FrameIdMap
, ProfData
);
383 llvm::SmallVector
<MemProfRecord
, 1> Records
;
384 for (const auto &KeyRecordPair
: Reader
) {
385 Records
.push_back(KeyRecordPair
.second
);
388 ASSERT_EQ(Records
.size(), 1U);
389 ASSERT_EQ(Records
[0].AllocSites
.size(), 1U);
390 ASSERT_EQ(Records
[0].AllocSites
[0].CallStack
.size(), 2U);
391 EXPECT_THAT(Records
[0].AllocSites
[0].CallStack
[0],
392 FrameContains("foo", 20U, 5U, true));
393 EXPECT_THAT(Records
[0].AllocSites
[0].CallStack
[1],
394 FrameContains("bar", 10U, 2U, false));