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