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