1012 lines · cpp
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-exception6//7//===----------------------------------------------------------------------===//8 9#include "llvm/ProfileData/MemProf.h"10#include "llvm/ADT/DenseMap.h"11#include "llvm/ADT/MapVector.h"12#include "llvm/ADT/STLForwardCompat.h"13#include "llvm/DebugInfo/DIContext.h"14#include "llvm/DebugInfo/Symbolize/SymbolizableModule.h"15#include "llvm/IR/Value.h"16#include "llvm/Object/ObjectFile.h"17#include "llvm/ProfileData/IndexedMemProfData.h"18#include "llvm/ProfileData/MemProfCommon.h"19#include "llvm/ProfileData/MemProfData.inc"20#include "llvm/ProfileData/MemProfRadixTree.h"21#include "llvm/ProfileData/MemProfReader.h"22#include "llvm/Support/Compiler.h"23#include "llvm/Support/raw_ostream.h"24#include "gmock/gmock.h"25#include "gtest/gtest.h"26 27#include <initializer_list>28 29namespace llvm {30 31LLVM_ABI extern cl::opt<float> MemProfLifetimeAccessDensityColdThreshold;32LLVM_ABI extern cl::opt<unsigned> MemProfAveLifetimeColdThreshold;33LLVM_ABI extern cl::opt<unsigned>34 MemProfMinAveLifetimeAccessDensityHotThreshold;35LLVM_ABI extern cl::opt<bool> MemProfUseHotHints;36 37namespace memprof {38 39namespace {40 41using ::llvm::DIGlobal;42using ::llvm::DIInliningInfo;43using ::llvm::DILineInfo;44using ::llvm::DILineInfoSpecifier;45using ::llvm::DILocal;46using ::llvm::StringRef;47using ::llvm::object::SectionedAddress;48using ::llvm::symbolize::SymbolizableModule;49using ::testing::ElementsAre;50using ::testing::IsEmpty;51using ::testing::Pair;52using ::testing::Return;53using ::testing::SizeIs;54using ::testing::UnorderedElementsAre;55 56class MockSymbolizer : public SymbolizableModule {57public:58 MOCK_CONST_METHOD3(symbolizeInlinedCode,59 DIInliningInfo(SectionedAddress, DILineInfoSpecifier,60 bool));61 // Most of the methods in the interface are unused. We only mock the62 // 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 83struct 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};90DIInliningInfo 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 104llvm::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 111const DILineInfoSpecifier specifier() {112 return DILineInfoSpecifier(113 DILineInfoSpecifier::FileLineInfoKind::RawValue,114 DILineInfoSpecifier::FunctionNameKind::LinkageName);115}116 117MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") {118 const Frame &F = arg;119 120 const uint64_t ExpectedHash = memprof::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: " << FunctionName127 << "\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 138TEST(MemProf, FillsValue) {139 auto Symbolizer = std::make_unique<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 // Mock program pseudocode and expected memprof record contents.180 //181 // AllocSite CallSite182 // inline foo() { new(); } Y N183 // bar() { foo(); } Y Y184 // inline xyz() { bar(); } N Y185 // abc() { xyz(); } N Y186 187 // We expect 4 records. We attach alloc site data to foo and bar, i.e.188 // all frames bottom up until we find a non-inline frame. We attach call site189 // data to bar, xyz and abc.190 ASSERT_THAT(Records, SizeIs(4));191 192 // Check the memprof record for foo.193 const llvm::GlobalValue::GUID FooId = memprof::getGUID("foo");194 ASSERT_TRUE(Records.contains(FooId));195 const MemProfRecord &Foo = Records[FooId];196 ASSERT_THAT(Foo.AllocSites, SizeIs(1));197 EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U);198 EXPECT_THAT(Foo.AllocSites[0].CallStack[0],199 FrameContains("foo", 5U, 30U, true));200 EXPECT_THAT(Foo.AllocSites[0].CallStack[1],201 FrameContains("bar", 51U, 20U, false));202 EXPECT_THAT(Foo.AllocSites[0].CallStack[2],203 FrameContains("xyz", 5U, 30U, true));204 EXPECT_THAT(Foo.AllocSites[0].CallStack[3],205 FrameContains("abc", 5U, 30U, false));206 EXPECT_TRUE(Foo.CallSites.empty());207 208 // Check the memprof record for bar.209 const llvm::GlobalValue::GUID BarId = memprof::getGUID("bar");210 ASSERT_TRUE(Records.contains(BarId));211 const MemProfRecord &Bar = Records[BarId];212 ASSERT_THAT(Bar.AllocSites, SizeIs(1));213 EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U);214 EXPECT_THAT(Bar.AllocSites[0].CallStack[0],215 FrameContains("foo", 5U, 30U, true));216 EXPECT_THAT(Bar.AllocSites[0].CallStack[1],217 FrameContains("bar", 51U, 20U, false));218 EXPECT_THAT(Bar.AllocSites[0].CallStack[2],219 FrameContains("xyz", 5U, 30U, true));220 EXPECT_THAT(Bar.AllocSites[0].CallStack[3],221 FrameContains("abc", 5U, 30U, false));222 223 EXPECT_THAT(Bar.CallSites,224 ElementsAre(testing::Field(225 &CallSiteInfo::Frames,226 ElementsAre(FrameContains("foo", 5U, 30U, true),227 FrameContains("bar", 51U, 20U, false)))));228 229 // Check the memprof record for xyz.230 const llvm::GlobalValue::GUID XyzId = memprof::getGUID("xyz");231 ASSERT_TRUE(Records.contains(XyzId));232 const MemProfRecord &Xyz = Records[XyzId];233 // Expect the entire frame even though in practice we only need the first234 // entry here.235 EXPECT_THAT(Xyz.CallSites,236 ElementsAre(testing::Field(237 &CallSiteInfo::Frames,238 ElementsAre(FrameContains("xyz", 5U, 30U, true),239 FrameContains("abc", 5U, 30U, false)))));240 241 // Check the memprof record for abc.242 const llvm::GlobalValue::GUID AbcId = memprof::getGUID("abc");243 ASSERT_TRUE(Records.contains(AbcId));244 const MemProfRecord &Abc = Records[AbcId];245 EXPECT_TRUE(Abc.AllocSites.empty());246 EXPECT_THAT(Abc.CallSites,247 ElementsAre(testing::Field(248 &CallSiteInfo::Frames,249 ElementsAre(FrameContains("xyz", 5U, 30U, true),250 FrameContains("abc", 5U, 30U, false)))));251}252 253TEST(MemProf, PortableWrapper) {254 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,255 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,256 /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);257 258 const auto Schema = getFullSchema();259 PortableMemInfoBlock WriteBlock(Info, Schema);260 261 std::string Buffer;262 llvm::raw_string_ostream OS(Buffer);263 WriteBlock.serialize(Schema, OS);264 265 PortableMemInfoBlock ReadBlock(266 Schema, reinterpret_cast<const unsigned char *>(Buffer.data()));267 268 EXPECT_EQ(ReadBlock, WriteBlock);269 // Here we compare directly with the actual counts instead of MemInfoBlock270 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros271 // take a reference to the params, this results in unaligned accesses.272 EXPECT_EQ(1UL, ReadBlock.getAllocCount());273 EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount());274 EXPECT_EQ(3UL, ReadBlock.getAllocCpuId());275}276 277TEST(MemProf, RecordSerializationRoundTripVerion2) {278 const auto Schema = getFullSchema();279 280 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000,281 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3,282 /*dealloc_cpu=*/4, /*Histogram=*/0, /*HistogramSize=*/0);283 284 llvm::SmallVector<CallStackId> CallStackIds = {0x123, 0x456};285 286 llvm::SmallVector<CallStackId> CallSiteIds = {0x333, 0x444};287 288 IndexedMemProfRecord Record;289 for (const auto &CSId : CallStackIds) {290 // Use the same info block for both allocation sites.291 Record.AllocSites.emplace_back(CSId, Info);292 }293 for (auto CSId : CallSiteIds)294 Record.CallSites.push_back(IndexedCallSiteInfo(CSId));295 296 std::string Buffer;297 llvm::raw_string_ostream OS(Buffer);298 Record.serialize(Schema, OS, Version2);299 300 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(301 Schema, reinterpret_cast<const unsigned char *>(Buffer.data()), Version2);302 303 EXPECT_EQ(Record, GotRecord);304}305 306TEST(MemProf, RecordSerializationRoundTripVersion4) {307 const auto Schema = 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<CallStackId> CallStackIds = {0x123, 0x456};314 315 llvm::SmallVector<IndexedCallSiteInfo> CallSites;316 CallSites.push_back(317 IndexedCallSiteInfo(0x333, {0xaaa, 0xbbb})); // CSId with GUIDs318 CallSites.push_back(IndexedCallSiteInfo(0x444)); // CSId without GUIDs319 320 IndexedMemProfRecord Record;321 for (const auto &CSId : CallStackIds) {322 // Use the same info block for both allocation sites.323 Record.AllocSites.emplace_back(CSId, Info);324 }325 Record.CallSites = std::move(CallSites);326 327 std::string Buffer;328 llvm::raw_string_ostream OS(Buffer);329 // Need a dummy map for V4 serialization330 llvm::DenseMap<CallStackId, LinearCallStackId> DummyMap = {331 {0x123, 1}, {0x456, 2}, {0x333, 3}, {0x444, 4}};332 Record.serialize(Schema, OS, Version4, &DummyMap);333 334 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(335 Schema, reinterpret_cast<const unsigned char *>(Buffer.data()), Version4);336 337 // Create the expected record using the linear IDs from the dummy map.338 IndexedMemProfRecord ExpectedRecord;339 for (const auto &CSId : CallStackIds) {340 ExpectedRecord.AllocSites.emplace_back(DummyMap[CSId], Info);341 }342 for (const auto &CSInfo :343 Record.CallSites) { // Use original Record's CallSites to get GUIDs344 ExpectedRecord.CallSites.emplace_back(DummyMap[CSInfo.CSId],345 CSInfo.CalleeGuids);346 }347 348 EXPECT_EQ(ExpectedRecord, GotRecord);349}350 351TEST(MemProf, RecordSerializationRoundTripVersion2HotColdSchema) {352 const auto Schema = getHotColdSchema();353 354 MemInfoBlock Info;355 Info.AllocCount = 11;356 Info.TotalSize = 22;357 Info.TotalLifetime = 33;358 Info.TotalLifetimeAccessDensity = 44;359 360 llvm::SmallVector<CallStackId> CallStackIds = {0x123, 0x456};361 362 llvm::SmallVector<CallStackId> CallSiteIds = {0x333, 0x444};363 364 IndexedMemProfRecord Record;365 for (const auto &CSId : CallStackIds) {366 // Use the same info block for both allocation sites.367 Record.AllocSites.emplace_back(CSId, Info, Schema);368 }369 for (auto CSId : CallSiteIds)370 Record.CallSites.push_back(IndexedCallSiteInfo(CSId));371 372 std::bitset<llvm::to_underlying(Meta::Size)> SchemaBitSet;373 for (auto Id : Schema)374 SchemaBitSet.set(llvm::to_underlying(Id));375 376 // Verify that SchemaBitSet has the fields we expect and nothing else, which377 // we check with count().378 EXPECT_EQ(SchemaBitSet.count(), 4U);379 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::AllocCount)]);380 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalSize)]);381 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalLifetime)]);382 EXPECT_TRUE(383 SchemaBitSet[llvm::to_underlying(Meta::TotalLifetimeAccessDensity)]);384 385 // Verify that Schema has propagated all the way to the Info field in each386 // IndexedAllocationInfo.387 ASSERT_THAT(Record.AllocSites, SizeIs(2));388 EXPECT_EQ(Record.AllocSites[0].Info.getSchema(), SchemaBitSet);389 EXPECT_EQ(Record.AllocSites[1].Info.getSchema(), SchemaBitSet);390 391 std::string Buffer;392 llvm::raw_string_ostream OS(Buffer);393 Record.serialize(Schema, OS, Version2);394 395 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(396 Schema, reinterpret_cast<const unsigned char *>(Buffer.data()), Version2);397 398 // Verify that Schema comes back correctly after deserialization. Technically,399 // the comparison between Record and GotRecord below includes the comparison400 // of their Schemas, but we'll verify the Schemas on our own.401 ASSERT_THAT(GotRecord.AllocSites, SizeIs(2));402 EXPECT_EQ(GotRecord.AllocSites[0].Info.getSchema(), SchemaBitSet);403 EXPECT_EQ(GotRecord.AllocSites[1].Info.getSchema(), SchemaBitSet);404 405 EXPECT_EQ(Record, GotRecord);406}407 408TEST(MemProf, RecordSerializationRoundTripVersion4HotColdSchema) {409 const auto Schema = getHotColdSchema();410 411 MemInfoBlock Info;412 Info.AllocCount = 11;413 Info.TotalSize = 22;414 Info.TotalLifetime = 33;415 Info.TotalLifetimeAccessDensity = 44;416 417 llvm::SmallVector<CallStackId> CallStackIds = {0x123, 0x456};418 419 llvm::SmallVector<CallStackId> CallSiteIds = {0x333, 0x444};420 421 IndexedMemProfRecord Record;422 for (const auto &CSId : CallStackIds) {423 // Use the same info block for both allocation sites.424 Record.AllocSites.emplace_back(CSId, Info, Schema);425 }426 for (auto CSId : CallSiteIds)427 Record.CallSites.push_back(IndexedCallSiteInfo(CSId));428 429 std::bitset<llvm::to_underlying(Meta::Size)> SchemaBitSet;430 for (auto Id : Schema)431 SchemaBitSet.set(llvm::to_underlying(Id));432 433 // Verify that SchemaBitSet has the fields we expect and nothing else, which434 // we check with count().435 EXPECT_EQ(SchemaBitSet.count(), 4U);436 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::AllocCount)]);437 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalSize)]);438 EXPECT_TRUE(SchemaBitSet[llvm::to_underlying(Meta::TotalLifetime)]);439 EXPECT_TRUE(440 SchemaBitSet[llvm::to_underlying(Meta::TotalLifetimeAccessDensity)]);441 442 // Verify that Schema has propagated all the way to the Info field in each443 // IndexedAllocationInfo.444 ASSERT_THAT(Record.AllocSites, SizeIs(2));445 EXPECT_EQ(Record.AllocSites[0].Info.getSchema(), SchemaBitSet);446 EXPECT_EQ(Record.AllocSites[1].Info.getSchema(), SchemaBitSet);447 448 std::string Buffer;449 llvm::raw_string_ostream OS(Buffer);450 // Need a dummy map for V4 serialization451 llvm::DenseMap<CallStackId, LinearCallStackId> DummyMap = {452 {0x123, 1}, {0x456, 2}, {0x333, 3}, {0x444, 4}};453 Record.serialize(Schema, OS, Version4, &DummyMap);454 455 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize(456 Schema, reinterpret_cast<const unsigned char *>(Buffer.data()), Version4);457 458 // Verify that Schema comes back correctly after deserialization. Technically,459 // the comparison between Record and GotRecord below includes the comparison460 // of their Schemas, but we'll verify the Schemas on our own.461 ASSERT_THAT(GotRecord.AllocSites, SizeIs(2));462 EXPECT_EQ(GotRecord.AllocSites[0].Info.getSchema(), SchemaBitSet);463 EXPECT_EQ(GotRecord.AllocSites[1].Info.getSchema(), SchemaBitSet);464 465 // Create the expected record using the linear IDs from the dummy map.466 IndexedMemProfRecord ExpectedRecord;467 for (const auto &CSId : CallStackIds) {468 ExpectedRecord.AllocSites.emplace_back(DummyMap[CSId], Info, Schema);469 }470 for (const auto &CSId : CallSiteIds) {471 ExpectedRecord.CallSites.emplace_back(DummyMap[CSId]);472 }473 474 EXPECT_EQ(ExpectedRecord, GotRecord);475}476 477TEST(MemProf, SymbolizationFilter) {478 auto Symbolizer = std::make_unique<MockSymbolizer>();479 480 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000},481 specifier(), false))482 .Times(1) // once since we don't lookup invalid PCs repeatedly.483 .WillRepeatedly(Return(makeInliningInfo({484 {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"},485 })));486 487 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000},488 specifier(), false))489 .Times(1) // once since we don't lookup invalid PCs repeatedly.490 .WillRepeatedly(Return(makeInliningInfo({491 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"},492 })));493 494 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000},495 specifier(), false))496 .Times(1) // once since we don't lookup invalid PCs repeatedly.497 .WillRepeatedly(Return(makeInliningInfo({498 {DILineInfo::BadString, 0, 0, 0},499 })));500 501 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000},502 specifier(), false))503 .Times(1)504 .WillRepeatedly(Return(makeInliningInfo({505 {"foo", 10, 5, 30, "memprof/memprof_test_file.cpp"},506 })));507 508 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x5000},509 specifier(), false))510 .Times(1)511 .WillRepeatedly(Return(makeInliningInfo({512 // Depending on how the runtime was compiled, only the filename513 // may be present in the debug information.514 {"malloc", 70, 57, 3, "memprof_malloc_linux.cpp"},515 })));516 517 CallStackMap CSM;518 CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000};519 // This entry should be dropped since all PCs are either not520 // symbolizable or belong to the runtime.521 CSM[0x2] = {0x1000, 0x2000, 0x5000};522 523 llvm::MapVector<uint64_t, MemInfoBlock> Prof;524 Prof[0x1].AllocCount = 1;525 Prof[0x2].AllocCount = 1;526 527 auto Seg = makeSegments();528 529 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM);530 531 llvm::SmallVector<MemProfRecord, 1> Records;532 for (const auto &KeyRecordPair : Reader)533 Records.push_back(KeyRecordPair.second);534 535 ASSERT_THAT(Records, SizeIs(1));536 ASSERT_THAT(Records[0].AllocSites, SizeIs(1));537 EXPECT_THAT(Records[0].AllocSites[0].CallStack,538 ElementsAre(FrameContains("foo", 5U, 30U, false)));539}540 541TEST(MemProf, BaseMemProfReader) {542 IndexedMemProfData MemProfData;543 Frame F1(/*Hash=*/memprof::getGUID("foo"), /*LineOffset=*/20,544 /*Column=*/5, /*IsInlineFrame=*/true);545 Frame F2(/*Hash=*/memprof::getGUID("bar"), /*LineOffset=*/10,546 /*Column=*/2, /*IsInlineFrame=*/false);547 auto F1Id = MemProfData.addFrame(F1);548 auto F2Id = MemProfData.addFrame(F2);549 550 llvm::SmallVector<FrameId> CallStack{F1Id, F2Id};551 CallStackId CSId = MemProfData.addCallStack(std::move(CallStack));552 553 IndexedMemProfRecord FakeRecord;554 MemInfoBlock Block;555 Block.AllocCount = 1U, Block.TotalAccessDensity = 4,556 Block.TotalLifetime = 200001;557 FakeRecord.AllocSites.emplace_back(/*CSId=*/CSId, /*MB=*/Block);558 MemProfData.Records.try_emplace(0x1234, std::move(FakeRecord));559 560 MemProfReader Reader(std::move(MemProfData));561 562 llvm::SmallVector<MemProfRecord, 1> Records;563 for (const auto &KeyRecordPair : Reader)564 Records.push_back(KeyRecordPair.second);565 566 ASSERT_THAT(Records, SizeIs(1));567 ASSERT_THAT(Records[0].AllocSites, SizeIs(1));568 EXPECT_THAT(Records[0].AllocSites[0].CallStack,569 ElementsAre(FrameContains("foo", 20U, 5U, true),570 FrameContains("bar", 10U, 2U, false)));571}572 573TEST(MemProf, BaseMemProfReaderWithCSIdMap) {574 IndexedMemProfData MemProfData;575 Frame F1(/*Hash=*/memprof::getGUID("foo"), /*LineOffset=*/20,576 /*Column=*/5, /*IsInlineFrame=*/true);577 Frame F2(/*Hash=*/memprof::getGUID("bar"), /*LineOffset=*/10,578 /*Column=*/2, /*IsInlineFrame=*/false);579 auto F1Id = MemProfData.addFrame(F1);580 auto F2Id = MemProfData.addFrame(F2);581 582 llvm::SmallVector<FrameId> CallStack = {F1Id, F2Id};583 auto CSId = MemProfData.addCallStack(std::move(CallStack));584 585 IndexedMemProfRecord FakeRecord;586 MemInfoBlock Block;587 Block.AllocCount = 1U, Block.TotalAccessDensity = 4,588 Block.TotalLifetime = 200001;589 FakeRecord.AllocSites.emplace_back(/*CSId=*/CSId, /*MB=*/Block);590 MemProfData.Records.try_emplace(0x1234, std::move(FakeRecord));591 592 MemProfReader Reader(std::move(MemProfData));593 594 llvm::SmallVector<MemProfRecord, 1> Records;595 for (const auto &KeyRecordPair : Reader)596 Records.push_back(KeyRecordPair.second);597 598 ASSERT_THAT(Records, SizeIs(1));599 ASSERT_THAT(Records[0].AllocSites, SizeIs(1));600 EXPECT_THAT(Records[0].AllocSites[0].CallStack,601 ElementsAre(FrameContains("foo", 20U, 5U, true),602 FrameContains("bar", 10U, 2U, false)));603}604 605TEST(MemProf, IndexedMemProfRecordToMemProfRecord) {606 // Verify that MemProfRecord can be constructed from IndexedMemProfRecord with607 // CallStackIds only.608 609 IndexedMemProfData MemProfData;610 Frame F1(1, 0, 0, false);611 Frame F2(2, 0, 0, false);612 Frame F3(3, 0, 0, false);613 Frame F4(4, 0, 0, false);614 auto F1Id = MemProfData.addFrame(F1);615 auto F2Id = MemProfData.addFrame(F2);616 auto F3Id = MemProfData.addFrame(F3);617 auto F4Id = MemProfData.addFrame(F4);618 619 llvm::SmallVector<FrameId> CS1 = {F1Id, F2Id};620 llvm::SmallVector<FrameId> CS2 = {F1Id, F3Id};621 llvm::SmallVector<FrameId> CS3 = {F2Id, F3Id};622 llvm::SmallVector<FrameId> CS4 = {F2Id, F4Id};623 auto CS1Id = MemProfData.addCallStack(std::move(CS1));624 auto CS2Id = MemProfData.addCallStack(std::move(CS2));625 auto CS3Id = MemProfData.addCallStack(std::move(CS3));626 auto CS4Id = MemProfData.addCallStack(std::move(CS4));627 628 IndexedMemProfRecord IndexedRecord;629 IndexedAllocationInfo AI;630 AI.CSId = CS1Id;631 IndexedRecord.AllocSites.push_back(AI);632 AI.CSId = CS2Id;633 IndexedRecord.AllocSites.push_back(AI);634 IndexedRecord.CallSites.push_back(IndexedCallSiteInfo(CS3Id));635 IndexedRecord.CallSites.push_back(IndexedCallSiteInfo(CS4Id));636 637 IndexedCallstackIdConverter CSIdConv(MemProfData);638 639 MemProfRecord Record = IndexedRecord.toMemProfRecord(CSIdConv);640 641 // Make sure that all lookups are successful.642 ASSERT_EQ(CSIdConv.FrameIdConv.LastUnmappedId, std::nullopt);643 ASSERT_EQ(CSIdConv.CSIdConv.LastUnmappedId, std::nullopt);644 645 // Verify the contents of Record.646 ASSERT_THAT(Record.AllocSites, SizeIs(2));647 EXPECT_THAT(Record.AllocSites[0].CallStack, ElementsAre(F1, F2));648 EXPECT_THAT(Record.AllocSites[1].CallStack, ElementsAre(F1, F3));649 ASSERT_THAT(Record.CallSites, SizeIs(2));650 EXPECT_THAT(Record.CallSites[0].Frames, ElementsAre(F2, F3));651 EXPECT_THAT(Record.CallSites[1].Frames, ElementsAre(F2, F4));652}653 654// Populate those fields returned by getHotColdSchema.655MemInfoBlock makePartialMIB() {656 MemInfoBlock MIB;657 MIB.AllocCount = 1;658 MIB.TotalSize = 5;659 MIB.TotalLifetime = 10;660 MIB.TotalLifetimeAccessDensity = 23;661 return MIB;662}663 664TEST(MemProf, MissingCallStackId) {665 // Use a non-existent CallStackId to trigger a mapping error in666 // toMemProfRecord.667 IndexedAllocationInfo AI(0xdeadbeefU, makePartialMIB(), getHotColdSchema());668 669 IndexedMemProfRecord IndexedMR;670 IndexedMR.AllocSites.push_back(AI);671 672 // Create empty maps.673 IndexedMemProfData MemProfData;674 IndexedCallstackIdConverter CSIdConv(MemProfData);675 676 // We are only interested in errors, not the return value.677 (void)IndexedMR.toMemProfRecord(CSIdConv);678 679 ASSERT_TRUE(CSIdConv.CSIdConv.LastUnmappedId.has_value());680 EXPECT_EQ(*CSIdConv.CSIdConv.LastUnmappedId, 0xdeadbeefU);681 EXPECT_EQ(CSIdConv.FrameIdConv.LastUnmappedId, std::nullopt);682}683 684TEST(MemProf, MissingFrameId) {685 // An empty Frame map to trigger a mapping error.686 IndexedMemProfData MemProfData;687 auto CSId = MemProfData.addCallStack(SmallVector<FrameId>{2, 3});688 689 IndexedMemProfRecord IndexedMR;690 IndexedMR.AllocSites.emplace_back(CSId, makePartialMIB(), getHotColdSchema());691 692 IndexedCallstackIdConverter CSIdConv(MemProfData);693 694 // We are only interested in errors, not the return value.695 (void)IndexedMR.toMemProfRecord(CSIdConv);696 697 EXPECT_EQ(CSIdConv.CSIdConv.LastUnmappedId, std::nullopt);698 ASSERT_TRUE(CSIdConv.FrameIdConv.LastUnmappedId.has_value());699 EXPECT_EQ(*CSIdConv.FrameIdConv.LastUnmappedId, 3U);700}701 702// Verify CallStackRadixTreeBuilder can handle empty inputs.703TEST(MemProf, RadixTreeBuilderEmpty) {704 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes;705 IndexedMemProfData MemProfData;706 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =707 computeFrameHistogram<FrameId>(MemProfData.CallStacks);708 CallStackRadixTreeBuilder<FrameId> Builder;709 Builder.build(std::move(MemProfData.CallStacks), &MemProfFrameIndexes,710 FrameHistogram);711 ASSERT_THAT(Builder.getRadixArray(), IsEmpty());712 const auto Mappings = Builder.takeCallStackPos();713 ASSERT_THAT(Mappings, IsEmpty());714}715 716// Verify CallStackRadixTreeBuilder can handle one trivial call stack.717TEST(MemProf, RadixTreeBuilderOne) {718 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes = {719 {11, 1}, {12, 2}, {13, 3}};720 llvm::SmallVector<FrameId> CS1 = {13, 12, 11};721 IndexedMemProfData MemProfData;722 auto CS1Id = MemProfData.addCallStack(std::move(CS1));723 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =724 computeFrameHistogram<FrameId>(MemProfData.CallStacks);725 CallStackRadixTreeBuilder<FrameId> Builder;726 Builder.build(std::move(MemProfData.CallStacks), &MemProfFrameIndexes,727 FrameHistogram);728 EXPECT_THAT(Builder.getRadixArray(),729 ElementsAre(3U, // Size of CS1,730 3U, // MemProfFrameIndexes[13]731 2U, // MemProfFrameIndexes[12]732 1U // MemProfFrameIndexes[11]733 ));734 const auto Mappings = Builder.takeCallStackPos();735 EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(CS1Id, 0U)));736}737 738// Verify CallStackRadixTreeBuilder can form a link between two call stacks.739TEST(MemProf, RadixTreeBuilderTwo) {740 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes = {741 {11, 1}, {12, 2}, {13, 3}};742 llvm::SmallVector<FrameId> CS1 = {12, 11};743 llvm::SmallVector<FrameId> CS2 = {13, 12, 11};744 IndexedMemProfData MemProfData;745 auto CS1Id = MemProfData.addCallStack(std::move(CS1));746 auto CS2Id = MemProfData.addCallStack(std::move(CS2));747 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =748 computeFrameHistogram<FrameId>(MemProfData.CallStacks);749 CallStackRadixTreeBuilder<FrameId> Builder;750 Builder.build(std::move(MemProfData.CallStacks), &MemProfFrameIndexes,751 FrameHistogram);752 EXPECT_THAT(Builder.getRadixArray(),753 ElementsAre(2U, // Size of CS1754 static_cast<uint32_t>(-3), // Jump 3 steps755 3U, // Size of CS2756 3U, // MemProfFrameIndexes[13]757 2U, // MemProfFrameIndexes[12]758 1U // MemProfFrameIndexes[11]759 ));760 const auto Mappings = Builder.takeCallStackPos();761 EXPECT_THAT(Mappings, UnorderedElementsAre(Pair(CS1Id, 0U), Pair(CS2Id, 2U)));762}763 764// Verify CallStackRadixTreeBuilder can form a jump to a prefix that itself has765// another jump to another prefix.766TEST(MemProf, RadixTreeBuilderSuccessiveJumps) {767 llvm::DenseMap<FrameId, LinearFrameId> MemProfFrameIndexes = {768 {11, 1}, {12, 2}, {13, 3}, {14, 4}, {15, 5}, {16, 6}, {17, 7}, {18, 8},769 };770 llvm::SmallVector<FrameId> CS1 = {14, 13, 12, 11};771 llvm::SmallVector<FrameId> CS2 = {15, 13, 12, 11};772 llvm::SmallVector<FrameId> CS3 = {17, 16, 12, 11};773 llvm::SmallVector<FrameId> CS4 = {18, 16, 12, 11};774 IndexedMemProfData MemProfData;775 auto CS1Id = MemProfData.addCallStack(std::move(CS1));776 auto CS2Id = MemProfData.addCallStack(std::move(CS2));777 auto CS3Id = MemProfData.addCallStack(std::move(CS3));778 auto CS4Id = MemProfData.addCallStack(std::move(CS4));779 llvm::DenseMap<FrameId, FrameStat> FrameHistogram =780 computeFrameHistogram<FrameId>(MemProfData.CallStacks);781 CallStackRadixTreeBuilder<FrameId> Builder;782 Builder.build(std::move(MemProfData.CallStacks), &MemProfFrameIndexes,783 FrameHistogram);784 EXPECT_THAT(Builder.getRadixArray(),785 ElementsAre(4U, // Size of CS1786 4U, // MemProfFrameIndexes[14]787 static_cast<uint32_t>(-3), // Jump 3 steps788 4U, // Size of CS2789 5U, // MemProfFrameIndexes[15]790 3U, // MemProfFrameIndexes[13]791 static_cast<uint32_t>(-7), // Jump 7 steps792 4U, // Size of CS3793 7U, // MemProfFrameIndexes[17]794 static_cast<uint32_t>(-3), // Jump 3 steps795 4U, // Size of CS4796 8U, // MemProfFrameIndexes[18]797 6U, // MemProfFrameIndexes[16]798 2U, // MemProfFrameIndexes[12]799 1U // MemProfFrameIndexes[11]800 ));801 const auto Mappings = Builder.takeCallStackPos();802 EXPECT_THAT(Mappings,803 UnorderedElementsAre(Pair(CS1Id, 0U), Pair(CS2Id, 3U),804 Pair(CS3Id, 7U), Pair(CS4Id, 10U)));805}806 807// Verify that we can parse YAML and retrieve IndexedMemProfData as expected.808TEST(MemProf, YAMLParser) {809 StringRef YAMLData = R"YAML(810---811HeapProfileRecords:812- GUID: 0xdeadbeef12345678813 AllocSites:814 - Callstack:815 - {Function: 0x100, LineOffset: 11, Column: 10, IsInlineFrame: true}816 - {Function: 0x200, LineOffset: 22, Column: 20, IsInlineFrame: false}817 MemInfoBlock:818 AllocCount: 777819 TotalSize: 888820 - Callstack:821 - {Function: 0x300, LineOffset: 33, Column: 30, IsInlineFrame: false}822 - {Function: 0x400, LineOffset: 44, Column: 40, IsInlineFrame: true}823 MemInfoBlock:824 AllocCount: 666825 TotalSize: 555826 CallSites:827 - Frames:828 - {Function: 0x500, LineOffset: 55, Column: 50, IsInlineFrame: true}829 - {Function: 0x600, LineOffset: 66, Column: 60, IsInlineFrame: false}830 CalleeGuids: [0x1000, 0x2000]831 - Frames:832 - {Function: 0x700, LineOffset: 77, Column: 70, IsInlineFrame: true}833 - {Function: 0x800, LineOffset: 88, Column: 80, IsInlineFrame: false}834 CalleeGuids: [0x3000]835)YAML";836 837 YAMLMemProfReader YAMLReader;838 YAMLReader.parse(YAMLData);839 IndexedMemProfData MemProfData = YAMLReader.takeMemProfData();840 841 // Verify the entire contents of MemProfData.Records.842 ASSERT_THAT(MemProfData.Records, SizeIs(1));843 const auto &[GUID, IndexedRecord] = MemProfData.Records.front();844 EXPECT_EQ(GUID, 0xdeadbeef12345678ULL);845 846 IndexedCallstackIdConverter CSIdConv(MemProfData);847 MemProfRecord Record = IndexedRecord.toMemProfRecord(CSIdConv);848 849 ASSERT_THAT(Record.AllocSites, SizeIs(2));850 EXPECT_THAT(851 Record.AllocSites[0].CallStack,852 ElementsAre(Frame(0x100, 11, 10, true), Frame(0x200, 22, 20, false)));853 EXPECT_EQ(Record.AllocSites[0].Info.getAllocCount(), 777U);854 EXPECT_EQ(Record.AllocSites[0].Info.getTotalSize(), 888U);855 EXPECT_THAT(856 Record.AllocSites[1].CallStack,857 ElementsAre(Frame(0x300, 33, 30, false), Frame(0x400, 44, 40, true)));858 EXPECT_EQ(Record.AllocSites[1].Info.getAllocCount(), 666U);859 EXPECT_EQ(Record.AllocSites[1].Info.getTotalSize(), 555U);860 EXPECT_THAT(861 Record.CallSites,862 ElementsAre(863 AllOf(testing::Field(&CallSiteInfo::Frames,864 ElementsAre(Frame(0x500, 55, 50, true),865 Frame(0x600, 66, 60, false))),866 testing::Field(&CallSiteInfo::CalleeGuids,867 ElementsAre(0x1000, 0x2000))),868 AllOf(testing::Field(&CallSiteInfo::Frames,869 ElementsAre(Frame(0x700, 77, 70, true),870 Frame(0x800, 88, 80, false))),871 testing::Field(&CallSiteInfo::CalleeGuids,872 ElementsAre(0x3000)))));873}874 875// Verify that the YAML parser accepts a GUID expressed as a function name.876TEST(MemProf, YAMLParserGUID) {877 StringRef YAMLData = R"YAML(878---879HeapProfileRecords:880- GUID: _Z3fooi881 AllocSites:882 - Callstack:883 - {Function: 0x100, LineOffset: 11, Column: 10, IsInlineFrame: true}884 MemInfoBlock: {}885 CallSites: []886)YAML";887 888 YAMLMemProfReader YAMLReader;889 YAMLReader.parse(YAMLData);890 IndexedMemProfData MemProfData = YAMLReader.takeMemProfData();891 892 // Verify the entire contents of MemProfData.Records.893 ASSERT_THAT(MemProfData.Records, SizeIs(1));894 const auto &[GUID, IndexedRecord] = MemProfData.Records.front();895 EXPECT_EQ(GUID, memprof::getGUID("_Z3fooi"));896 897 IndexedCallstackIdConverter CSIdConv(MemProfData);898 MemProfRecord Record = IndexedRecord.toMemProfRecord(CSIdConv);899 900 ASSERT_THAT(Record.AllocSites, SizeIs(1));901 EXPECT_THAT(Record.AllocSites[0].CallStack,902 ElementsAre(Frame(0x100, 11, 10, true)));903 EXPECT_THAT(Record.CallSites, IsEmpty());904}905 906template <typename T> std::string serializeInYAML(T &Val) {907 std::string Out;908 llvm::raw_string_ostream OS(Out);909 llvm::yaml::Output Yout(OS);910 Yout << Val;911 return Out;912}913 914TEST(MemProf, YAMLWriterFrame) {915 Frame F(0x0123456789abcdefULL, 22, 33, true);916 917 std::string Out = serializeInYAML(F);918 EXPECT_EQ(Out, R"YAML(---919{ Function: 0x123456789abcdef, LineOffset: 22, Column: 33, IsInlineFrame: true }920...921)YAML");922}923 924TEST(MemProf, YAMLWriterMIB) {925 MemInfoBlock MIB;926 MIB.AllocCount = 111;927 MIB.TotalSize = 222;928 MIB.TotalLifetime = 333;929 MIB.TotalLifetimeAccessDensity = 444;930 PortableMemInfoBlock PMIB(MIB, getHotColdSchema());931 932 std::string Out = serializeInYAML(PMIB);933 EXPECT_EQ(Out, R"YAML(---934AllocCount: 111935TotalSize: 222936TotalLifetime: 333937TotalLifetimeAccessDensity: 444938...939)YAML");940}941 942// Test getAllocType helper.943// Basic checks on the allocation type for values just above and below944// the thresholds.945TEST(MemProf, GetAllocType) {946 const uint64_t AllocCount = 2;947 // To be cold we require that948 // ((float)TotalLifetimeAccessDensity) / AllocCount / 100 <949 // MemProfLifetimeAccessDensityColdThreshold950 // so compute the ColdTotalLifetimeAccessDensityThreshold at the threshold.951 const uint64_t ColdTotalLifetimeAccessDensityThreshold =952 (uint64_t)(MemProfLifetimeAccessDensityColdThreshold * AllocCount * 100);953 // To be cold we require that954 // ((float)TotalLifetime) / AllocCount >=955 // MemProfAveLifetimeColdThreshold * 1000956 // so compute the TotalLifetime right at the threshold.957 const uint64_t ColdTotalLifetimeThreshold =958 MemProfAveLifetimeColdThreshold * AllocCount * 1000;959 // To be hot we require that960 // ((float)TotalLifetimeAccessDensity) / AllocCount / 100 >961 // MemProfMinAveLifetimeAccessDensityHotThreshold962 // so compute the HotTotalLifetimeAccessDensityThreshold at the threshold.963 const uint64_t HotTotalLifetimeAccessDensityThreshold =964 (uint64_t)(MemProfMinAveLifetimeAccessDensityHotThreshold * AllocCount *965 100);966 967 // Make sure the option for detecting hot allocations is set.968 bool OrigMemProfUseHotHints = MemProfUseHotHints;969 MemProfUseHotHints = true;970 971 // Test Hot972 // More accesses per byte per sec than hot threshold is hot.973 EXPECT_EQ(getAllocType(HotTotalLifetimeAccessDensityThreshold + 1, AllocCount,974 ColdTotalLifetimeThreshold + 1),975 AllocationType::Hot);976 977 // Restore original option value.978 MemProfUseHotHints = OrigMemProfUseHotHints;979 980 // Without MemProfUseHotHints (default) we should treat simply as NotCold.981 EXPECT_EQ(getAllocType(HotTotalLifetimeAccessDensityThreshold + 1, AllocCount,982 ColdTotalLifetimeThreshold + 1),983 AllocationType::NotCold);984 985 // Test Cold986 // Long lived with less accesses per byte per sec than cold threshold is cold.987 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold - 1,988 AllocCount, ColdTotalLifetimeThreshold + 1),989 AllocationType::Cold);990 991 // Test NotCold992 // Long lived with more accesses per byte per sec than cold threshold is not993 // cold.994 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold + 1,995 AllocCount, ColdTotalLifetimeThreshold + 1),996 AllocationType::NotCold);997 // Short lived with more accesses per byte per sec than cold threshold is not998 // cold.999 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold + 1,1000 AllocCount, ColdTotalLifetimeThreshold - 1),1001 AllocationType::NotCold);1002 // Short lived with less accesses per byte per sec than cold threshold is not1003 // cold.1004 EXPECT_EQ(getAllocType(ColdTotalLifetimeAccessDensityThreshold - 1,1005 AllocCount, ColdTotalLifetimeThreshold - 1),1006 AllocationType::NotCold);1007}1008 1009} // namespace1010} // namespace memprof1011} // namespace llvm1012