brintos

brintos / llvm-project-archived public Read only

0
0
Text · 38.6 KiB · 6ea951e Raw
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