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/Object/ObjectFile.h" 7 #include "llvm/ProfileData/InstrProf.h" 8 #include "llvm/ProfileData/MemProfData.inc" 9 #include "llvm/ProfileData/RawMemProfReader.h" 10 #include "llvm/Support/Error.h" 11 #include "llvm/Support/MD5.h" 12 #include "llvm/Support/raw_ostream.h" 13 #include "gmock/gmock.h" 14 #include "gtest/gtest.h" 15 16 #include <initializer_list> 17 18 namespace { 19 20 using ::llvm::DIGlobal; 21 using ::llvm::DIInliningInfo; 22 using ::llvm::DILineInfo; 23 using ::llvm::DILineInfoSpecifier; 24 using ::llvm::DILocal; 25 using ::llvm::memprof::CallStackMap; 26 using ::llvm::memprof::MemInfoBlock; 27 using ::llvm::memprof::MemProfRecord; 28 using ::llvm::memprof::MemProfSchema; 29 using ::llvm::memprof::Meta; 30 using ::llvm::memprof::PortableMemInfoBlock; 31 using ::llvm::memprof::RawMemProfReader; 32 using ::llvm::memprof::SegmentEntry; 33 using ::llvm::object::SectionedAddress; 34 using ::llvm::symbolize::SymbolizableModule; 35 using ::testing::Return; 36 37 class MockSymbolizer : public SymbolizableModule { 38 public: 39 MOCK_CONST_METHOD3(symbolizeInlinedCode, 40 DIInliningInfo(SectionedAddress, DILineInfoSpecifier, 41 bool)); 42 // Most of the methods in the interface are unused. We only mock the 43 // method that we expect to be called from the memprof reader. 44 virtual DILineInfo symbolizeCode(SectionedAddress, DILineInfoSpecifier, 45 bool) const { 46 llvm_unreachable("unused"); 47 } 48 virtual DIGlobal symbolizeData(SectionedAddress) const { 49 llvm_unreachable("unused"); 50 } 51 virtual std::vector<DILocal> symbolizeFrame(SectionedAddress) const { 52 llvm_unreachable("unused"); 53 } 54 virtual bool isWin32Module() const { llvm_unreachable("unused"); } 55 virtual uint64_t getModulePreferredBase() const { 56 llvm_unreachable("unused"); 57 } 58 }; 59 60 struct MockInfo { 61 std::string FunctionName; 62 uint32_t Line; 63 uint32_t StartLine; 64 uint32_t Column; 65 std::string FileName = "valid/path.cc"; 66 }; 67 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) { 68 DIInliningInfo Result; 69 for (const auto &Item : MockFrames) { 70 DILineInfo Frame; 71 Frame.FunctionName = Item.FunctionName; 72 Frame.Line = Item.Line; 73 Frame.StartLine = Item.StartLine; 74 Frame.Column = Item.Column; 75 Frame.FileName = Item.FileName; 76 Result.addFrame(Frame); 77 } 78 return Result; 79 } 80 81 llvm::SmallVector<SegmentEntry, 4> makeSegments() { 82 llvm::SmallVector<SegmentEntry, 4> Result; 83 // Mimic an entry for a non position independent executable. 84 Result.emplace_back(0x0, 0x40000, 0x0); 85 return Result; 86 } 87 88 const DILineInfoSpecifier specifier() { 89 return DILineInfoSpecifier( 90 DILineInfoSpecifier::FileLineInfoKind::RawValue, 91 DILineInfoSpecifier::FunctionNameKind::LinkageName); 92 } 93 94 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") { 95 const uint64_t ExpectedHash = llvm::Function::getGUID(FunctionName); 96 if (arg.Function != ExpectedHash) { 97 *result_listener << "Hash mismatch"; 98 return false; 99 } 100 if (arg.LineOffset == LineOffset && arg.Column == Column && 101 arg.IsInlineFrame == Inline) { 102 return true; 103 } 104 *result_listener << "LineOffset, Column or Inline mismatch"; 105 return false; 106 } 107 108 MATCHER_P(EqualsRecord, Want, "") { 109 if (arg == Want) 110 return true; 111 112 std::string Explanation; 113 llvm::raw_string_ostream OS(Explanation); 114 OS << "\n Want: \n"; 115 Want.print(OS); 116 OS << "\n Got: \n"; 117 arg.print(OS); 118 OS.flush(); 119 120 *result_listener << Explanation; 121 return false; 122 } 123 124 MemProfSchema getFullSchema() { 125 MemProfSchema Schema; 126 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name); 127 #include "llvm/ProfileData/MIBEntryDef.inc" 128 #undef MIBEntryDef 129 return Schema; 130 } 131 132 TEST(MemProf, FillsValue) { 133 std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer()); 134 135 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000}, 136 specifier(), false)) 137 .Times(1) // Only once since we cache the result for future lookups. 138 .WillRepeatedly(Return(makeInliningInfo({ 139 {"foo", 10, 5, 30}, 140 {"bar", 201, 150, 20}, 141 }))); 142 143 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x6000}, 144 specifier(), false)) 145 .Times(1) 146 .WillRepeatedly(Return(makeInliningInfo({ 147 {"baz", 10, 5, 30}, 148 {"qux.llvm.12345", 75, 70, 10}, 149 }))); 150 151 CallStackMap CSM; 152 CSM[0x1] = {0x2000}; 153 CSM[0x2] = {0x6000, 0x2000}; 154 155 llvm::MapVector<uint64_t, MemInfoBlock> Prof; 156 Prof[0x1].AllocCount = 1; 157 Prof[0x2].AllocCount = 2; 158 159 auto Seg = makeSegments(); 160 161 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM); 162 163 std::vector<MemProfRecord> Records; 164 for (const MemProfRecord &R : Reader) { 165 Records.push_back(R); 166 } 167 EXPECT_EQ(Records.size(), 2U); 168 169 EXPECT_EQ(Records[0].Info.getAllocCount(), 1U); 170 EXPECT_EQ(Records[1].Info.getAllocCount(), 2U); 171 EXPECT_THAT(Records[0].CallStack[0], FrameContains("foo", 5U, 30U, false)); 172 EXPECT_THAT(Records[0].CallStack[1], FrameContains("bar", 51U, 20U, true)); 173 174 EXPECT_THAT(Records[1].CallStack[0], FrameContains("baz", 5U, 30U, false)); 175 EXPECT_THAT(Records[1].CallStack[1], FrameContains("qux", 5U, 10U, true)); 176 EXPECT_THAT(Records[1].CallStack[2], FrameContains("foo", 5U, 30U, false)); 177 EXPECT_THAT(Records[1].CallStack[3], FrameContains("bar", 51U, 20U, true)); 178 } 179 180 TEST(MemProf, PortableWrapper) { 181 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000, 182 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3, 183 /*dealloc_cpu=*/4); 184 185 const auto Schema = getFullSchema(); 186 PortableMemInfoBlock WriteBlock(Info); 187 188 std::string Buffer; 189 llvm::raw_string_ostream OS(Buffer); 190 WriteBlock.serialize(Schema, OS); 191 OS.flush(); 192 193 PortableMemInfoBlock ReadBlock( 194 Schema, reinterpret_cast<const unsigned char *>(Buffer.data())); 195 196 EXPECT_EQ(ReadBlock, WriteBlock); 197 // Here we compare directly with the actual counts instead of MemInfoBlock 198 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros 199 // take a reference to the params, this results in unaligned accesses. 200 EXPECT_EQ(1UL, ReadBlock.getAllocCount()); 201 EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount()); 202 EXPECT_EQ(3UL, ReadBlock.getAllocCpuId()); 203 } 204 205 TEST(MemProf, RecordSerializationRoundTrip) { 206 const MemProfSchema Schema = getFullSchema(); 207 208 llvm::SmallVector<MemProfRecord, 3> Records; 209 MemProfRecord MR; 210 211 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000, 212 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3, 213 /*dealloc_cpu=*/4); 214 215 MR.Info = PortableMemInfoBlock(Info); 216 MR.CallStack.push_back({0x123, 1, 2, false}); 217 MR.CallStack.push_back({0x345, 3, 4, false}); 218 Records.push_back(MR); 219 220 MR.clear(); 221 MR.Info = PortableMemInfoBlock(Info); 222 MR.CallStack.push_back({0x567, 5, 6, false}); 223 MR.CallStack.push_back({0x789, 7, 8, false}); 224 Records.push_back(MR); 225 226 std::string Buffer; 227 llvm::raw_string_ostream OS(Buffer); 228 serializeRecords(Records, Schema, OS); 229 OS.flush(); 230 231 const llvm::SmallVector<MemProfRecord, 4> GotRecords = deserializeRecords( 232 Schema, reinterpret_cast<const unsigned char *>(Buffer.data())); 233 234 ASSERT_TRUE(!GotRecords.empty()); 235 EXPECT_EQ(GotRecords.size(), Records.size()); 236 EXPECT_THAT(GotRecords[0], EqualsRecord(Records[0])); 237 EXPECT_THAT(GotRecords[1], EqualsRecord(Records[1])); 238 } 239 240 TEST(MemProf, SymbolizationFilter) { 241 std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer()); 242 243 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x1000}, 244 specifier(), false)) 245 .Times(1) // once since we don't lookup invalid PCs repeatedly. 246 .WillRepeatedly(Return(makeInliningInfo({ 247 {"malloc", 70, 57, 3, "memprof/memprof_malloc_linux.cpp"}, 248 }))); 249 250 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000}, 251 specifier(), false)) 252 .Times(1) // once since we don't lookup invalid PCs repeatedly. 253 .WillRepeatedly(Return(makeInliningInfo({ 254 {"new", 70, 57, 3, "memprof/memprof_new_delete.cpp"}, 255 }))); 256 257 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x3000}, 258 specifier(), false)) 259 .Times(1) // once since we don't lookup invalid PCs repeatedly. 260 .WillRepeatedly(Return(makeInliningInfo({ 261 {DILineInfo::BadString, 0, 0, 0}, 262 }))); 263 264 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x4000}, 265 specifier(), false)) 266 .Times(1) 267 .WillRepeatedly(Return(makeInliningInfo({ 268 {"foo", 10, 5, 30}, 269 }))); 270 271 CallStackMap CSM; 272 CSM[0x1] = {0x1000, 0x2000, 0x3000, 0x4000}; 273 // This entry should be dropped since all PCs are either not 274 // symbolizable or belong to the runtime. 275 CSM[0x2] = {0x1000, 0x2000}; 276 277 llvm::MapVector<uint64_t, MemInfoBlock> Prof; 278 Prof[0x1].AllocCount = 1; 279 Prof[0x2].AllocCount = 1; 280 281 auto Seg = makeSegments(); 282 283 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM); 284 285 std::vector<MemProfRecord> Records; 286 for (const MemProfRecord &R : Reader) { 287 Records.push_back(R); 288 } 289 ASSERT_EQ(Records.size(), 1U); 290 ASSERT_EQ(Records[0].CallStack.size(), 1U); 291 EXPECT_THAT(Records[0].CallStack[0], FrameContains("foo", 5U, 30U, false)); 292 } 293 } // namespace 294