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