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