1//===- unittests/Support/MemProfTest.cpp ----------------------------------===//
2//
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
6//
7//===----------------------------------------------------------------------===//
8
9#include "llvm/ProfileData/MemProf.h"
10#include "llvm/ADT/DenseMap.h"
11#include "llvm/ADT/MapVector.h"
12#include "llvm/ADT/STLForwardCompat.h"
13#include "llvm/DebugInfo/DIContext.h"
14#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"
15#include "llvm/IR/Value.h"
16#include "llvm/Object/ObjectFile.h"
17#include "llvm/ProfileData/IndexedMemProfData.h"
18#include "llvm/ProfileData/MemProfCommon.h"
19#include "llvm/ProfileData/MemProfData.inc"
20#include "llvm/ProfileData/MemProfRadixTree.h"
21#include "llvm/ProfileData/MemProfReader.h"
22#include "llvm/Support/Compiler.h"
23#include "llvm/Support/raw_ostream.h"
24#include "gmock/gmock.h"
25#include "gtest/gtest.h"
26
27#include <initializer_list>
28
29LLVM_ABI extern llvm::cl::opt<float> MemProfLifetimeAccessDensityColdThreshold;
30LLVM_ABI extern llvm::cl::opt<unsigned> MemProfAveLifetimeColdThreshold;
31LLVM_ABI extern llvm::cl::opt<unsigned>
32 MemProfMinAveLifetimeAccessDensityHotThreshold;
33LLVM_ABI extern llvm::cl::opt<bool> MemProfUseHotHints;
34
35namespace llvm {
36namespace memprof {
37
38namespace {
39
40using ::llvm::DIGlobal;
41using ::llvm::DIInliningInfo;
42using ::llvm::DILineInfo;
43using ::llvm::DILineInfoSpecifier;
44using ::llvm::DILocal;
45using ::llvm::StringRef;
46using ::llvm::object::SectionedAddress;
47using ::llvm::symbolize::SymbolizableModule;
48using ::testing::ElementsAre;
49using ::testing::IsEmpty;
50using ::testing::Pair;
51using ::testing::Return;
52using ::testing::SizeIs;
53using ::testing::UnorderedElementsAre;
54
55class MockSymbolizer : public SymbolizableModule {
56public:
57 MOCK_CONST_METHOD3(symbolizeInlinedCode,
58 DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
59 bool));
60 // Most of the methods in the interface are unused. We only mock the
61 // method that we expect to be called from the memprof reader.
62 virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier,
63 bool) const {
64 llvm_unreachable("unused");
65 }
66 virtual DIGlobal symbolizeData(SectionedAddress) const {
67 llvm_unreachable("unused");
68 }
69 virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const {
70 llvm_unreachable("unused");
71 }
72 virtual std::vector<SectionedAddress> findSymbol(StringRef Symbol,
73 uint64_t Offset) const {
74 llvm_unreachable("unused");
75 }
76 virtual bool isWin32Module() const { llvm_unreachable("unused"); }
77 virtual uint64_t getModulePreferredBase() const {
78 llvm_unreachable("unused");
79 }
80};
81
82struct MockInfo {
83 std::string FunctionName;
84 uint32_t Line;
85 uint32_t StartLine;
86 uint32_t Column;
87 std::string FileName = "valid/path.cc";
88};
89DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
90 DIInliningInfo Result;
91 for (const auto &Item : MockFrames) {
92 DILineInfo Frame;
93 Frame.FunctionName = Item.FunctionName;
94 Frame.Line = Item.Line;
95 Frame.StartLine = Item.StartLine;
96 Frame.Column = Item.Column;
97 Frame.FileName = Item.FileName;
98 Result.addFrame(Frame);
99 }
100 return Result;
101}
102
103llvm::SmallVector<SegmentEntry, 4> makeSegments() {
104 llvm::SmallVector<SegmentEntry, 4> Result;
105 // Mimic an entry for a non position independent executable.
106 Result.emplace_back(Args: 0x0, Args: 0x40000, Args: 0x0);
107 return Result;
108}
109
110const DILineInfoSpecifier specifier() {
111 return DILineInfoSpecifier(
112 DILineInfoSpecifier::FileLineInfoKind::RawValue,
113 DILineInfoSpecifier::FunctionNameKind::LinkageName);
114}
115
116MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {
117 const Frame &F = arg;
118
119 const uint64_t ExpectedHash = memprof::getGUID(FunctionName);
120 if (F.Function != ExpectedHash) {
121 *result_listener << "Hash mismatch";
122 return false;
123 }
124 if (F.SymbolName && *F.SymbolName != FunctionName) {
125 *result_listener << "SymbolName mismatch\nWant: " << FunctionName
126 << "\nGot: " << *F.SymbolName;
127 return false;
128 }
129 if (F.LineOffset == LineOffset && F.Column == Column &&
130 F.IsInlineFrame == Inline) {
131 return true;
132 }
133 *result_listener << "LineOffset, Column or Inline mismatch";
134 return false;
135}
136
137TEST(MemProf, FillsValue) {
138 auto Symbolizer = std::make_unique<MockSymbolizer>();
139
140 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
141 specifier(), false))
142 .Times(n: 1) // Only once since we remember invalid PCs.
143 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
144 {.FunctionName: "new", .Line: 70, .StartLine: 57, .Column: 3, .FileName: "memprof/memprof_new_delete.cpp"},
145 })));
146
147 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
148 specifier(), false))
149 .Times(n: 1) // Only once since we cache the result for future lookups.
150 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
151 {.FunctionName: "foo", .Line: 10, .StartLine: 5, .Column: 30},
152 {.FunctionName: "bar", .Line: 201, .StartLine: 150, .Column: 20},
153 })));
154
155 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
156 specifier(), false))
157 .Times(n: 1)
158 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
159 {.FunctionName: "xyz.llvm.123", .Line: 10, .StartLine: 5, .Column: 30},
160 {.FunctionName: "abc", .Line: 10, .StartLine: 5, .Column: 30},
161 })));
162
163 CallStackMap CSM;
164 CSM[0x1] = {0x1000, 0x2000, 0x3000};
165
166 llvm::MapVector<uint64_t, MemInfoBlock> Prof;
167 Prof[0x1].AllocCount = 1;
168
169 auto Seg = makeSegments();
170
171 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM,
172 /*KeepName=*/true);
173
174 llvm::DenseMap<llvm::GlobalValue::GUID, MemProfRecord> Records;
175 for (const auto &Pair : Reader)
176 Records.insert(KV: {Pair.first, Pair.second});
177
178 // Mock program pseudocode and expected memprof record contents.
179 //
180 // AllocSite CallSite
181 // inline foo() { new(); } Y N
182 // bar() { foo(); } Y Y
183 // inline xyz() { bar(); } N Y
184 // abc() { xyz(); } N Y
185
186 // We expect 4 records. We attach alloc site data to foo and bar, i.e.
187 // all frames bottom up until we find a non-inline frame. We attach call site
188 // data to bar, xyz and abc.
189 ASSERT_THAT(Records, SizeIs(4));
190
191 // Check the memprof record for foo.
192 const llvm::GlobalValue::GUID FooId = memprof::getGUID(FunctionName: "foo");
193 ASSERT_TRUE(Records.contains(FooId));
194 const MemProfRecord &Foo = Records[FooId];
195 ASSERT_THAT(Foo.AllocSites, SizeIs(1));
196 EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U);
197 EXPECT_THAT(Foo.AllocSites[0].CallStack[0],
198 FrameContains("foo", 5U, 30U, true));
199 EXPECT_THAT(Foo.AllocSites[0].CallStack[1],
200 FrameContains("bar", 51U, 20U, false));
201 EXPECT_THAT(Foo.AllocSites[0].CallStack[2],
202 FrameContains("xyz", 5U, 30U, true));
203 EXPECT_THAT(Foo.AllocSites[0].CallStack[3],
204 FrameContains("abc", 5U, 30U, false));
205 EXPECT_TRUE(Foo.CallSites.empty());
206
207 // Check the memprof record for bar.
208 const llvm::GlobalValue::GUID BarId = memprof::getGUID(FunctionName: "bar");
209 ASSERT_TRUE(Records.contains(BarId));
210 const MemProfRecord &Bar = Records[BarId];
211 ASSERT_THAT(Bar.AllocSites, SizeIs(1));
212 EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U);
213 EXPECT_THAT(Bar.AllocSites[0].CallStack[0],
214 FrameContains("foo", 5U, 30U, true));
215 EXPECT_THAT(Bar.AllocSites[0].CallStack[1],
216 FrameContains("bar", 51U, 20U, false));
217 EXPECT_THAT(Bar.AllocSites[0].CallStack[2],
218 FrameContains("xyz", 5U, 30U, true));
219 EXPECT_THAT(Bar.AllocSites[0].CallStack[3],
220 FrameContains("abc", 5U, 30U, false));
221
222 EXPECT_THAT(Bar.CallSites,
223 ElementsAre(testing::Field(
224 &CallSiteInfo::Frames,
225 ElementsAre(FrameContains("foo", 5U, 30U, true),
226 FrameContains("bar", 51U, 20U, false)))));
227
228 // Check the memprof record for xyz.
229 const llvm::GlobalValue::GUID XyzId = memprof::getGUID(FunctionName: "xyz");
230 ASSERT_TRUE(Records.contains(XyzId));
231 const MemProfRecord &Xyz = Records[XyzId];
232 // Expect the entire frame even though in practice we only need the first
233 // entry here.
234 EXPECT_THAT(Xyz.CallSites,
235 ElementsAre(testing::Field(
236 &CallSiteInfo::Frames,
237 ElementsAre(FrameContains("xyz", 5U, 30U, true),
238 FrameContains("abc", 5U, 30U, false)))));
239
240 // Check the memprof record for abc.
241 const llvm::GlobalValue::GUID AbcId = memprof::getGUID(FunctionName: "abc");
242 ASSERT_TRUE(Records.contains(AbcId));
243 const MemProfRecord &Abc = Records[AbcId];
244 EXPECT_TRUE(Abc.AllocSites.empty());
245 EXPECT_THAT(Abc.CallSites,
246 ElementsAre(testing::Field(
247 &CallSiteInfo::Frames,
248 ElementsAre(FrameContains("xyz", 5U, 30U, true),
249 FrameContains("abc", 5U, 30U, false)))));
250}
251
252TEST(MemProf, PortableWrapper) {
253 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
254 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
255 /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);
256
257 const auto Schema = getFullSchema();
258 PortableMemInfoBlock WriteBlock(Info, Schema);
259
260 std::string Buffer;
261 llvm::raw_string_ostream OS(Buffer);
262 WriteBlock.serialize(Schema, OS);
263
264 PortableMemInfoBlock ReadBlock(
265 Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));
266
267 EXPECT_EQ(ReadBlock, WriteBlock);
268 // Here we compare directly with the actual counts instead of MemInfoBlock
269 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros
270 // take a reference to the params, this results in unaligned accesses.
271 EXPECT_EQ(1UL, ReadBlock.getAllocCount());
272 EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount());
273 EXPECT_EQ(3UL, ReadBlock.getAllocCpuId());
274}
275
276TEST(MemProf, RecordSerializationRoundTripVerion2) {
277 const auto Schema = getFullSchema();
278
279 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
280 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
281 /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);
282
283 llvm::SmallVector<CallStackId> CallStackIds = {0x123, 0x456};
284
285 llvm::SmallVector<CallStackId> CallSiteIds = {0x333, 0x444};
286
287 IndexedMemProfRecord Record;
288 for (const auto &CSId : CallStackIds) {
289 // Use the same info block for both allocation sites.
290 Record.AllocSites.emplace_back(Args: CSId, Args&: Info);
291 }
292 for (auto CSId : CallSiteIds)
293 Record.CallSites.push_back(Elt: IndexedCallSiteInfo(CSId));
294
295 std::string Buffer;
296 llvm::raw_string_ostream OS(Buffer);
297 Record.serialize(Schema, OS, Version: Version2);
298
299 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
300 Schema, Buffer: reinterpret_cast<const unsigned char *>(Buffer.data()), Version: Version2);
301
302 EXPECT_EQ(Record, GotRecord);
303}
304
305TEST(MemProf, RecordSerializationRoundTripVersion4) {
306 const auto Schema = getFullSchema();
307
308 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,
309 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,
310 /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);
311
312 llvm::SmallVector<CallStackId> CallStackIds = {0x123, 0x456};
313
314 llvm::SmallVector<IndexedCallSiteInfo> CallSites;
315 CallSites.push_back(
316 Elt: IndexedCallSiteInfo(0x333, {0xaaa, 0xbbb})); // CSId with GUIDs
317 CallSites.push_back(Elt: IndexedCallSiteInfo(0x444)); // CSId without GUIDs
318
319 IndexedMemProfRecord Record;
320 for (const auto &CSId : CallStackIds) {
321 // Use the same info block for both allocation sites.
322 Record.AllocSites.emplace_back(Args: CSId, Args&: Info);
323 }
324 Record.CallSites = std::move(CallSites);
325
326 std::string Buffer;
327 llvm::raw_string_ostream OS(Buffer);
328 // Need a dummy map for V4 serialization
329 llvm::DenseMap<CallStackId, LinearCallStackId> DummyMap = {
330 {0x123, 1}, {0x456, 2}, {0x333, 3}, {0x444, 4}};
331 Record.serialize(Schema, OS, Version: Version4, MemProfCallStackIndexes: &DummyMap);
332
333 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
334 Schema, Buffer: reinterpret_cast<const unsigned char *>(Buffer.data()), Version: Version4);
335
336 // Create the expected record using the linear IDs from the dummy map.
337 IndexedMemProfRecord ExpectedRecord;
338 for (const auto &CSId : CallStackIds) {
339 ExpectedRecord.AllocSites.emplace_back(Args&: DummyMap[CSId], Args&: Info);
340 }
341 for (const auto &CSInfo :
342 Record.CallSites) { // Use original Record's CallSites to get GUIDs
343 ExpectedRecord.CallSites.emplace_back(Args&: DummyMap[CSInfo.CSId],
344 Args: CSInfo.CalleeGuids);
345 }
346
347 EXPECT_EQ(ExpectedRecord, GotRecord);
348}
349
350TEST(MemProf, RecordSerializationRoundTripVersion2HotColdSchema) {
351 const auto Schema = getHotColdSchema();
352
353 MemInfoBlock Info;
354 Info.AllocCount = 11;
355 Info.TotalSize = 22;
356 Info.TotalLifetime = 33;
357 Info.TotalLifetimeAccessDensity = 44;
358
359 llvm::SmallVector<CallStackId> CallStackIds = {0x123, 0x456};
360
361 llvm::SmallVector<CallStackId> CallSiteIds = {0x333, 0x444};
362
363 IndexedMemProfRecord Record;
364 for (const auto &CSId : CallStackIds) {
365 // Use the same info block for both allocation sites.
366 Record.AllocSites.emplace_back(Args: CSId, Args&: Info, Args: Schema);
367 }
368 for (auto CSId : CallSiteIds)
369 Record.CallSites.push_back(Elt: IndexedCallSiteInfo(CSId));
370
371 std::bitset<llvm::to_underlying(E: Meta::Size)> SchemaBitSet;
372 for (auto Id : Schema)
373 SchemaBitSet.set(position: llvm::to_underlying(E: Id));
374
375 // Verify that SchemaBitSet has the fields we expect and nothing else, which
376 // we check with count().
377 EXPECT_EQ(SchemaBitSet.count(), 4U);
378 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::AllocCount)]);
379 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalSize)]);
380 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalLifetime)]);
381 EXPECT_TRUE(
382 SchemaBitSet[llvm::to_underlying(Meta::TotalLifetimeAccessDensity)]);
383
384 // Verify that Schema has propagated all the way to the Info field in each
385 // IndexedAllocationInfo.
386 ASSERT_THAT(Record.AllocSites, SizeIs(2));
387 EXPECT_EQ(Record.AllocSites[0].Info.getSchema(), SchemaBitSet);
388 EXPECT_EQ(Record.AllocSites[1].Info.getSchema(), SchemaBitSet);
389
390 std::string Buffer;
391 llvm::raw_string_ostream OS(Buffer);
392 Record.serialize(Schema, OS, Version: Version2);
393
394 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
395 Schema, Buffer: reinterpret_cast<const unsigned char *>(Buffer.data()), Version: Version2);
396
397 // Verify that Schema comes back correctly after deserialization. Technically,
398 // the comparison between Record and GotRecord below includes the comparison
399 // of their Schemas, but we'll verify the Schemas on our own.
400 ASSERT_THAT(GotRecord.AllocSites, SizeIs(2));
401 EXPECT_EQ(GotRecord.AllocSites[0].Info.getSchema(), SchemaBitSet);
402 EXPECT_EQ(GotRecord.AllocSites[1].Info.getSchema(), SchemaBitSet);
403
404 EXPECT_EQ(Record, GotRecord);
405}
406
407TEST(MemProf, SymbolizationFilter) {
408 auto Symbolizer = std::make_unique<MockSymbolizer>();
409
410 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
411 specifier(), false))
412 .Times(n: 1) // once since we don't lookup invalid PCs repeatedly.
413 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
414 {.FunctionName: "malloc", .Line: 70, .StartLine: 57, .Column: 3, .FileName: "memprof/memprof_malloc_linux.cpp"},
415 })));
416
417 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
418 specifier(), false))
419 .Times(n: 1) // once since we don't lookup invalid PCs repeatedly.
420 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
421 {.FunctionName: "new", .Line: 70, .StartLine: 57, .Column: 3, .FileName: "memprof/memprof_new_delete.cpp"},
422 })));
423
424 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
425 specifier(), false))
426 .Times(n: 1) // once since we don't lookup invalid PCs repeatedly.
427 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
428 {.FunctionName: DILineInfo::BadString, .Line: 0, .StartLine: 0, .Column: 0},
429 })));
430
431 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
432 specifier(), false))
433 .Times(n: 1)
434 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
435 {.FunctionName: "foo", .Line: 10, .StartLine: 5, .Column: 30, .FileName: "memprof/memprof_test_file.cpp"},
436 })));
437
438 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x5000},
439 specifier(), false))
440 .Times(n: 1)
441 .WillRepeatedly(action: Return(value: makeInliningInfo(MockFrames: {
442 // Depending on how the runtime was compiled, only the filename
443 // may be present in the debug information.
444 {.FunctionName: "malloc", .Line: 70, .StartLine: 57, .Column: 3, .FileName: "memprof_malloc_linux.cpp"},
445 })));
446
447 CallStackMap CSM;
448 CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
449 // This entry should be dropped since all PCs are either not
450 // symbolizable or belong to the runtime.
451 CSM[0x2] = {0x1000, 0x2000, 0x5000};
452
453 llvm::MapVector<uint64_t, MemInfoBlock> Prof;
454 Prof[0x1].AllocCount = 1;
455 Prof[0x2].AllocCount = 1;
456
457 auto Seg = makeSegments();
458
459 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
460
461 llvm::SmallVector<MemProfRecord, 1> Records;
462 for (const auto &KeyRecordPair : Reader)
463 Records.push_back(Elt: KeyRecordPair.second);
464
465 ASSERT_THAT(Records, SizeIs(1));
466 ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
467 EXPECT_THAT(Records[0].AllocSites[0].CallStack,
468 ElementsAre(FrameContains("foo", 5U, 30U, false)));
469}
470
471TEST(MemProf, BaseMemProfReader) {
472 IndexedMemProfData MemProfData;
473 Frame F1(/*Hash=*/memprof::getGUID(FunctionName: "foo"), /*LineOffset=*/20,
474 /*Column=*/5, /*IsInlineFrame=*/true);
475 Frame F2(/*Hash=*/memprof::getGUID(FunctionName: "bar"), /*LineOffset=*/10,
476 /*Column=*/2, /*IsInlineFrame=*/false);
477 auto F1Id = MemProfData.addFrame(F: F1);
478 auto F2Id = MemProfData.addFrame(F: F2);
479
480 llvm::SmallVector<FrameId> CallStack{F1Id, F2Id};
481 CallStackId CSId = MemProfData.addCallStack(CS: std::move(CallStack));
482
483 IndexedMemProfRecord FakeRecord;
484 MemInfoBlock Block;
485 Block.AllocCount = 1U, Block.TotalAccessDensity = 4,
486 Block.TotalLifetime = 200001;
487 FakeRecord.AllocSites.emplace_back(/*CSId=*/Args&: CSId, /*MB=*/Args&: Block);
488 MemProfData.Records.try_emplace(Key: 0x1234, Args: std::move(FakeRecord));
489
490 MemProfReader Reader(std::move(MemProfData));
491
492 llvm::SmallVector<MemProfRecord, 1> Records;
493 for (const auto &KeyRecordPair : Reader)
494 Records.push_back(Elt: KeyRecordPair.second);
495
496 ASSERT_THAT(Records, SizeIs(1));
497 ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
498 EXPECT_THAT(Records[0].AllocSites[0].CallStack,
499 ElementsAre(FrameContains("foo", 20U, 5U, true),
500 FrameContains("bar", 10U, 2U, false)));
501}
502
503TEST(MemProf, BaseMemProfReaderWithCSIdMap) {
504 IndexedMemProfData MemProfData;
505 Frame F1(/*Hash=*/memprof::getGUID(FunctionName: "foo"), /*LineOffset=*/20,
506 /*Column=*/5, /*IsInlineFrame=*/true);
507 Frame F2(/*Hash=*/memprof::getGUID(FunctionName: "bar"), /*LineOffset=*/10,
508 /*Column=*/2, /*IsInlineFrame=*/false);
509 auto F1Id = MemProfData.addFrame(F: F1);
510 auto F2Id = MemProfData.addFrame(F: F2);
511
512 llvm::SmallVector<FrameId> CallStack = {F1Id, F2Id};
513 auto CSId = MemProfData.addCallStack(CS: std::move(CallStack));
514
515 IndexedMemProfRecord FakeRecord;
516 MemInfoBlock Block;
517 Block.AllocCount = 1U, Block.TotalAccessDensity = 4,
518 Block.TotalLifetime = 200001;
519 FakeRecord.AllocSites.emplace_back(/*CSId=*/Args&: CSId, /*MB=*/Args&: Block);
520 MemProfData.Records.try_emplace(Key: 0x1234, Args: std::move(FakeRecord));
521
522 MemProfReader Reader(std::move(MemProfData));
523
524 llvm::SmallVector<MemProfRecord, 1> Records;
525 for (const auto &KeyRecordPair : Reader)
526 Records.push_back(Elt: KeyRecordPair.second);
527
528 ASSERT_THAT(Records, SizeIs(1));
529 ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
530 EXPECT_THAT(Records[0].AllocSites[0].CallStack,
531 ElementsAre(FrameContains("foo", 20U, 5U, true),
532 FrameContains("bar", 10U, 2U, false)));
533}
534
535TEST(MemProf, IndexedMemProfRecordToMemProfRecord) {
536 // Verify that MemProfRecord can be constructed from IndexedMemProfRecord with
537 // CallStackIds only.
538
539 IndexedMemProfData MemProfData;
540 Frame F1(1, 0, 0, false);
541 Frame F2(2, 0, 0, false);
542 Frame F3(3, 0, 0, false);
543 Frame F4(4, 0, 0, false);
544 auto F1Id = MemProfData.addFrame(F: F1);
545 auto F2Id = MemProfData.addFrame(F: F2);
546 auto F3Id = MemProfData.addFrame(F: F3);
547 auto F4Id = MemProfData.addFrame(F: F4);
548
549 llvm::SmallVector<FrameId> CS1 = {F1Id, F2Id};
550 llvm::SmallVector<FrameId> CS2 = {F1Id, F3Id};
551 llvm::SmallVector<FrameId> CS3 = {F2Id, F3Id};
552 llvm::SmallVector<FrameId> CS4 = {F2Id, F4Id};
553 auto CS1Id = MemProfData.addCallStack(CS: std::move(CS1));
554 auto CS2Id = MemProfData.addCallStack(CS: std::move(CS2));
555 auto CS3Id = MemProfData.addCallStack(CS: std::move(CS3));
556 auto CS4Id = MemProfData.addCallStack(CS: std::move(CS4));
557
558 IndexedMemProfRecord IndexedRecord;
559 IndexedAllocationInfo AI;
560 AI.CSId = CS1Id;
561 IndexedRecord.AllocSites.push_back(Elt: AI);
562 AI.CSId = CS2Id;
563 IndexedRecord.AllocSites.push_back(Elt: AI);
564 IndexedRecord.CallSites.push_back(Elt: IndexedCallSiteInfo(CS3Id));
565 IndexedRecord.CallSites.push_back(Elt: IndexedCallSiteInfo(CS4Id));
566
567 IndexedCallstackIdConverter CSIdConv(MemProfData);
568
569 MemProfRecord Record = IndexedRecord.toMemProfRecord(Callback: CSIdConv);
570
571 // Make sure that all lookups are successful.
572 ASSERT_EQ(CSIdConv.FrameIdConv.LastUnmappedId, std::nullopt);
573 ASSERT_EQ(CSIdConv.CSIdConv.LastUnmappedId, std::nullopt);
574
575 // Verify the contents of Record.
576 ASSERT_THAT(Record.AllocSites, SizeIs(2));
577 EXPECT_THAT(Record.AllocSites[0].CallStack, ElementsAre(F1, F2));
578 EXPECT_THAT(Record.AllocSites[1].CallStack, ElementsAre(F1, F3));
579 ASSERT_THAT(Record.CallSites, SizeIs(2));
580 EXPECT_THAT(Record.CallSites[0].Frames, ElementsAre(F2, F3));
581 EXPECT_THAT(Record.CallSites[1].Frames, ElementsAre(F2, F4));
582}
583
584// Populate those fields returned by getHotColdSchema.
585MemInfoBlock makePartialMIB() {
586 MemInfoBlock MIB;
587 MIB.AllocCount = 1;
588 MIB.TotalSize = 5;
589 MIB.TotalLifetime = 10;
590 MIB.TotalLifetimeAccessDensity = 23;
591 return MIB;
592}
593
594TEST(MemProf, MissingCallStackId) {
595 // Use a non-existent CallStackId to trigger a mapping error in
596 // toMemProfRecord.
597 IndexedAllocationInfo AI(0xdeadbeefU, makePartialMIB(), getHotColdSchema());
598
599 IndexedMemProfRecord IndexedMR;
600 IndexedMR.AllocSites.push_back(Elt: AI);
601
602 // Create empty maps.
603 IndexedMemProfData MemProfData;
604 IndexedCallstackIdConverter CSIdConv(MemProfData);
605
606 // We are only interested in errors, not the return value.
607 (void)IndexedMR.toMemProfRecord(Callback: CSIdConv);
608
609 ASSERT_TRUE(CSIdConv.CSIdConv.LastUnmappedId.has_value());
610 EXPECT_EQ(*CSIdConv.CSIdConv.LastUnmappedId, 0xdeadbeefU);
611 EXPECT_EQ(CSIdConv.FrameIdConv.LastUnmappedId, std::nullopt);
612}
613
614TEST(MemProf, MissingFrameId) {
615 // An empty Frame map to trigger a mapping error.
616 IndexedMemProfData MemProfData;
617 auto CSId = MemProfData.addCallStack(CS: SmallVector<FrameId>{2, 3});
618
619 IndexedMemProfRecord IndexedMR;
620 IndexedMR.AllocSites.emplace_back(Args&: CSId, Args: makePartialMIB(), Args: getHotColdSchema());
621
622 IndexedCallstackIdConverter CSIdConv(MemProfData);
623
624 // We are only interested in errors, not the return value.
625 (void)IndexedMR.toMemProfRecord(Callback: CSIdConv);
626
627 EXPECT_EQ(CSIdConv.CSIdConv.LastUnmappedId, std::nullopt);
628 ASSERT_TRUE(CSIdConv.FrameIdConv.LastUnmappedId.has_value());
629 EXPECT_EQ(*CSIdConv.FrameIdConv.LastUnmappedId, 3U);
630}
631
632// Verify CallStackRadixTreeBuilder can handle empty inputs.
633TEST(MemProf, RadixTreeBuilderEmpty) {
634 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes;
635 IndexedMemProfData MemProfData;
636 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =
637 computeFrameHistogram<FrameId>(MemProfCallStackData&: MemProfData.CallStacks);
638 CallStackRadixTreeBuilder<FrameId> Builder;
639 Builder.build(MemProfCallStackData: std::move(MemProfData.CallStacks), MemProfFrameIndexes: &MemProfFrameIndexes,
640 FrameHistogram);
641 ASSERT_THAT(Builder.getRadixArray(), IsEmpty());
642 const auto Mappings = Builder.takeCallStackPos();
643 ASSERT_THAT(Mappings, IsEmpty());
644}
645
646// Verify CallStackRadixTreeBuilder can handle one trivial call stack.
647TEST(MemProf, RadixTreeBuilderOne) {
648 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes = {
649 {11, 1}, {12, 2}, {13, 3}};
650 llvm::SmallVector<FrameId> CS1 = {13, 12, 11};
651 IndexedMemProfData MemProfData;
652 auto CS1Id = MemProfData.addCallStack(CS: std::move(CS1));
653 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =
654 computeFrameHistogram<FrameId>(MemProfCallStackData&: MemProfData.CallStacks);
655 CallStackRadixTreeBuilder<FrameId> Builder;
656 Builder.build(MemProfCallStackData: std::move(MemProfData.CallStacks), MemProfFrameIndexes: &MemProfFrameIndexes,
657 FrameHistogram);
658 EXPECT_THAT(Builder.getRadixArray(),
659 ElementsAre(3U, // Size of CS1,
660 3U, // MemProfFrameIndexes[13]
661 2U, // MemProfFrameIndexes[12]
662 1U // MemProfFrameIndexes[11]
663 ));
664 const auto Mappings = Builder.takeCallStackPos();
665 EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(CS1Id, 0U)));
666}
667
668// Verify CallStackRadixTreeBuilder can form a link between two call stacks.
669TEST(MemProf, RadixTreeBuilderTwo) {
670 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes = {
671 {11, 1}, {12, 2}, {13, 3}};
672 llvm::SmallVector<FrameId> CS1 = {12, 11};
673 llvm::SmallVector<FrameId> CS2 = {13, 12, 11};
674 IndexedMemProfData MemProfData;
675 auto CS1Id = MemProfData.addCallStack(CS: std::move(CS1));
676 auto CS2Id = MemProfData.addCallStack(CS: std::move(CS2));
677 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =
678 computeFrameHistogram<FrameId>(MemProfCallStackData&: MemProfData.CallStacks);
679 CallStackRadixTreeBuilder<FrameId> Builder;
680 Builder.build(MemProfCallStackData: std::move(MemProfData.CallStacks), MemProfFrameIndexes: &MemProfFrameIndexes,
681 FrameHistogram);
682 EXPECT_THAT(Builder.getRadixArray(),
683 ElementsAre(2U, // Size of CS1
684 static_cast<uint32_t>(-3), // Jump 3 steps
685 3U, // Size of CS2
686 3U, // MemProfFrameIndexes[13]
687 2U, // MemProfFrameIndexes[12]
688 1U // MemProfFrameIndexes[11]
689 ));
690 const auto Mappings = Builder.takeCallStackPos();
691 EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(CS1Id, 0U), Pair(CS2Id, 2U)));
692}
693
694// Verify CallStackRadixTreeBuilder can form a jump to a prefix that itself has
695// another jump to another prefix.
696TEST(MemProf, RadixTreeBuilderSuccessiveJumps) {
697 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes = {
698 {11, 1}, {12, 2}, {13, 3}, {14, 4}, {15, 5}, {16, 6}, {17, 7}, {18, 8},
699 };
700 llvm::SmallVector<FrameId> CS1 = {14, 13, 12, 11};
701 llvm::SmallVector<FrameId> CS2 = {15, 13, 12, 11};
702 llvm::SmallVector<FrameId> CS3 = {17, 16, 12, 11};
703 llvm::SmallVector<FrameId> CS4 = {18, 16, 12, 11};
704 IndexedMemProfData MemProfData;
705 auto CS1Id = MemProfData.addCallStack(CS: std::move(CS1));
706 auto CS2Id = MemProfData.addCallStack(CS: std::move(CS2));
707 auto CS3Id = MemProfData.addCallStack(CS: std::move(CS3));
708 auto CS4Id = MemProfData.addCallStack(CS: std::move(CS4));
709 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =
710 computeFrameHistogram<FrameId>(MemProfCallStackData&: MemProfData.CallStacks);
711 CallStackRadixTreeBuilder<FrameId> Builder;
712 Builder.build(MemProfCallStackData: std::move(MemProfData.CallStacks), MemProfFrameIndexes: &MemProfFrameIndexes,
713 FrameHistogram);
714 EXPECT_THAT(Builder.getRadixArray(),
715 ElementsAre(4U, // Size of CS1
716 4U, // MemProfFrameIndexes[14]
717 static_cast<uint32_t>(-3), // Jump 3 steps
718 4U, // Size of CS2
719 5U, // MemProfFrameIndexes[15]
720 3U, // MemProfFrameIndexes[13]
721 static_cast<uint32_t>(-7), // Jump 7 steps
722 4U, // Size of CS3
723 7U, // MemProfFrameIndexes[17]
724 static_cast<uint32_t>(-3), // Jump 3 steps
725 4U, // Size of CS4
726 8U, // MemProfFrameIndexes[18]
727 6U, // MemProfFrameIndexes[16]
728 2U, // MemProfFrameIndexes[12]
729 1U // MemProfFrameIndexes[11]
730 ));
731 const auto Mappings = Builder.takeCallStackPos();
732 EXPECT_THAT(Mappings,
733 UnorderedElementsAre(Pair(CS1Id, 0U), Pair(CS2Id, 3U),
734 Pair(CS3Id, 7U), Pair(CS4Id, 10U)));
735}
736
737// Verify that we can parse YAML and retrieve IndexedMemProfData as expected.
738TEST(MemProf, YAMLParser) {
739 StringRef YAMLData = R"YAML(
740---
741HeapProfileRecords:
742- GUID: 0xdeadbeef12345678
743 AllocSites:
744 - Callstack:
745 - {Function: 0x100, LineOffset: 11, Column: 10, IsInlineFrame: true}
746 - {Function: 0x200, LineOffset: 22, Column: 20, IsInlineFrame: false}
747 MemInfoBlock:
748 AllocCount: 777
749 TotalSize: 888
750 - Callstack:
751 - {Function: 0x300, LineOffset: 33, Column: 30, IsInlineFrame: false}
752 - {Function: 0x400, LineOffset: 44, Column: 40, IsInlineFrame: true}
753 MemInfoBlock:
754 AllocCount: 666
755 TotalSize: 555
756 CallSites:
757 - Frames:
758 - {Function: 0x500, LineOffset: 55, Column: 50, IsInlineFrame: true}
759 - {Function: 0x600, LineOffset: 66, Column: 60, IsInlineFrame: false}
760 CalleeGuids: [0x1000, 0x2000]
761 - Frames:
762 - {Function: 0x700, LineOffset: 77, Column: 70, IsInlineFrame: true}
763 - {Function: 0x800, LineOffset: 88, Column: 80, IsInlineFrame: false}
764 CalleeGuids: [0x3000]
765)YAML";
766
767 YAMLMemProfReader YAMLReader;
768 YAMLReader.parse(YAMLData);
769 IndexedMemProfData MemProfData = YAMLReader.takeMemProfData();
770
771 // Verify the entire contents of MemProfData.Records.
772 ASSERT_THAT(MemProfData.Records, SizeIs(1));
773 const auto &[GUID, IndexedRecord] = MemProfData.Records.front();
774 EXPECT_EQ(GUID, 0xdeadbeef12345678ULL);
775
776 IndexedCallstackIdConverter CSIdConv(MemProfData);
777 MemProfRecord Record = IndexedRecord.toMemProfRecord(Callback: CSIdConv);
778
779 ASSERT_THAT(Record.AllocSites, SizeIs(2));
780 EXPECT_THAT(
781 Record.AllocSites[0].CallStack,
782 ElementsAre(Frame(0x100, 11, 10, true), Frame(0x200, 22, 20, false)));
783 EXPECT_EQ(Record.AllocSites[0].Info.getAllocCount(), 777U);
784 EXPECT_EQ(Record.AllocSites[0].Info.getTotalSize(), 888U);
785 EXPECT_THAT(
786 Record.AllocSites[1].CallStack,
787 ElementsAre(Frame(0x300, 33, 30, false), Frame(0x400, 44, 40, true)));
788 EXPECT_EQ(Record.AllocSites[1].Info.getAllocCount(), 666U);
789 EXPECT_EQ(Record.AllocSites[1].Info.getTotalSize(), 555U);
790 EXPECT_THAT(
791 Record.CallSites,
792 ElementsAre(
793 AllOf(testing::Field(&CallSiteInfo::Frames,
794 ElementsAre(Frame(0x500, 55, 50, true),
795 Frame(0x600, 66, 60, false))),
796 testing::Field(&CallSiteInfo::CalleeGuids,
797 ElementsAre(0x1000, 0x2000))),
798 AllOf(testing::Field(&CallSiteInfo::Frames,
799 ElementsAre(Frame(0x700, 77, 70, true),
800 Frame(0x800, 88, 80, false))),
801 testing::Field(&CallSiteInfo::CalleeGuids,
802 ElementsAre(0x3000)))));
803}
804
805// Verify that the YAML parser accepts a GUID expressed as a function name.
806TEST(MemProf, YAMLParserGUID) {
807 StringRef YAMLData = R"YAML(
808---
809HeapProfileRecords:
810- GUID: _Z3fooi
811 AllocSites:
812 - Callstack:
813 - {Function: 0x100, LineOffset: 11, Column: 10, IsInlineFrame: true}
814 MemInfoBlock: {}
815 CallSites: []
816)YAML";
817
818 YAMLMemProfReader YAMLReader;
819 YAMLReader.parse(YAMLData);
820 IndexedMemProfData MemProfData = YAMLReader.takeMemProfData();
821
822 // Verify the entire contents of MemProfData.Records.
823 ASSERT_THAT(MemProfData.Records, SizeIs(1));
824 const auto &[GUID, IndexedRecord] = MemProfData.Records.front();
825 EXPECT_EQ(GUID, memprof::getGUID("_Z3fooi"));
826
827 IndexedCallstackIdConverter CSIdConv(MemProfData);
828 MemProfRecord Record = IndexedRecord.toMemProfRecord(Callback: CSIdConv);
829
830 ASSERT_THAT(Record.AllocSites, SizeIs(1));
831 EXPECT_THAT(Record.AllocSites[0].CallStack,
832 ElementsAre(Frame(0x100, 11, 10, true)));
833 EXPECT_THAT(Record.CallSites, IsEmpty());
834}
835
836template <typename T> std::string serializeInYAML(T &Val) {
837 std::string Out;
838 llvm::raw_string_ostream OS(Out);
839 llvm::yaml::Output Yout(OS);
840 Yout << Val;
841 return Out;
842}
843
844TEST(MemProf, YAMLWriterFrame) {
845 Frame F(0x0123456789abcdefULL, 22, 33, true);
846
847 std::string Out = serializeInYAML(Val&: F);
848 EXPECT_EQ(Out, R"YAML(---
849{ Function: 0x123456789abcdef, LineOffset: 22, Column: 33, IsInlineFrame: true }
850...
851)YAML");
852}
853
854TEST(MemProf, YAMLWriterMIB) {
855 MemInfoBlock MIB;
856 MIB.AllocCount = 111;
857 MIB.TotalSize = 222;
858 MIB.TotalLifetime = 333;
859 MIB.TotalLifetimeAccessDensity = 444;
860 PortableMemInfoBlock PMIB(MIB, getHotColdSchema());
861
862 std::string Out = serializeInYAML(Val&: PMIB);
863 EXPECT_EQ(Out, R"YAML(---
864AllocCount: 111
865TotalSize: 222
866TotalLifetime: 333
867TotalLifetimeAccessDensity: 444
868...
869)YAML");
870}
871
872// Test getAllocType helper.
873// Basic checks on the allocation type for values just above and below
874// the thresholds.
875TEST(MemProf, GetAllocType) {
876 const uint64_t AllocCount = 2;
877 // To be cold we require that
878 // ((float)TotalLifetimeAccessDensity) / AllocCount / 100 <
879 // MemProfLifetimeAccessDensityColdThreshold
880 // so compute the ColdTotalLifetimeAccessDensityThreshold at the threshold.
881 const uint64_t ColdTotalLifetimeAccessDensityThreshold =
882 (uint64_t)(MemProfLifetimeAccessDensityColdThreshold * AllocCount * 100);
883 // To be cold we require that
884 // ((float)TotalLifetime) / AllocCount >=
885 // MemProfAveLifetimeColdThreshold * 1000
886 // so compute the TotalLifetime right at the threshold.
887 const uint64_t ColdTotalLifetimeThreshold =
888 MemProfAveLifetimeColdThreshold * AllocCount * 1000;
889 // To be hot we require that
890 // ((float)TotalLifetimeAccessDensity) / AllocCount / 100 >
891 // MemProfMinAveLifetimeAccessDensityHotThreshold
892 // so compute the HotTotalLifetimeAccessDensityThreshold at the threshold.
893 const uint64_t HotTotalLifetimeAccessDensityThreshold =
894 (uint64_t)(MemProfMinAveLifetimeAccessDensityHotThreshold * AllocCount *
895 100);
896
897 // Make sure the option for detecting hot allocations is set.
898 bool OrigMemProfUseHotHints = MemProfUseHotHints;
899 MemProfUseHotHints = true;
900
901 // Test Hot
902 // More accesses per byte per sec than hot threshold is hot.
903 EXPECT_EQ(getAllocType(HotTotalLifetimeAccessDensityThreshold + 1, AllocCount,
904 ColdTotalLifetimeThreshold + 1),
905 AllocationType::Hot);
906
907 // Restore original option value.
908 MemProfUseHotHints = OrigMemProfUseHotHints;
909
910 // Without MemProfUseHotHints (default) we should treat simply as NotCold.
911 EXPECT_EQ(getAllocType(HotTotalLifetimeAccessDensityThreshold + 1, AllocCount,
912 ColdTotalLifetimeThreshold + 1),
913 AllocationType::NotCold);
914
915 // Test Cold
916 // Long lived with less accesses per byte per sec than cold threshold is cold.
917 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold - 1,
918 AllocCount, ColdTotalLifetimeThreshold + 1),
919 AllocationType::Cold);
920
921 // Test NotCold
922 // Long lived with more accesses per byte per sec than cold threshold is not
923 // cold.
924 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold + 1,
925 AllocCount, ColdTotalLifetimeThreshold + 1),
926 AllocationType::NotCold);
927 // Short lived with more accesses per byte per sec than cold threshold is not
928 // cold.
929 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold + 1,
930 AllocCount, ColdTotalLifetimeThreshold - 1),
931 AllocationType::NotCold);
932 // Short lived with less accesses per byte per sec than cold threshold is not
933 // cold.
934 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold - 1,
935 AllocCount, ColdTotalLifetimeThreshold - 1),
936 AllocationType::NotCold);
937}
938
939} // namespace
940} // namespace memprof
941} // namespace llvm
942

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of llvm/unittests/ProfileData/MemProfTest.cpp