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