1 #include "llvm/ProfileData/MemProf.h" 2 #include "llvm/ADT/DenseMap.h" 3 #include "llvm/ADT/MapVector.h" 4 #include "llvm/DebugInfo/DIContext.h" 5 #include "llvm/DebugInfo/Symbolize/SymbolizableModule.h" 6 #include "llvm/IR/Function.h" 7 #include "llvm/IR/Value.h" 8 #include "llvm/Object/ObjectFile.h" 9 #include "llvm/ProfileData/InstrProf.h" 10 #include "llvm/ProfileData/MemProfData.inc" 11 #include "llvm/ProfileData/RawMemProfReader.h" 12 #include "llvm/Support/Error.h" 13 #include "llvm/Support/MD5.h" 14 #include "llvm/Support/raw_ostream.h" 15 #include "gmock/gmock.h" 16 #include "gtest/gtest.h" 17 18 #include <initializer_list> 19 20 namespace { 21 22 using ::llvm::DIGlobal; 23 using ::llvm::DIInliningInfo; 24 using ::llvm::DILineInfo; 25 using ::llvm::DILineInfoSpecifier; 26 using ::llvm::DILocal; 27 using ::llvm::memprof::CallStackMap; 28 using ::llvm::memprof::Frame; 29 using ::llvm::memprof::FrameId; 30 using ::llvm::memprof::IndexedMemProfRecord; 31 using ::llvm::memprof::MemInfoBlock; 32 using ::llvm::memprof::MemProfRecord; 33 using ::llvm::memprof::MemProfSchema; 34 using ::llvm::memprof::Meta; 35 using ::llvm::memprof::PortableMemInfoBlock; 36 using ::llvm::memprof::RawMemProfReader; 37 using ::llvm::memprof::SegmentEntry; 38 using ::llvm::object::SectionedAddress; 39 using ::llvm::symbolize::SymbolizableModule; 40 using ::testing::Return; 41 42 class MockSymbolizer : public SymbolizableModule { 43 public: 44 MOCK_CONST_METHOD3(symbolizeInlinedCode, 45 DIInliningInfo(SectionedAddress, DILineInfoSpecifier, 46 bool)); 47 // Most of the methods in the interface are unused. We only mock the 48 // method that we expect to be called from the memprof reader. 49 virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier, 50 bool) const { 51 llvm_unreachable("unused"); 52 } 53 virtual DIGlobal symbolizeData(SectionedAddress) const { 54 llvm_unreachable("unused"); 55 } 56 virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const { 57 llvm_unreachable("unused"); 58 } 59 virtual bool isWin32Module() const { llvm_unreachable("unused"); } 60 virtual uint64_t getModulePreferredBase() const { 61 llvm_unreachable("unused"); 62 } 63 }; 64 65 struct MockInfo { 66 std::string FunctionName; 67 uint32_t Line; 68 uint32_t StartLine; 69 uint32_t Column; 70 std::string FileName = "valid/path.cc"; 71 }; 72 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) { 73 DIInliningInfo Result; 74 for (const auto &Item : MockFrames) { 75 DILineInfo Frame; 76 Frame.FunctionName = Item.FunctionName; 77 Frame.Line = Item.Line; 78 Frame.StartLine = Item.StartLine; 79 Frame.Column = Item.Column; 80 Frame.FileName = Item.FileName; 81 Result.addFrame(Frame); 82 } 83 return Result; 84 } 85 86 llvm::SmallVector<SegmentEntry, 4> makeSegments() { 87 llvm::SmallVector<SegmentEntry, 4> Result; 88 // Mimic an entry for a non position independent executable. 89 Result.emplace_back(0x0, 0x40000, 0x0); 90 return Result; 91 } 92 93 const DILineInfoSpecifier specifier() { 94 return DILineInfoSpecifier( 95 DILineInfoSpecifier::FileLineInfoKind::RawValue, 96 DILineInfoSpecifier::FunctionNameKind::LinkageName); 97 } 98 99 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") { 100 const Frame &F = arg; 101 102 const uint64_t ExpectedHash = llvm::Function::getGUID(FunctionName); 103 if (F.Function != ExpectedHash) { 104 *result_listener << "Hash mismatch"; 105 return false; 106 } 107 if (F.SymbolName.hasValue() && F.SymbolName.getValue() != FunctionName) { 108 *result_listener << "SymbolName mismatch\nWant: " << FunctionName 109 << "\nGot: " << F.SymbolName.getValue(); 110 return false; 111 } 112 if (F.LineOffset == LineOffset && F.Column == Column && 113 F.IsInlineFrame == Inline) { 114 return true; 115 } 116 *result_listener << "LineOffset, Column or Inline mismatch"; 117 return false; 118 } 119 120 MemProfSchema getFullSchema() { 121 MemProfSchema Schema; 122 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name); 123 #include "llvm/ProfileData/MIBEntryDef.inc" 124 #undef MIBEntryDef 125 return Schema; 126 } 127 128 TEST(MemProf, FillsValue) { 129 std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer()); 130 131 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000}, 132 specifier(), false)) 133 .Times(1) // Only once since we remember invalid PCs. 134 .WillRepeatedly(Return(makeInliningInfo({ 135 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"}, 136 }))); 137 138 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000}, 139 specifier(), false)) 140 .Times(1) // Only once since we cache the result for future lookups. 141 .WillRepeatedly(Return(makeInliningInfo({ 142 {"foo", 10, 5, 30}, 143 {"bar", 201, 150, 20}, 144 }))); 145 146 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000}, 147 specifier(), false)) 148 .Times(1) 149 .WillRepeatedly(Return(makeInliningInfo({ 150 {"xyz", 10, 5, 30}, 151 {"abc", 10, 5, 30}, 152 }))); 153 154 CallStackMap CSM; 155 CSM[0x1] = {0x1000, 0x2000, 0x3000}; 156 157 llvm::MapVector<uint64_t, MemInfoBlock> Prof; 158 Prof[0x1].AllocCount = 1; 159 160 auto Seg = makeSegments(); 161 162 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM, 163 /*KeepName=*/true); 164 165 llvm::DenseMap<llvm::GlobalValue::GUID, MemProfRecord> Records; 166 for (const auto &Pair : Reader) { 167 Records.insert({Pair.first, Pair.second}); 168 } 169 170 // Mock program psuedocode and expected memprof record contents. 171 // 172 // AllocSite CallSite 173 // inline foo() { new(); } Y N 174 // bar() { foo(); } Y Y 175 // inline xyz() { bar(); } N Y 176 // abc() { xyz(); } N Y 177 178 // We expect 4 records. We attach alloc site data to foo and bar, i.e. 179 // all frames bottom up until we find a non-inline frame. We attach call site 180 // data to bar, xyz and abc. 181 ASSERT_EQ(Records.size(), 4U); 182 183 // Check the memprof record for foo. 184 const llvm::GlobalValue::GUID FooId = IndexedMemProfRecord::getGUID("foo"); 185 ASSERT_EQ(Records.count(FooId), 1U); 186 const MemProfRecord &Foo = Records[FooId]; 187 ASSERT_EQ(Foo.AllocSites.size(), 1U); 188 EXPECT_EQ(Foo.AllocSites[0].Info.getAllocCount(), 1U); 189 EXPECT_THAT(Foo.AllocSites[0].CallStack[0], 190 FrameContains("foo", 5U, 30U, true)); 191 EXPECT_THAT(Foo.AllocSites[0].CallStack[1], 192 FrameContains("bar", 51U, 20U, false)); 193 EXPECT_THAT(Foo.AllocSites[0].CallStack[2], 194 FrameContains("xyz", 5U, 30U, true)); 195 EXPECT_THAT(Foo.AllocSites[0].CallStack[3], 196 FrameContains("abc", 5U, 30U, false)); 197 EXPECT_TRUE(Foo.CallSites.empty()); 198 199 // Check the memprof record for bar. 200 const llvm::GlobalValue::GUID BarId = IndexedMemProfRecord::getGUID("bar"); 201 ASSERT_EQ(Records.count(BarId), 1U); 202 const MemProfRecord &Bar = Records[BarId]; 203 ASSERT_EQ(Bar.AllocSites.size(), 1U); 204 EXPECT_EQ(Bar.AllocSites[0].Info.getAllocCount(), 1U); 205 EXPECT_THAT(Bar.AllocSites[0].CallStack[0], 206 FrameContains("foo", 5U, 30U, true)); 207 EXPECT_THAT(Bar.AllocSites[0].CallStack[1], 208 FrameContains("bar", 51U, 20U, false)); 209 EXPECT_THAT(Bar.AllocSites[0].CallStack[2], 210 FrameContains("xyz", 5U, 30U, true)); 211 EXPECT_THAT(Bar.AllocSites[0].CallStack[3], 212 FrameContains("abc", 5U, 30U, false)); 213 214 ASSERT_EQ(Bar.CallSites.size(), 1U); 215 ASSERT_EQ(Bar.CallSites[0].size(), 2U); 216 EXPECT_THAT(Bar.CallSites[0][0], FrameContains("foo", 5U, 30U, true)); 217 EXPECT_THAT(Bar.CallSites[0][1], FrameContains("bar", 51U, 20U, false)); 218 219 // Check the memprof record for xyz. 220 const llvm::GlobalValue::GUID XyzId = IndexedMemProfRecord::getGUID("xyz"); 221 ASSERT_EQ(Records.count(XyzId), 1U); 222 const MemProfRecord &Xyz = Records[XyzId]; 223 ASSERT_EQ(Xyz.CallSites.size(), 1U); 224 ASSERT_EQ(Xyz.CallSites[0].size(), 2U); 225 // Expect the entire frame even though in practice we only need the first 226 // entry here. 227 EXPECT_THAT(Xyz.CallSites[0][0], FrameContains("xyz", 5U, 30U, true)); 228 EXPECT_THAT(Xyz.CallSites[0][1], FrameContains("abc", 5U, 30U, false)); 229 230 // Check the memprof record for abc. 231 const llvm::GlobalValue::GUID AbcId = IndexedMemProfRecord::getGUID("abc"); 232 ASSERT_EQ(Records.count(AbcId), 1U); 233 const MemProfRecord &Abc = Records[AbcId]; 234 EXPECT_TRUE(Abc.AllocSites.empty()); 235 ASSERT_EQ(Abc.CallSites.size(), 1U); 236 ASSERT_EQ(Abc.CallSites[0].size(), 2U); 237 EXPECT_THAT(Abc.CallSites[0][0], FrameContains("xyz", 5U, 30U, true)); 238 EXPECT_THAT(Abc.CallSites[0][1], FrameContains("abc", 5U, 30U, false)); 239 } 240 241 TEST(MemProf, PortableWrapper) { 242 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000, 243 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3, 244 /*dealloc_cpu=*/4); 245 246 const auto Schema = getFullSchema(); 247 PortableMemInfoBlock WriteBlock(Info); 248 249 std::string Buffer; 250 llvm::raw_string_ostream OS(Buffer); 251 WriteBlock.serialize(Schema, OS); 252 OS.flush(); 253 254 PortableMemInfoBlock ReadBlock( 255 Schema, reinterpret_cast<const unsigned char *>(Buffer.data())); 256 257 EXPECT_EQ(ReadBlock, WriteBlock); 258 // Here we compare directly with the actual counts instead of MemInfoBlock 259 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros 260 // take a reference to the params, this results in unaligned accesses. 261 EXPECT_EQ(1UL, ReadBlock.getAllocCount()); 262 EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount()); 263 EXPECT_EQ(3UL, ReadBlock.getAllocCpuId()); 264 } 265 266 TEST(MemProf, RecordSerializationRoundTrip) { 267 const MemProfSchema Schema = getFullSchema(); 268 269 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000, 270 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3, 271 /*dealloc_cpu=*/4); 272 273 llvm::SmallVector<llvm::SmallVector<FrameId>> AllocCallStacks = { 274 {0x123, 0x345}, {0x123, 0x567}}; 275 276 llvm::SmallVector<llvm::SmallVector<FrameId>> CallSites = {{0x333, 0x777}}; 277 278 IndexedMemProfRecord Record; 279 for (const auto &ACS : AllocCallStacks) { 280 // Use the same info block for both allocation sites. 281 Record.AllocSites.emplace_back(ACS, Info); 282 } 283 Record.CallSites.assign(CallSites); 284 285 std::string Buffer; 286 llvm::raw_string_ostream OS(Buffer); 287 Record.serialize(Schema, OS); 288 OS.flush(); 289 290 const IndexedMemProfRecord GotRecord = IndexedMemProfRecord::deserialize( 291 Schema, reinterpret_cast<const unsigned char *>(Buffer.data())); 292 293 EXPECT_EQ(Record, GotRecord); 294 } 295 296 TEST(MemProf, SymbolizationFilter) { 297 std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer()); 298 299 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000}, 300 specifier(), false)) 301 .Times(1) // once since we don't lookup invalid PCs repeatedly. 302 .WillRepeatedly(Return(makeInliningInfo({ 303 {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"}, 304 }))); 305 306 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000}, 307 specifier(), false)) 308 .Times(1) // once since we don't lookup invalid PCs repeatedly. 309 .WillRepeatedly(Return(makeInliningInfo({ 310 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"}, 311 }))); 312 313 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000}, 314 specifier(), false)) 315 .Times(1) // once since we don't lookup invalid PCs repeatedly. 316 .WillRepeatedly(Return(makeInliningInfo({ 317 {DILineInfo::BadString, 0, 0, 0}, 318 }))); 319 320 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000}, 321 specifier(), false)) 322 .Times(1) 323 .WillRepeatedly(Return(makeInliningInfo({ 324 {"foo", 10, 5, 30}, 325 }))); 326 327 CallStackMap CSM; 328 CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000}; 329 // This entry should be dropped since all PCs are either not 330 // symbolizable or belong to the runtime. 331 CSM[0x2] = {0x1000, 0x2000}; 332 333 llvm::MapVector<uint64_t, MemInfoBlock> Prof; 334 Prof[0x1].AllocCount = 1; 335 Prof[0x2].AllocCount = 1; 336 337 auto Seg = makeSegments(); 338 339 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM); 340 341 llvm::SmallVector<MemProfRecord, 1> Records; 342 for (const auto &KeyRecordPair : Reader) { 343 Records.push_back(KeyRecordPair.second); 344 } 345 346 ASSERT_EQ(Records.size(), 1U); 347 ASSERT_EQ(Records[0].AllocSites.size(), 1U); 348 ASSERT_EQ(Records[0].AllocSites[0].CallStack.size(), 1U); 349 EXPECT_THAT(Records[0].AllocSites[0].CallStack[0], 350 FrameContains("foo", 5U, 30U, false)); 351 } 352 } // namespace 353