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 }; 66 DIInliningInfo makeInliningInfo(std::initializer_list<MockInfo> MockFrames) { 67 DIInliningInfo Result; 68 for (const auto &Item : MockFrames) { 69 DILineInfo Frame; 70 Frame.FunctionName = Item.FunctionName; 71 Frame.Line = Item.Line; 72 Frame.StartLine = Item.StartLine; 73 Frame.Column = Item.Column; 74 Result.addFrame(Frame); 75 } 76 return Result; 77 } 78 79 llvm::SmallVector<SegmentEntry, 4> makeSegments() { 80 llvm::SmallVector<SegmentEntry, 4> Result; 81 // Mimic an entry for a non position independent executable. 82 Result.emplace_back(0x0, 0x40000, 0x0); 83 return Result; 84 } 85 86 const DILineInfoSpecifier specifier() { 87 return DILineInfoSpecifier( 88 DILineInfoSpecifier::FileLineInfoKind::RawValue, 89 DILineInfoSpecifier::FunctionNameKind::LinkageName); 90 } 91 92 MATCHER_P4(FrameContains, FunctionName, LineOffset, Column, Inline, "") { 93 const uint64_t ExpectedHash = llvm::Function::getGUID(FunctionName); 94 if (arg.Function != ExpectedHash) { 95 *result_listener << "Hash mismatch"; 96 return false; 97 } 98 if (arg.LineOffset == LineOffset && arg.Column == Column && 99 arg.IsInlineFrame == Inline) { 100 return true; 101 } 102 *result_listener << "LineOffset, Column or Inline mismatch"; 103 return false; 104 } 105 106 MATCHER_P(EqualsRecord, Want, "") { 107 if (arg == Want) 108 return true; 109 110 std::string Explanation; 111 llvm::raw_string_ostream OS(Explanation); 112 OS << "\n Want: \n"; 113 Want.print(OS); 114 OS << "\n Got: \n"; 115 arg.print(OS); 116 OS.flush(); 117 118 *result_listener << Explanation; 119 return false; 120 } 121 122 MemProfSchema getFullSchema() { 123 MemProfSchema Schema; 124 #define MIBEntryDef(NameTag, Name, Type) Schema.push_back(Meta::Name); 125 #include "llvm/ProfileData/MIBEntryDef.inc" 126 #undef MIBEntryDef 127 return Schema; 128 } 129 130 TEST(MemProf, FillsValue) { 131 std::unique_ptr<MockSymbolizer> Symbolizer(new MockSymbolizer()); 132 133 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x2000}, 134 specifier(), false)) 135 .Times(2) 136 .WillRepeatedly(Return(makeInliningInfo({ 137 {"foo", 10, 5, 30}, 138 {"bar", 201, 150, 20}, 139 }))); 140 141 EXPECT_CALL(*Symbolizer, symbolizeInlinedCode(SectionedAddress{0x6000}, 142 specifier(), false)) 143 .Times(1) 144 .WillRepeatedly(Return(makeInliningInfo({ 145 {"baz", 10, 5, 30}, 146 {"qux.llvm.12345", 75, 70, 10}, 147 }))); 148 149 CallStackMap CSM; 150 CSM[0x1] = {0x2000}; 151 CSM[0x2] = {0x6000, 0x2000}; 152 153 llvm::MapVector<uint64_t, MemInfoBlock> Prof; 154 Prof[0x1].AllocCount = 1; 155 Prof[0x2].AllocCount = 2; 156 157 auto Seg = makeSegments(); 158 159 RawMemProfReader Reader(std::move(Symbolizer), Seg, Prof, CSM); 160 161 std::vector<MemProfRecord> Records; 162 for (const MemProfRecord &R : Reader) { 163 Records.push_back(R); 164 } 165 EXPECT_EQ(Records.size(), 2U); 166 167 EXPECT_EQ(Records[0].Info.getAllocCount(), 1U); 168 EXPECT_EQ(Records[1].Info.getAllocCount(), 2U); 169 EXPECT_THAT(Records[0].CallStack[0], FrameContains("foo", 5U, 30U, false)); 170 EXPECT_THAT(Records[0].CallStack[1], FrameContains("bar", 51U, 20U, true)); 171 172 EXPECT_THAT(Records[1].CallStack[0], FrameContains("baz", 5U, 30U, false)); 173 EXPECT_THAT(Records[1].CallStack[1], FrameContains("qux", 5U, 10U, true)); 174 EXPECT_THAT(Records[1].CallStack[2], FrameContains("foo", 5U, 30U, false)); 175 EXPECT_THAT(Records[1].CallStack[3], FrameContains("bar", 51U, 20U, true)); 176 } 177 178 TEST(MemProf, PortableWrapper) { 179 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000, 180 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3, 181 /*dealloc_cpu=*/4); 182 183 const auto Schema = getFullSchema(); 184 PortableMemInfoBlock WriteBlock(Info); 185 186 std::string Buffer; 187 llvm::raw_string_ostream OS(Buffer); 188 WriteBlock.serialize(Schema, OS); 189 OS.flush(); 190 191 PortableMemInfoBlock ReadBlock( 192 Schema, reinterpret_cast<const unsigned char *>(Buffer.data())); 193 194 EXPECT_EQ(ReadBlock, WriteBlock); 195 // Here we compare directly with the actual counts instead of MemInfoBlock 196 // members. Since the MemInfoBlock struct is packed and the EXPECT_EQ macros 197 // take a reference to the params, this results in unaligned accesses. 198 EXPECT_EQ(1UL, ReadBlock.getAllocCount()); 199 EXPECT_EQ(7ULL, ReadBlock.getTotalAccessCount()); 200 EXPECT_EQ(3UL, ReadBlock.getAllocCpuId()); 201 } 202 203 TEST(MemProf, RecordSerializationRoundTrip) { 204 const MemProfSchema Schema = getFullSchema(); 205 206 llvm::SmallVector<MemProfRecord, 3> Records; 207 MemProfRecord MR; 208 209 MemInfoBlock Info(/*size=*/16, /*access_count=*/7, /*alloc_timestamp=*/1000, 210 /*dealloc_timestamp=*/2000, /*alloc_cpu=*/3, 211 /*dealloc_cpu=*/4); 212 213 MR.Info = PortableMemInfoBlock(Info); 214 MR.CallStack.push_back({0x123, 1, 2, false}); 215 MR.CallStack.push_back({0x345, 3, 4, false}); 216 Records.push_back(MR); 217 218 MR.clear(); 219 MR.Info = PortableMemInfoBlock(Info); 220 MR.CallStack.push_back({0x567, 5, 6, false}); 221 MR.CallStack.push_back({0x789, 7, 8, false}); 222 Records.push_back(MR); 223 224 std::string Buffer; 225 llvm::raw_string_ostream OS(Buffer); 226 serializeRecords(Records, Schema, OS); 227 OS.flush(); 228 229 const llvm::SmallVector<MemProfRecord, 4> GotRecords = deserializeRecords( 230 Schema, reinterpret_cast<const unsigned char *>(Buffer.data())); 231 232 ASSERT_TRUE(!GotRecords.empty()); 233 EXPECT_EQ(GotRecords.size(), Records.size()); 234 EXPECT_THAT(GotRecords[0], EqualsRecord(Records[0])); 235 EXPECT_THAT(GotRecords[1], EqualsRecord(Records[1])); 236 } 237 } // namespace 238