xref: /llvm-project/llvm/unittests/ProfileData/MemProfTest.cpp (revision 00090ac0b9d391acb07c0099311987671c2cac1c)
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/MemProfData.inc"
18 #include "llvm/ProfileData/MemProfReader.h"
19 #include "llvm/Support/raw_ostream.h"
20 #include "gmock/gmock.h"
21 #include "gtest/gtest.h"
22 
23 #include <initializer_list>
24 
25 namespace {
26 
27 using ::llvm::DIGlobal;
28 using ::llvm::DIInliningInfo;
29 using ::llvm::DILineInfo;
30 using ::llvm::DILineInfoSpecifier;
31 using ::llvm::DILocal;
32 using ::llvm::StringRef;
33 using ::llvm::memprof::CallStackId;
34 using ::llvm::memprof::CallStackMap;
35 using ::llvm::memprof::Frame;
36 using ::llvm::memprof::FrameId;
37 using ::llvm::memprof::hashCallStack;
38 using ::llvm::memprof::IndexedAllocationInfo;
39 using ::llvm::memprof::IndexedMemProfData;
40 using ::llvm::memprof::IndexedMemProfRecord;
41 using ::llvm::memprof::MemInfoBlock;
42 using ::llvm::memprof::MemProfReader;
43 using ::llvm::memprof::MemProfRecord;
44 using ::llvm::memprof::MemProfSchema;
45 using ::llvm::memprof::Meta;
46 using ::llvm::memprof::PortableMemInfoBlock;
47 using ::llvm::memprof::RawMemProfReader;
48 using ::llvm::memprof::SegmentEntry;
49 using ::llvm::object::SectionedAddress;
50 using ::llvm::symbolize::SymbolizableModule;
51 using ::testing::ElementsAre;
52 using ::testing::Pair;
53 using ::testing::Return;
54 using ::testing::SizeIs;
55 using ::testing::UnorderedElementsAre;
56 
57 class MockSymbolizer : public SymbolizableModule {
58 public:
59   MOCK_CONST_METHOD3(symbolizeInlinedCode,
60                      DIInliningInfo(SectionedAddress, DILineInfoSpecifier,
61                                     bool));
62   // Most of the methods in the interface are unused. We only mock the
63   // method that we expect to be called from the memprof reader.
64   virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier,
65                                    bool) const {
66     llvm_unreachable("unused");
67   }
68   virtual DIGlobal symbolizeData(SectionedAddress) const {
69     llvm_unreachable("unused");
70   }
71   virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const {
72     llvm_unreachable("unused");
73   }
74   virtual std::vector<SectionedAddress> findSymbol(StringRef Symbol,
75                                                    uint64_t Offset) const {
76     llvm_unreachable("unused");
77   }
78   virtual bool isWin32Module() const { llvm_unreachable("unused"); }
79   virtual uint64_t getModulePreferredBase() const {
80     llvm_unreachable("unused");
81   }
82 };
83 
84 struct MockInfo {
85   std::string FunctionName;
86   uint32_t Line;
87   uint32_t StartLine;
88   uint32_t Column;
89   std::string FileName = "valid/path.cc";
90 };
91 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) {
92   DIInliningInfo Result;
93   for (const auto &Item : MockFrames) {
94     DILineInfo Frame;
95     Frame.FunctionName = Item.FunctionName;
96     Frame.Line = Item.Line;
97     Frame.StartLine = Item.StartLine;
98     Frame.Column = Item.Column;
99     Frame.FileName = Item.FileName;
100     Result.addFrame(Frame);
101   }
102   return Result;
103 }
104 
105 llvm::SmallVector<SegmentEntry, 4> makeSegments() {
106   llvm::SmallVector<SegmentEntry, 4> Result;
107   // Mimic an entry for a non position independent executable.
108   Result.emplace_back(0x0, 0x40000, 0x0);
109   return Result;
110 }
111 
112 const DILineInfoSpecifier specifier() {
113   return DILineInfoSpecifier(
114       DILineInfoSpecifier::FileLineInfoKind::RawValue,
115       DILineInfoSpecifier::FunctionNameKind::LinkageName);
116 }
117 
118 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {
119   const Frame &F = arg;
120 
121   const uint64_t ExpectedHash = IndexedMemProfRecord::getGUID(FunctionName);
122   if (F.Function != ExpectedHash) {
123     *result_listener << "Hash mismatch";
124     return false;
125   }
126   if (F.SymbolName && *F.SymbolName != FunctionName) {
127     *result_listener << "SymbolName mismatch\nWant: " << FunctionName
128                      << "\nGot: " << *F.SymbolName;
129     return false;
130   }
131   if (F.LineOffset == LineOffset && F.Column == Column &&
132       F.IsInlineFrame == Inline) {
133     return true;
134   }
135   *result_listener << "LineOffset, Column or Inline mismatch";
136   return false;
137 }
138 
139 TEST(MemProf, FillsValue) {
140   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
141 
142   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
143                                                 specifier(), false))
144       .Times(1) // Only once since we remember invalid PCs.
145       .WillRepeatedly(Return(makeInliningInfo({
146           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
147       })));
148 
149   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
150                                                 specifier(), false))
151       .Times(1) // Only once since we cache the result for future lookups.
152       .WillRepeatedly(Return(makeInliningInfo({
153           {"foo", 10, 5, 30},
154           {"bar", 201, 150, 20},
155       })));
156 
157   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
158                                                 specifier(), false))
159       .Times(1)
160       .WillRepeatedly(Return(makeInliningInfo({
161           {"xyz.llvm.123", 10, 5, 30},
162           {"abc", 10, 5, 30},
163       })));
164 
165   CallStackMap CSM;
166   CSM[0x1] = {0x1000, 0x2000, 0x3000};
167 
168   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
169   Prof[0x1].AllocCount = 1;
170 
171   auto Seg = makeSegments();
172 
173   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM,
174                           /*KeepName=*/true);
175 
176   llvm::DenseMap<llvm::GlobalValue::GUID, MemProfRecord> Records;
177   for (const auto &Pair : Reader) {
178     Records.insert({Pair.first, Pair.second});
179   }
180 
181   // Mock program pseudocode and expected memprof record contents.
182   //
183   //                              AllocSite       CallSite
184   // inline foo() { new(); }         Y               N
185   // bar() { foo(); }                Y               Y
186   // inline xyz() { bar(); }         N               Y
187   // abc() { xyz(); }                N               Y
188 
189   // We expect 4 records. We attach alloc site data to foo and bar, i.e.
190   // all frames bottom up until we find a non-inline frame. We attach call site
191   // data to bar, xyz and abc.
192   ASSERT_THAT(Records, SizeIs(4));
193 
194   // Check the memprof record for foo.
195   const llvm::GlobalValue::GUID FooId = IndexedMemProfRecord::getGUID("foo");
196   ASSERT_TRUE(Records.contains(FooId));
197   const MemProfRecord &Foo = Records[FooId];
198   ASSERT_THAT(Foo.AllocSites, SizeIs(1));
199   EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U);
200   EXPECT_THAT(Foo.AllocSites[0].CallStack[0],
201               FrameContains("foo", 5U, 30U, true));
202   EXPECT_THAT(Foo.AllocSites[0].CallStack[1],
203               FrameContains("bar", 51U, 20U, false));
204   EXPECT_THAT(Foo.AllocSites[0].CallStack[2],
205               FrameContains("xyz", 5U, 30U, true));
206   EXPECT_THAT(Foo.AllocSites[0].CallStack[3],
207               FrameContains("abc", 5U, 30U, false));
208   EXPECT_TRUE(Foo.CallSites.empty());
209 
210   // Check the memprof record for bar.
211   const llvm::GlobalValue::GUID BarId = IndexedMemProfRecord::getGUID("bar");
212   ASSERT_TRUE(Records.contains(BarId));
213   const MemProfRecord &Bar = Records[BarId];
214   ASSERT_THAT(Bar.AllocSites, SizeIs(1));
215   EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U);
216   EXPECT_THAT(Bar.AllocSites[0].CallStack[0],
217               FrameContains("foo", 5U, 30U, true));
218   EXPECT_THAT(Bar.AllocSites[0].CallStack[1],
219               FrameContains("bar", 51U, 20U, false));
220   EXPECT_THAT(Bar.AllocSites[0].CallStack[2],
221               FrameContains("xyz", 5U, 30U, true));
222   EXPECT_THAT(Bar.AllocSites[0].CallStack[3],
223               FrameContains("abc", 5U, 30U, false));
224 
225   ASSERT_THAT(Bar.CallSites, SizeIs(1));
226   ASSERT_THAT(Bar.CallSites[0], SizeIs(2));
227   EXPECT_THAT(Bar.CallSites[0][0], FrameContains("foo", 5U, 30U, true));
228   EXPECT_THAT(Bar.CallSites[0][1], FrameContains("bar", 51U, 20U, false));
229 
230   // Check the memprof record for xyz.
231   const llvm::GlobalValue::GUID XyzId = IndexedMemProfRecord::getGUID("xyz");
232   ASSERT_TRUE(Records.contains(XyzId));
233   const MemProfRecord &Xyz = Records[XyzId];
234   ASSERT_THAT(Xyz.CallSites, SizeIs(1));
235   ASSERT_THAT(Xyz.CallSites[0], SizeIs(2));
236   // Expect the entire frame even though in practice we only need the first
237   // entry here.
238   EXPECT_THAT(Xyz.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
239   EXPECT_THAT(Xyz.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
240 
241   // Check the memprof record for abc.
242   const llvm::GlobalValue::GUID AbcId = IndexedMemProfRecord::getGUID("abc");
243   ASSERT_TRUE(Records.contains(AbcId));
244   const MemProfRecord &Abc = Records[AbcId];
245   EXPECT_TRUE(Abc.AllocSites.empty());
246   ASSERT_THAT(Abc.CallSites, SizeIs(1));
247   ASSERT_THAT(Abc.CallSites[0], SizeIs(2));
248   EXPECT_THAT(Abc.CallSites[0][0], FrameContains("xyz", 5U, 30U, true));
249   EXPECT_THAT(Abc.CallSites[0][1], FrameContains("abc", 5U, 30U, false));
250 }
251 
252 TEST(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 = llvm::memprof::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 
276 TEST(MemProf, RecordSerializationRoundTripVerion2) {
277   const auto Schema = llvm::memprof::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<llvm::memprof::CallStackId> CallStackIds = {0x123, 0x456};
284 
285   llvm::SmallVector<llvm::memprof::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(CSId, Info);
291   }
292   Record.CallSiteIds.assign(CallSiteIds);
293 
294   std::string Buffer;
295   llvm::raw_string_ostream OS(Buffer);
296   Record.serialize(Schema, OS, llvm::memprof::Version2);
297 
298   const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
299       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()),
300       llvm::memprof::Version2);
301 
302   EXPECT_EQ(Record, GotRecord);
303 }
304 
305 TEST(MemProf, RecordSerializationRoundTripVersion2HotColdSchema) {
306   const auto Schema = llvm::memprof::getHotColdSchema();
307 
308   MemInfoBlock Info;
309   Info.AllocCount = 11;
310   Info.TotalSize = 22;
311   Info.TotalLifetime = 33;
312   Info.TotalLifetimeAccessDensity = 44;
313 
314   llvm::SmallVector<llvm::memprof::CallStackId> CallStackIds = {0x123, 0x456};
315 
316   llvm::SmallVector<llvm::memprof::CallStackId> CallSiteIds = {0x333, 0x444};
317 
318   IndexedMemProfRecord Record;
319   for (const auto &CSId : CallStackIds) {
320     // Use the same info block for both allocation sites.
321     Record.AllocSites.emplace_back(CSId, Info, Schema);
322   }
323   Record.CallSiteIds.assign(CallSiteIds);
324 
325   std::bitset<llvm::to_underlying(Meta::Size)> SchemaBitSet;
326   for (auto Id : Schema)
327     SchemaBitSet.set(llvm::to_underlying(Id));
328 
329   // Verify that SchemaBitSet has the fields we expect and nothing else, which
330   // we check with count().
331   EXPECT_EQ(SchemaBitSet.count(), 4U);
332   EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::AllocCount)]);
333   EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalSize)]);
334   EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalLifetime)]);
335   EXPECT_TRUE(
336       SchemaBitSet[llvm::to_underlying(Meta::TotalLifetimeAccessDensity)]);
337 
338   // Verify that Schema has propagated all the way to the Info field in each
339   // IndexedAllocationInfo.
340   ASSERT_THAT(Record.AllocSites, ::SizeIs(2));
341   EXPECT_EQ(Record.AllocSites[0].Info.getSchema(), SchemaBitSet);
342   EXPECT_EQ(Record.AllocSites[1].Info.getSchema(), SchemaBitSet);
343 
344   std::string Buffer;
345   llvm::raw_string_ostream OS(Buffer);
346   Record.serialize(Schema, OS, llvm::memprof::Version2);
347 
348   const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(
349       Schema, reinterpret_cast<const unsigned char *>(Buffer.data()),
350       llvm::memprof::Version2);
351 
352   // Verify that Schema comes back correctly after deserialization. Technically,
353   // the comparison between Record and GotRecord below includes the comparison
354   // of their Schemas, but we'll verify the Schemas on our own.
355   ASSERT_THAT(GotRecord.AllocSites, ::SizeIs(2));
356   EXPECT_EQ(GotRecord.AllocSites[0].Info.getSchema(), SchemaBitSet);
357   EXPECT_EQ(GotRecord.AllocSites[1].Info.getSchema(), SchemaBitSet);
358 
359   EXPECT_EQ(Record, GotRecord);
360 }
361 
362 TEST(MemProf, SymbolizationFilter) {
363   std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer());
364 
365   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},
366                                                 specifier(), false))
367       .Times(1) // once since we don't lookup invalid PCs repeatedly.
368       .WillRepeatedly(Return(makeInliningInfo({
369           {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},
370       })));
371 
372   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},
373                                                 specifier(), false))
374       .Times(1) // once since we don't lookup invalid PCs repeatedly.
375       .WillRepeatedly(Return(makeInliningInfo({
376           {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},
377       })));
378 
379   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},
380                                                 specifier(), false))
381       .Times(1) // once since we don't lookup invalid PCs repeatedly.
382       .WillRepeatedly(Return(makeInliningInfo({
383           {DILineInfo::BadString, 0, 0, 0},
384       })));
385 
386   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},
387                                                 specifier(), false))
388       .Times(1)
389       .WillRepeatedly(Return(makeInliningInfo({
390           {"foo", 10, 5, 30, "memprof/memprof_test_file.cpp"},
391       })));
392 
393   EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x5000},
394                                                 specifier(), false))
395       .Times(1)
396       .WillRepeatedly(Return(makeInliningInfo({
397           // Depending on how the runtime was compiled, only the filename
398           // may be present in the debug information.
399           {"malloc", 70, 57, 3, "memprof_malloc_linux.cpp"},
400       })));
401 
402   CallStackMap CSM;
403   CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};
404   // This entry should be dropped since all PCs are either not
405   // symbolizable or belong to the runtime.
406   CSM[0x2] = {0x1000, 0x2000, 0x5000};
407 
408   llvm::MapVector<uint64_t, MemInfoBlock> Prof;
409   Prof[0x1].AllocCount = 1;
410   Prof[0x2].AllocCount = 1;
411 
412   auto Seg = makeSegments();
413 
414   RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);
415 
416   llvm::SmallVector<MemProfRecord, 1> Records;
417   for (const auto &KeyRecordPair : Reader) {
418     Records.push_back(KeyRecordPair.second);
419   }
420 
421   ASSERT_THAT(Records, SizeIs(1));
422   ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
423   ASSERT_THAT(Records[0].AllocSites[0].CallStack, SizeIs(1));
424   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
425               FrameContains("foo", 5U, 30U, false));
426 }
427 
428 TEST(MemProf, BaseMemProfReader) {
429   llvm::memprof::IndexedMemProfData MemProfData;
430   Frame F1(/*Hash=*/IndexedMemProfRecord::getGUID("foo"), /*LineOffset=*/20,
431            /*Column=*/5, /*IsInlineFrame=*/true);
432   Frame F2(/*Hash=*/IndexedMemProfRecord::getGUID("bar"), /*LineOffset=*/10,
433            /*Column=*/2, /*IsInlineFrame=*/false);
434   MemProfData.addFrame(F1);
435   MemProfData.addFrame(F2);
436 
437   llvm::SmallVector<FrameId> CallStack{F1.hash(), F2.hash()};
438   CallStackId CSId = MemProfData.addCallStack(std::move(CallStack));
439 
440   IndexedMemProfRecord FakeRecord;
441   MemInfoBlock Block;
442   Block.AllocCount = 1U, Block.TotalAccessDensity = 4,
443   Block.TotalLifetime = 200001;
444   FakeRecord.AllocSites.emplace_back(/*CSId=*/CSId, /*MB=*/Block);
445   MemProfData.Records.insert({F1.hash(), FakeRecord});
446 
447   MemProfReader Reader(std::move(MemProfData));
448 
449   llvm::SmallVector<MemProfRecord, 1> Records;
450   for (const auto &KeyRecordPair : Reader) {
451     Records.push_back(KeyRecordPair.second);
452   }
453 
454   ASSERT_THAT(Records, SizeIs(1));
455   ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
456   ASSERT_THAT(Records[0].AllocSites[0].CallStack, SizeIs(2));
457   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
458               FrameContains("foo", 20U, 5U, true));
459   EXPECT_THAT(Records[0].AllocSites[0].CallStack[1],
460               FrameContains("bar", 10U, 2U, false));
461 }
462 
463 TEST(MemProf, BaseMemProfReaderWithCSIdMap) {
464   llvm::memprof::IndexedMemProfData MemProfData;
465   Frame F1(/*Hash=*/IndexedMemProfRecord::getGUID("foo"), /*LineOffset=*/20,
466            /*Column=*/5, /*IsInlineFrame=*/true);
467   Frame F2(/*Hash=*/IndexedMemProfRecord::getGUID("bar"), /*LineOffset=*/10,
468            /*Column=*/2, /*IsInlineFrame=*/false);
469   MemProfData.addFrame(F1);
470   MemProfData.addFrame(F2);
471 
472   llvm::SmallVector<FrameId> CallStack = {F1.hash(), F2.hash()};
473   MemProfData.addCallStack(CallStack);
474 
475   IndexedMemProfRecord FakeRecord;
476   MemInfoBlock Block;
477   Block.AllocCount = 1U, Block.TotalAccessDensity = 4,
478   Block.TotalLifetime = 200001;
479   FakeRecord.AllocSites.emplace_back(
480       /*CSId=*/hashCallStack(CallStack),
481       /*MB=*/Block);
482   MemProfData.Records.insert({F1.hash(), FakeRecord});
483 
484   MemProfReader Reader(std::move(MemProfData));
485 
486   llvm::SmallVector<MemProfRecord, 1> Records;
487   for (const auto &KeyRecordPair : Reader) {
488     Records.push_back(KeyRecordPair.second);
489   }
490 
491   ASSERT_THAT(Records, SizeIs(1));
492   ASSERT_THAT(Records[0].AllocSites, SizeIs(1));
493   ASSERT_THAT(Records[0].AllocSites[0].CallStack, SizeIs(2));
494   EXPECT_THAT(Records[0].AllocSites[0].CallStack[0],
495               FrameContains("foo", 20U, 5U, true));
496   EXPECT_THAT(Records[0].AllocSites[0].CallStack[1],
497               FrameContains("bar", 10U, 2U, false));
498 }
499 
500 TEST(MemProf, IndexedMemProfRecordToMemProfRecord) {
501   // Verify that MemProfRecord can be constructed from IndexedMemProfRecord with
502   // CallStackIds only.
503 
504   IndexedMemProfData MemProfData;
505   Frame F1(1, 0, 0, false);
506   Frame F2(2, 0, 0, false);
507   Frame F3(3, 0, 0, false);
508   Frame F4(4, 0, 0, false);
509   MemProfData.addFrame(F1);
510   MemProfData.addFrame(F2);
511   MemProfData.addFrame(F3);
512   MemProfData.addFrame(F4);
513 
514   llvm::SmallVector<FrameId> CS1 = {F1.hash(), F2.hash()};
515   llvm::SmallVector<FrameId> CS2 = {F1.hash(), F3.hash()};
516   llvm::SmallVector<FrameId> CS3 = {F2.hash(), F3.hash()};
517   llvm::SmallVector<FrameId> CS4 = {F2.hash(), F4.hash()};
518   MemProfData.addCallStack(CS1);
519   MemProfData.addCallStack(CS2);
520   MemProfData.addCallStack(CS3);
521   MemProfData.addCallStack(CS4);
522 
523   IndexedMemProfRecord IndexedRecord;
524   IndexedAllocationInfo AI;
525   AI.CSId = hashCallStack(CS1);
526   IndexedRecord.AllocSites.push_back(AI);
527   AI.CSId = hashCallStack(CS2);
528   IndexedRecord.AllocSites.push_back(AI);
529   IndexedRecord.CallSiteIds.push_back(hashCallStack(CS3));
530   IndexedRecord.CallSiteIds.push_back(hashCallStack(CS4));
531 
532   llvm::memprof::FrameIdConverter<decltype(MemProfData.Frames)> FrameIdConv(
533       MemProfData.Frames);
534   llvm::memprof::CallStackIdConverter<decltype(MemProfData.CallStacks)>
535       CSIdConv(MemProfData.CallStacks, FrameIdConv);
536 
537   MemProfRecord Record = IndexedRecord.toMemProfRecord(CSIdConv);
538 
539   // Make sure that all lookups are successful.
540   ASSERT_EQ(FrameIdConv.LastUnmappedId, std::nullopt);
541   ASSERT_EQ(CSIdConv.LastUnmappedId, std::nullopt);
542 
543   // Verify the contents of Record.
544   ASSERT_THAT(Record.AllocSites, SizeIs(2));
545   ASSERT_THAT(Record.AllocSites[0].CallStack, SizeIs(2));
546   EXPECT_EQ(Record.AllocSites[0].CallStack[0].hash(), F1.hash());
547   EXPECT_EQ(Record.AllocSites[0].CallStack[1].hash(), F2.hash());
548   ASSERT_THAT(Record.AllocSites[1].CallStack, SizeIs(2));
549   EXPECT_EQ(Record.AllocSites[1].CallStack[0].hash(), F1.hash());
550   EXPECT_EQ(Record.AllocSites[1].CallStack[1].hash(), F3.hash());
551   ASSERT_THAT(Record.CallSites, SizeIs(2));
552   ASSERT_THAT(Record.CallSites[0], SizeIs(2));
553   EXPECT_EQ(Record.CallSites[0][0].hash(), F2.hash());
554   EXPECT_EQ(Record.CallSites[0][1].hash(), F3.hash());
555   ASSERT_THAT(Record.CallSites[1], SizeIs(2));
556   EXPECT_EQ(Record.CallSites[1][0].hash(), F2.hash());
557   EXPECT_EQ(Record.CallSites[1][1].hash(), F4.hash());
558 }
559 
560 using FrameIdMapTy =
561     llvm::DenseMap<::llvm::memprof::FrameId, ::llvm::memprof::Frame>;
562 using CallStackIdMapTy =
563     llvm::DenseMap<::llvm::memprof::CallStackId,
564                    ::llvm::SmallVector<::llvm::memprof::FrameId>>;
565 
566 // Populate those fields returned by getHotColdSchema.
567 MemInfoBlock makePartialMIB() {
568   MemInfoBlock MIB;
569   MIB.AllocCount = 1;
570   MIB.TotalSize = 5;
571   MIB.TotalLifetime = 10;
572   MIB.TotalLifetimeAccessDensity = 23;
573   return MIB;
574 }
575 
576 TEST(MemProf, MissingCallStackId) {
577   // Use a non-existent CallStackId to trigger a mapping error in
578   // toMemProfRecord.
579   llvm::memprof::IndexedAllocationInfo AI(0xdeadbeefU, makePartialMIB(),
580                                           llvm::memprof::getHotColdSchema());
581 
582   IndexedMemProfRecord IndexedMR;
583   IndexedMR.AllocSites.push_back(AI);
584 
585   // Create empty maps.
586   const FrameIdMapTy IdToFrameMap;
587   const CallStackIdMapTy CSIdToCallStackMap;
588   llvm::memprof::FrameIdConverter<decltype(IdToFrameMap)> FrameIdConv(
589       IdToFrameMap);
590   llvm::memprof::CallStackIdConverter<decltype(CSIdToCallStackMap)> CSIdConv(
591       CSIdToCallStackMap, FrameIdConv);
592 
593   // We are only interested in errors, not the return value.
594   (void)IndexedMR.toMemProfRecord(CSIdConv);
595 
596   ASSERT_TRUE(CSIdConv.LastUnmappedId.has_value());
597   EXPECT_EQ(*CSIdConv.LastUnmappedId, 0xdeadbeefU);
598   EXPECT_EQ(FrameIdConv.LastUnmappedId, std::nullopt);
599 }
600 
601 TEST(MemProf, MissingFrameId) {
602   llvm::memprof::IndexedAllocationInfo AI(0x222, makePartialMIB(),
603                                           llvm::memprof::getHotColdSchema());
604 
605   IndexedMemProfRecord IndexedMR;
606   IndexedMR.AllocSites.push_back(AI);
607 
608   // An empty map to trigger a mapping error.
609   const FrameIdMapTy IdToFrameMap;
610   CallStackIdMapTy CSIdToCallStackMap;
611   CSIdToCallStackMap.insert({0x222, {2, 3}});
612 
613   llvm::memprof::FrameIdConverter<decltype(IdToFrameMap)> FrameIdConv(
614       IdToFrameMap);
615   llvm::memprof::CallStackIdConverter<decltype(CSIdToCallStackMap)> CSIdConv(
616       CSIdToCallStackMap, FrameIdConv);
617 
618   // We are only interested in errors, not the return value.
619   (void)IndexedMR.toMemProfRecord(CSIdConv);
620 
621   EXPECT_EQ(CSIdConv.LastUnmappedId, std::nullopt);
622   ASSERT_TRUE(FrameIdConv.LastUnmappedId.has_value());
623   EXPECT_EQ(*FrameIdConv.LastUnmappedId, 3U);
624 }
625 
626 // Verify CallStackRadixTreeBuilder can handle empty inputs.
627 TEST(MemProf, RadixTreeBuilderEmpty) {
628   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes;
629   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
630   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
631       FrameHistogram =
632           llvm::memprof::computeFrameHistogram<FrameId>(MemProfCallStackData);
633   llvm::memprof::CallStackRadixTreeBuilder<FrameId> Builder;
634   Builder.build(std::move(MemProfCallStackData), &MemProfFrameIndexes,
635                 FrameHistogram);
636   ASSERT_THAT(Builder.getRadixArray(), testing::IsEmpty());
637   const auto Mappings = Builder.takeCallStackPos();
638   ASSERT_THAT(Mappings, testing::IsEmpty());
639 }
640 
641 // Verify CallStackRadixTreeBuilder can handle one trivial call stack.
642 TEST(MemProf, RadixTreeBuilderOne) {
643   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes = {
644       {11, 1}, {12, 2}, {13, 3}};
645   llvm::SmallVector<llvm::memprof::FrameId> CS1 = {13, 12, 11};
646   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
647   MemProfCallStackData.insert({hashCallStack(CS1), CS1});
648   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
649       FrameHistogram =
650           llvm::memprof::computeFrameHistogram<FrameId>(MemProfCallStackData);
651   llvm::memprof::CallStackRadixTreeBuilder<FrameId> Builder;
652   Builder.build(std::move(MemProfCallStackData), &MemProfFrameIndexes,
653                 FrameHistogram);
654   EXPECT_THAT(Builder.getRadixArray(),
655               ElementsAre(3U, // Size of CS1,
656                           3U, // MemProfFrameIndexes[13]
657                           2U, // MemProfFrameIndexes[12]
658                           1U  // MemProfFrameIndexes[11]
659                           ));
660   const auto Mappings = Builder.takeCallStackPos();
661   EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(hashCallStack(CS1), 0U)));
662 }
663 
664 // Verify CallStackRadixTreeBuilder can form a link between two call stacks.
665 TEST(MemProf, RadixTreeBuilderTwo) {
666   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes = {
667       {11, 1}, {12, 2}, {13, 3}};
668   llvm::SmallVector<llvm::memprof::FrameId> CS1 = {12, 11};
669   llvm::SmallVector<llvm::memprof::FrameId> CS2 = {13, 12, 11};
670   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
671   MemProfCallStackData.insert({hashCallStack(CS1), CS1});
672   MemProfCallStackData.insert({hashCallStack(CS2), CS2});
673   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
674       FrameHistogram =
675           llvm::memprof::computeFrameHistogram<FrameId>(MemProfCallStackData);
676   llvm::memprof::CallStackRadixTreeBuilder<FrameId> Builder;
677   Builder.build(std::move(MemProfCallStackData), &MemProfFrameIndexes,
678                 FrameHistogram);
679   EXPECT_THAT(Builder.getRadixArray(),
680               ElementsAre(2U,                        // Size of CS1
681                           static_cast<uint32_t>(-3), // Jump 3 steps
682                           3U,                        // Size of CS2
683                           3U,                        // MemProfFrameIndexes[13]
684                           2U,                        // MemProfFrameIndexes[12]
685                           1U                         // MemProfFrameIndexes[11]
686                           ));
687   const auto Mappings = Builder.takeCallStackPos();
688   EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(hashCallStack(CS1), 0U),
689                                              Pair(hashCallStack(CS2), 2U)));
690 }
691 
692 // Verify CallStackRadixTreeBuilder can form a jump to a prefix that itself has
693 // another jump to another prefix.
694 TEST(MemProf, RadixTreeBuilderSuccessiveJumps) {
695   llvm::DenseMap<FrameId, llvm::memprof::LinearFrameId> MemProfFrameIndexes = {
696       {11, 1}, {12, 2}, {13, 3}, {14, 4}, {15, 5}, {16, 6}, {17, 7}, {18, 8},
697   };
698   llvm::SmallVector<llvm::memprof::FrameId> CS1 = {14, 13, 12, 11};
699   llvm::SmallVector<llvm::memprof::FrameId> CS2 = {15, 13, 12, 11};
700   llvm::SmallVector<llvm::memprof::FrameId> CS3 = {17, 16, 12, 11};
701   llvm::SmallVector<llvm::memprof::FrameId> CS4 = {18, 16, 12, 11};
702   llvm::MapVector<CallStackId, llvm::SmallVector<FrameId>> MemProfCallStackData;
703   MemProfCallStackData.insert({hashCallStack(CS1), CS1});
704   MemProfCallStackData.insert({hashCallStack(CS2), CS2});
705   MemProfCallStackData.insert({hashCallStack(CS3), CS3});
706   MemProfCallStackData.insert({hashCallStack(CS4), CS4});
707   llvm::DenseMap<llvm::memprof::FrameId, llvm::memprof::FrameStat>
708       FrameHistogram =
709           llvm::memprof::computeFrameHistogram<FrameId>(MemProfCallStackData);
710   llvm::memprof::CallStackRadixTreeBuilder<FrameId> Builder;
711   Builder.build(std::move(MemProfCallStackData), &MemProfFrameIndexes,
712                 FrameHistogram);
713   EXPECT_THAT(Builder.getRadixArray(),
714               ElementsAre(4U,                        // Size of CS1
715                           4U,                        // MemProfFrameIndexes[14]
716                           static_cast<uint32_t>(-3), // Jump 3 steps
717                           4U,                        // Size of CS2
718                           5U,                        // MemProfFrameIndexes[15]
719                           3U,                        // MemProfFrameIndexes[13]
720                           static_cast<uint32_t>(-7), // Jump 7 steps
721                           4U,                        // Size of CS3
722                           7U,                        // MemProfFrameIndexes[17]
723                           static_cast<uint32_t>(-3), // Jump 3 steps
724                           4U,                        // Size of CS4
725                           8U,                        // MemProfFrameIndexes[18]
726                           6U,                        // MemProfFrameIndexes[16]
727                           2U,                        // MemProfFrameIndexes[12]
728                           1U                         // MemProfFrameIndexes[11]
729                           ));
730   const auto Mappings = Builder.takeCallStackPos();
731   EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(hashCallStack(CS1), 0U),
732                                              Pair(hashCallStack(CS2), 3U),
733                                              Pair(hashCallStack(CS3), 7U),
734                                              Pair(hashCallStack(CS4), 10U)));
735 }
736 
737 // Verify that we can parse YAML and retrieve IndexedMemProfData as expected.
738 TEST(MemProf, YAMLParser) {
739   StringRef YAMLData = R"YAML(
740 ---
741 HeapProfileRecords:
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   - - {Function: 0x500, LineOffset: 55, Column: 50, IsInlineFrame: true}
758     - {Function: 0x600, LineOffset: 66, Column: 60, IsInlineFrame: false}
759   - - {Function: 0x700, LineOffset: 77, Column: 70, IsInlineFrame: true}
760     - {Function: 0x800, LineOffset: 88, Column: 80, IsInlineFrame: false}
761 )YAML";
762 
763   llvm::memprof::YAMLMemProfReader YAMLReader;
764   YAMLReader.parse(YAMLData);
765   llvm::memprof::IndexedMemProfData MemProfData = YAMLReader.takeMemProfData();
766 
767   Frame F1(0x100, 11, 10, true);
768   Frame F2(0x200, 22, 20, false);
769   Frame F3(0x300, 33, 30, false);
770   Frame F4(0x400, 44, 40, true);
771   Frame F5(0x500, 55, 50, true);
772   Frame F6(0x600, 66, 60, false);
773   Frame F7(0x700, 77, 70, true);
774   Frame F8(0x800, 88, 80, false);
775 
776   llvm::SmallVector<FrameId> CS1 = {F1.hash(), F2.hash()};
777   llvm::SmallVector<FrameId> CS2 = {F3.hash(), F4.hash()};
778   llvm::SmallVector<FrameId> CS3 = {F5.hash(), F6.hash()};
779   llvm::SmallVector<FrameId> CS4 = {F7.hash(), F8.hash()};
780 
781   // Verify the entire contents of MemProfData.Frames.
782   EXPECT_THAT(MemProfData.Frames,
783               UnorderedElementsAre(Pair(F1.hash(), F1), Pair(F2.hash(), F2),
784                                    Pair(F3.hash(), F3), Pair(F4.hash(), F4),
785                                    Pair(F5.hash(), F5), Pair(F6.hash(), F6),
786                                    Pair(F7.hash(), F7), Pair(F8.hash(), F8)));
787 
788   // Verify the entire contents of MemProfData.Frames.
789   EXPECT_THAT(MemProfData.CallStacks,
790               UnorderedElementsAre(Pair(hashCallStack(CS1), CS1),
791                                    Pair(hashCallStack(CS2), CS2),
792                                    Pair(hashCallStack(CS3), CS3),
793                                    Pair(hashCallStack(CS4), CS4)));
794 
795   // Verify the entire contents of MemProfData.Records.
796   ASSERT_THAT(MemProfData.Records, SizeIs(1));
797   const auto &[GUID, Record] = *MemProfData.Records.begin();
798   EXPECT_EQ(GUID, 0xdeadbeef12345678ULL);
799   ASSERT_THAT(Record.AllocSites, SizeIs(2));
800   EXPECT_EQ(Record.AllocSites[0].CSId, hashCallStack(CS1));
801   EXPECT_EQ(Record.AllocSites[0].Info.getAllocCount(), 777U);
802   EXPECT_EQ(Record.AllocSites[0].Info.getTotalSize(), 888U);
803   EXPECT_EQ(Record.AllocSites[1].CSId, hashCallStack(CS2));
804   EXPECT_EQ(Record.AllocSites[1].Info.getAllocCount(), 666U);
805   EXPECT_EQ(Record.AllocSites[1].Info.getTotalSize(), 555U);
806   EXPECT_THAT(Record.CallSiteIds,
807               ElementsAre(hashCallStack(CS3), hashCallStack(CS4)));
808 }
809 
810 template <typename T> std::string serializeInYAML(T &Val) {
811   std::string Out;
812   llvm::raw_string_ostream OS(Out);
813   llvm::yaml::Output Yout(OS);
814   Yout << Val;
815   return Out;
816 }
817 
818 TEST(MemProf, YAMLWriterFrame) {
819   Frame F(11, 22, 33, true);
820 
821   std::string Out = serializeInYAML(F);
822   EXPECT_EQ(Out, R"YAML(---
823 { Function: 11, LineOffset: 22, Column: 33, IsInlineFrame: true }
824 ...
825 )YAML");
826 }
827 
828 TEST(MemProf, YAMLWriterMIB) {
829   MemInfoBlock MIB;
830   MIB.AllocCount = 111;
831   MIB.TotalSize = 222;
832   MIB.TotalLifetime = 333;
833   MIB.TotalLifetimeAccessDensity = 444;
834   PortableMemInfoBlock PMIB(MIB, llvm::memprof::getHotColdSchema());
835 
836   std::string Out = serializeInYAML(PMIB);
837   EXPECT_EQ(Out, R"YAML(---
838 AllocCount:      111
839 TotalSize:       222
840 TotalLifetime:   333
841 TotalLifetimeAccessDensity: 444
842 ...
843 )YAML");
844 }
845 } // namespace
846