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