1 /* 2 * Copyright (c) 2016, Google Inc. 3 * All rights reserved. 4 * Use of this source code is governed by a BSD-style license that can be 5 * found in the LICENSE file. 6 */ 7 8 // Tests converting perf.data files to sets of Profile 9 10 #include "perf_data_converter.h" 11 12 #include <unistd.h> 13 #include <cstdlib> 14 #include <cstring> 15 #include <fstream> 16 #include <iostream> 17 #include <sstream> 18 #include <unordered_map> 19 #include <unordered_set> 20 #include <utility> 21 #include <vector> 22 23 #include "int_compat.h" 24 #include "intervalmap.h" 25 #include "string_compat.h" 26 #include "test_compat.h" 27 #include "quipper/perf_parser.h" 28 #include "quipper/perf_reader.h" 29 30 using perftools::ProcessProfiles; 31 using perftools::profiles::Location; 32 using perftools::profiles::Mapping; 33 using quipper::PerfDataProto; 34 using testing::Contains; 35 36 namespace { 37 38 typedef std::unordered_map<string, std::pair<int64, int64>> MapCounts; 39 40 // GetMapCounts returns a map keyed by a location identifier and 41 // mapping to self and total counts for that location. 42 MapCounts GetMapCounts(const ProcessProfiles& pps) { 43 MapCounts map_counts; 44 for (const auto& pp : pps) { 45 const auto& profile = pp->data; 46 std::unordered_map<uint64, const Location*> locations; 47 perftools::IntervalMap<const Mapping*> mappings; 48 if (profile.mapping_size() <= 0) { 49 std::cerr << "Invalid mapping size: " << profile.mapping_size() 50 << std::endl; 51 abort(); 52 } 53 const Mapping& main = profile.mapping(0); 54 for (const auto& mapping : profile.mapping()) { 55 mappings.Set(mapping.memory_start(), mapping.memory_limit(), &mapping); 56 } 57 for (const auto& location : profile.location()) { 58 locations[location.id()] = &location; 59 } 60 for (int i = 0; i < profile.sample_size(); ++i) { 61 const auto& sample = profile.sample(i); 62 for (int id_index = 0; id_index < sample.location_id_size(); ++id_index) { 63 uint64 id = sample.location_id(id_index); 64 if (!locations[id]) { 65 std::cerr << "No location for id: " << id << std::endl; 66 abort(); 67 } 68 69 std::stringstream key_stream; 70 key_stream << profile.string_table(main.filename()) << ":" 71 << profile.string_table(main.build_id()); 72 if (locations[id]->mapping_id() != 0) { 73 const Mapping* dso; 74 uint64 addr = locations[id]->address(); 75 if (!mappings.Lookup(addr, &dso)) { 76 std::cerr << "no mapping for id: " << std::hex << addr << std::endl; 77 abort(); 78 } 79 key_stream << "+" << profile.string_table(dso->filename()) << ":" 80 << profile.string_table(dso->build_id()) << std::hex 81 << (addr - dso->memory_start()); 82 } 83 const auto& key = key_stream.str(); 84 auto count = map_counts[key]; 85 if (id_index == 0) { 86 // Exclusive. 87 ++count.first; 88 } else { 89 // Inclusive. 90 ++count.second; 91 } 92 map_counts[key] = count; 93 } 94 } 95 } 96 return map_counts; 97 } 98 99 std::unordered_set<string> AllBuildIDs(const ProcessProfiles& pps) { 100 std::unordered_set<string> ret; 101 for (const auto& pp : pps) { 102 for (const auto& it : pp->data.mapping()) { 103 ret.insert(pp->data.string_table(it.build_id())); 104 } 105 } 106 return ret; 107 } 108 109 std::unordered_set<string> AllComments(const ProcessProfiles& pps) { 110 std::unordered_set<string> ret; 111 for (const auto& pp : pps) { 112 for (const auto& it : pp->data.comment()) { 113 ret.insert(pp->data.string_table(it)); 114 } 115 } 116 return ret; 117 } 118 } // namespace 119 120 namespace perftools { 121 122 // Reads the content of the file at path into a string. Aborts if it is unable 123 // to. 124 void GetContents(const string& path, string* content) { 125 std::ifstream file(path); 126 ASSERT_EQ((file.rdstate() & std::ifstream::failbit), 0); 127 std::stringstream contents; 128 contents << file.rdbuf(); 129 *content = contents.str(); 130 } 131 132 // Set dir to the current directory, or return false if an error occurs. 133 bool GetCurrentDirectory(string* dir) { 134 std::unique_ptr<char, decltype(std::free)*> cwd(getcwd(nullptr, 0), 135 std::free); 136 if (cwd == nullptr) { 137 return false; 138 } 139 *dir = cwd.get(); 140 return true; 141 } 142 143 // Gets the string after the last '/' or returns the entire string if there are 144 // no slashes. 145 inline string Basename(const string& path) { 146 return path.substr(path.find_last_of("/")); 147 } 148 149 // Assumes relpath does not begin with a '/' 150 string GetResource(const string& relpath) { 151 string cwd; 152 GetCurrentDirectory(&cwd); 153 string resdir = cwd + "/" + relpath; 154 return resdir; 155 } 156 157 PerfDataProto ToPerfDataProto(const string& raw_perf_data) { 158 std::unique_ptr<quipper::PerfReader> reader(new quipper::PerfReader); 159 EXPECT_TRUE(reader->ReadFromString(raw_perf_data)); 160 161 std::unique_ptr<quipper::PerfParser> parser; 162 parser.reset(new quipper::PerfParser(reader.get())); 163 EXPECT_TRUE(parser->ParseRawEvents()); 164 165 PerfDataProto perf_data_proto; 166 EXPECT_TRUE(reader->Serialize(&perf_data_proto)); 167 return perf_data_proto; 168 } 169 170 class PerfDataConverterTest : public ::testing::Test { 171 protected: 172 PerfDataConverterTest() {} 173 }; 174 175 struct TestCase { 176 string filename; 177 int64 key_count; 178 int64 total_exclusive; 179 int64 total_inclusive; 180 }; 181 182 // Builds a set of counts for each sample in the profile. This is a 183 // very high-level test -- major changes in the values should 184 // be validated via manual inspection of new golden values. 185 TEST_F(PerfDataConverterTest, Converts) { 186 string single_profile( 187 GetResource("testdata" 188 "/single-event-single-process.perf.data")); 189 string multi_pid_profile( 190 GetResource("testdata" 191 "/single-event-multi-process.perf.data")); 192 string multi_event_profile( 193 GetResource("testdata" 194 "/multi-event-single-process.perf.data")); 195 string stack_profile( 196 GetResource("testdata" 197 "/with-callchain.perf.data")); 198 199 std::vector<TestCase> cases; 200 cases.emplace_back(TestCase{single_profile, 1061, 1061, 0}); 201 cases.emplace_back(TestCase{multi_pid_profile, 442, 730, 0}); 202 cases.emplace_back(TestCase{multi_event_profile, 1124, 1124, 0}); 203 cases.emplace_back(TestCase{stack_profile, 1138, 1210, 2247}); 204 205 for (const auto& c : cases) { 206 string casename = "case " + Basename(c.filename); 207 string raw_perf_data; 208 GetContents(c.filename, &raw_perf_data); 209 210 // Test RawPerfData input. 211 auto pps = RawPerfDataToProfiles( 212 reinterpret_cast<const void*>(raw_perf_data.c_str()), 213 raw_perf_data.size(), {}, kNoLabels, kNoOptions); 214 // Does not group by PID, Vector should only contain one element 215 EXPECT_EQ(pps.size(), 1); 216 auto counts = GetMapCounts(pps); 217 EXPECT_EQ(c.key_count, counts.size()) << casename; 218 int64 total_exclusive = 0; 219 int64 total_inclusive = 0; 220 for (const auto& it : counts) { 221 total_exclusive += it.second.first; 222 total_inclusive += it.second.second; 223 } 224 EXPECT_EQ(c.total_exclusive, total_exclusive) << casename; 225 EXPECT_EQ(c.total_inclusive, total_inclusive) << casename; 226 227 // Test PerfDataProto input. 228 const auto perf_data_proto = ToPerfDataProto(raw_perf_data); 229 pps = PerfDataProtoToProfiles( 230 &perf_data_proto, kNoLabels, kNoOptions); 231 counts = GetMapCounts(pps); 232 EXPECT_EQ(c.key_count, counts.size()) << casename; 233 total_exclusive = 0; 234 total_inclusive = 0; 235 for (const auto& it : counts) { 236 total_exclusive += it.second.first; 237 total_inclusive += it.second.second; 238 } 239 EXPECT_EQ(c.total_exclusive, total_exclusive) << casename; 240 EXPECT_EQ(c.total_inclusive, total_inclusive) << casename; 241 } 242 } 243 244 TEST_F(PerfDataConverterTest, ConvertsGroupPid) { 245 string multiple_profile( 246 GetResource("testdata" 247 "/single-event-multi-process.perf.data")); 248 249 // Fetch the stdout_injected result and emit it to a profile.proto. Group by 250 // PIDs so the inner vector will have multiple entries. 251 string raw_perf_data; 252 GetContents(multiple_profile, &raw_perf_data); 253 // Test PerfDataProto input. 254 const auto perf_data_proto = ToPerfDataProto(raw_perf_data); 255 const auto pps = PerfDataProtoToProfiles( 256 &perf_data_proto, kPidAndTidLabels, kGroupByPids); 257 258 uint64 total_samples = 0; 259 // Samples were collected for 6 pids in this case, so the outer vector should 260 // contain 6 profiles, one for each pid. 261 int pids = 6; 262 EXPECT_EQ(pids, pps.size()); 263 for (const auto& per_thread : pps) { 264 for (const auto& sample : per_thread->data.sample()) { 265 // Count only samples, which are the even numbers. Total event counts 266 // are the odds. 267 for (int x = 0; x < sample.value_size(); x += 2) { 268 total_samples += sample.value(x); 269 } 270 } 271 } 272 // The perf.data file contained 19989 original samples. Still should. 273 EXPECT_EQ(19989, total_samples); 274 } 275 276 TEST_F(PerfDataConverterTest, Injects) { 277 string path = GetResource("testdata" 278 "/with-callchain.perf.data"); 279 string raw_perf_data; 280 GetContents(path, &raw_perf_data); 281 const string want_build_id = "abcdabcd"; 282 std::map<string, string> build_ids = { 283 {"[kernel.kallsyms]", want_build_id}}; 284 285 // Test RawPerfData input. 286 const ProcessProfiles pps = RawPerfDataToProfiles( 287 reinterpret_cast<const void*>(raw_perf_data.c_str()), 288 raw_perf_data.size(), build_ids); 289 std::unordered_set<string> all_build_ids = AllBuildIDs(pps); 290 EXPECT_THAT(all_build_ids, Contains(want_build_id)); 291 } 292 293 TEST_F(PerfDataConverterTest, HandlesKernelMmapOverlappingUserCode) { 294 string path = GetResource("testdata" 295 "/perf-overlapping-kernel-mapping.pb_proto"); 296 string asciiPb; 297 GetContents(path, &asciiPb); 298 PerfDataProto perf_data_proto; 299 ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(asciiPb, &perf_data_proto)); 300 ProcessProfiles pps = PerfDataProtoToProfiles(&perf_data_proto); 301 EXPECT_EQ(1, pps.size()); 302 const auto& profile = pps[0]->data; 303 EXPECT_EQ(3, profile.sample_size()); 304 305 EXPECT_EQ(2, profile.mapping_size()); 306 EXPECT_EQ(1000, profile.mapping(0).memory_start()); // user 307 int64 user_mapping_id = profile.mapping(0).id(); 308 EXPECT_EQ(0, profile.mapping(1).memory_start()); // kernel 309 int64 kernel_mapping_id = profile.mapping(1).id(); 310 311 EXPECT_EQ(3, profile.location_size()); 312 EXPECT_EQ(kernel_mapping_id, profile.location(0).mapping_id()); 313 EXPECT_EQ(user_mapping_id, profile.location(1).mapping_id()); 314 EXPECT_EQ(kernel_mapping_id, profile.location(2).mapping_id()); 315 } 316 317 TEST_F(PerfDataConverterTest, PerfInfoSavedInComment) { 318 string path = GetResource( 319 "testdata" 320 "/single-event-single-process.perf.data"); 321 string raw_perf_data; 322 GetContents(path, &raw_perf_data); 323 const string want_version = "perf-version:3.16.7-ckt20"; 324 const string want_command = "perf-command:/usr/bin/perf_3.16 record ./a.out"; 325 326 // Test RawPerfData input. 327 const ProcessProfiles pps = RawPerfDataToProfiles( 328 reinterpret_cast<const void*>(raw_perf_data.c_str()), 329 raw_perf_data.size(), {}); 330 std::unordered_set<string> comments = AllComments(pps); 331 EXPECT_THAT(comments, Contains(want_version)); 332 EXPECT_THAT(comments, Contains(want_command)); 333 } 334 335 } // namespace perftools 336 337 int main(int argc, char** argv) { 338 testing::InitGoogleTest(&argc, argv); 339 return RUN_ALL_TESTS(); 340 } 341