[docs] Add LICENSE.txt to the root of the mono-repo
[llvm-project.git] / llvm / unittests / ProfileData / MemProfTest.cpp
blob290d33154e6c022ee0c66ce6d52a9c2c3eb1358f
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>
20 namespace {
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 {
43 public:
44 MOCK_CONST_METHOD3(symbolizeInlinedCode,
45 DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
46 bool));
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,
50 bool) const {
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");
65 struct MockInfo {
66 std::string FunctionName;
67 uint32_t Line;
68 uint32_t StartLine;
69 uint32_t Column;
70 std::string FileName = "valid/path.cc";
72 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
73 DIInliningInfo Result;
74 for (const auto &Item : MockFrames) {
75 DILineInfo Frame;
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);
83 return Result;
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);
90 return Result;
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";
105 return false;
107 if (F.SymbolName && F.SymbolName.value() != FunctionName) {
108 *result_listener << "SymbolName mismatch\nWant: " << FunctionName
109 << "\nGot: " << F.SymbolName.value();
110 return false;
112 if (F.LineOffset == LineOffset && F.Column == Column &&
113 F.IsInlineFrame == Inline) {
114 return true;
116 *result_listener << "LineOffset, Column or Inline mismatch";
117 return false;
120 MemProfSchema getFullSchema() {
121 MemProfSchema Schema;
122 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name);
123 #include "llvm/ProfileData/MIBEntryDef.inc"
124 #undef MIBEntryDef
125 return Schema;
128 TEST(MemProf, FillsValue) {
129 std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
131 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
132 specifier(), false))
133 .Times(1) // Only once since we remember invalid PCs.
134 .WillRepeatedly(Return(makeInliningInfo({
135 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
136 })));
138 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
139 specifier(), false))
140 .Times(1) // Only once since we cache the result for future lookups.
141 .WillRepeatedly(Return(makeInliningInfo({
142 {"foo", 10, 5, 30},
143 {"bar", 201, 150, 20},
144 })));
146 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
147 specifier(), false))
148 .Times(1)
149 .WillRepeatedly(Return(makeInliningInfo({
150 {"xyz", 10, 5, 30},
151 {"abc", 10, 5, 30},
152 })));
154 CallStackMap CSM;
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,
163 /*KeepName=*/true);
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
226 // entry here.
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,
244 /*dealloc_cpu=*/4);
246 const auto Schema = getFullSchema();
247 PortableMemInfoBlock WriteBlock(Info);
249 std::string Buffer;
250 llvm::raw_string_ostream OS(Buffer);
251 WriteBlock.serialize(Schema, OS);
252 OS.flush();
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,
271 /*dealloc_cpu=*/4);
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);
285 std::string Buffer;
286 llvm::raw_string_ostream OS(Buffer);
287 Record.serialize(Schema, OS);
288 OS.flush();
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},
300 specifier(), false))
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"},
304 })));
306 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
307 specifier(), false))
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"},
311 })));
313 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
314 specifier(), false))
315 .Times(1) // once since we don't lookup invalid PCs repeatedly.
316 .WillRepeatedly(Return(makeInliningInfo({
317 {DILineInfo::BadString, 0, 0, 0},
318 })));
320 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
321 specifier(), false))
322 .Times(1)
323 .WillRepeatedly(Return(makeInliningInfo({
324 {"foo", 10, 5, 30},
325 })));
327 CallStackMap CSM;
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));
352 } // namespace