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 | |
29 | LLVM_ABI extern llvm::cl::opt<float> MemProfLifetimeAccessDensityColdThreshold; |
30 | LLVM_ABI extern llvm::cl::opt<unsigned> MemProfAveLifetimeColdThreshold; |
31 | LLVM_ABI extern llvm::cl::opt<unsigned> |
32 | MemProfMinAveLifetimeAccessDensityHotThreshold; |
33 | LLVM_ABI extern llvm::cl::opt<bool> MemProfUseHotHints; |
34 | |
35 | namespace llvm { |
36 | namespace memprof { |
37 | |
38 | namespace { |
39 | |
40 | using ::llvm::DIGlobal; |
41 | using ::llvm::DIInliningInfo; |
42 | using ::llvm::DILineInfo; |
43 | using ::llvm::DILineInfoSpecifier; |
44 | using ::llvm::DILocal; |
45 | using ::llvm::StringRef; |
46 | using ::llvm::object::SectionedAddress; |
47 | using ::llvm::symbolize::SymbolizableModule; |
48 | using ::testing::ElementsAre; |
49 | using ::testing::IsEmpty; |
50 | using ::testing::Pair; |
51 | using ::testing::Return; |
52 | using ::testing::SizeIs; |
53 | using ::testing::UnorderedElementsAre; |
54 | |
55 | class MockSymbolizer : public SymbolizableModule { |
56 | public: |
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 | |
82 | struct 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 | }; |
89 | DIInliningInfo 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 | |
103 | llvm::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 | |
110 | const DILineInfoSpecifier specifier() { |
111 | return DILineInfoSpecifier( |
112 | DILineInfoSpecifier::FileLineInfoKind::RawValue, |
113 | DILineInfoSpecifier::FunctionNameKind::LinkageName); |
114 | } |
115 | |
116 | MATCHER_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 | |
137 | TEST(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 | |
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 = 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 = 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 | |
305 | TEST(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 | |
350 | TEST(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 | |
407 | TEST(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 | |
471 | TEST(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 | |
503 | TEST(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 | |
535 | TEST(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. |
585 | MemInfoBlock 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 | |
594 | TEST(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 | |
614 | TEST(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. |
633 | TEST(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. |
647 | TEST(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. |
669 | TEST(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. |
696 | TEST(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. |
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 | - 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. |
806 | TEST(MemProf, YAMLParserGUID) { |
807 | StringRef YAMLData = R"YAML( |
808 | --- |
809 | HeapProfileRecords: |
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 | |
836 | template <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 | |
844 | TEST(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 | |
854 | TEST(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(--- |
864 | AllocCount: 111 |
865 | TotalSize: 222 |
866 | TotalLifetime: 333 |
867 | TotalLifetimeAccessDensity: 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. |
875 | TEST(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 | |