Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (c) 2016, Google Inc. |
| 3 | * All rights reserved. |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 4 | * Use of this source code is governed by a BSD-style license that can be |
| 5 | * found in the LICENSE file. |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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 | |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 23 | #include "int_compat.h" |
| 24 | #include "intervalmap.h" |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 25 | #include "string_compat.h" |
| 26 | #include "test_compat.h" |
lakshmana | 5d24e0b | 2017-08-14 14:18:43 -0700 | [diff] [blame] | 27 | #include "quipper/perf_parser.h" |
| 28 | #include "quipper/perf_reader.h" |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 29 | |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 30 | using perftools::ProcessProfiles; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 31 | using perftools::profiles::Location; |
| 32 | using perftools::profiles::Mapping; |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 33 | using quipper::PerfDataProto; |
vlankhaar | 733f709 | 2017-10-31 16:43:19 -0700 | [diff] [blame] | 34 | using testing::Contains; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 35 | |
| 36 | namespace { |
| 37 | |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 38 | typedef std::unordered_map<string, std::pair<int64, int64>> MapCounts; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 39 | |
| 40 | // GetMapCounts returns a map keyed by a location identifier and |
| 41 | // mapping to self and total counts for that location. |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 42 | MapCounts GetMapCounts(const ProcessProfiles& pps) { |
| 43 | MapCounts map_counts; |
| 44 | for (const auto& pp : pps) { |
| 45 | const auto& profile = pp->data; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 46 | std::unordered_map<uint64, const Location*> locations; |
| 47 | perftools::IntervalMap<const Mapping*> mappings; |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 48 | if (profile.mapping_size() <= 0) { |
| 49 | std::cerr << "Invalid mapping size: " << profile.mapping_size() |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 50 | << std::endl; |
| 51 | abort(); |
| 52 | } |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 53 | const Mapping& main = profile.mapping(0); |
| 54 | for (const auto& mapping : profile.mapping()) { |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 55 | mappings.Set(mapping.memory_start(), mapping.memory_limit(), &mapping); |
| 56 | } |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 57 | for (const auto& location : profile.location()) { |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 58 | locations[location.id()] = &location; |
| 59 | } |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 60 | for (int i = 0; i < profile.sample_size(); ++i) { |
| 61 | const auto& sample = profile.sample(i); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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; |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 70 | key_stream << profile.string_table(main.filename()) << ":" |
| 71 | << profile.string_table(main.build_id()); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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 | } |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 79 | key_stream << "+" << profile.string_table(dso->filename()) << ":" |
| 80 | << profile.string_table(dso->build_id()) << std::hex |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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 | |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 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 | } |
vlankhaar | 733f709 | 2017-10-31 16:43:19 -0700 | [diff] [blame] | 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 | } |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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); |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 126 | ASSERT_EQ((file.rdstate() & std::ifstream::failbit), 0); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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); |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 153 | string resdir = cwd + "/" + relpath; |
| 154 | return resdir; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 155 | } |
| 156 | |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 157 | PerfDataProto ToPerfDataProto(const string& raw_perf_data) { |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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 | |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 165 | PerfDataProto perf_data_proto; |
| 166 | EXPECT_TRUE(reader->Serialize(&perf_data_proto)); |
| 167 | return perf_data_proto; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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( |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 187 | GetResource("testdata" |
| 188 | "/single-event-single-process.perf.data")); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 189 | string multi_pid_profile( |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 190 | GetResource("testdata" |
| 191 | "/single-event-multi-process.perf.data")); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 192 | string multi_event_profile( |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 193 | GetResource("testdata" |
| 194 | "/multi-event-single-process.perf.data")); |
| 195 | string stack_profile( |
| 196 | GetResource("testdata" |
| 197 | "/with-callchain.perf.data")); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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) { |
vlankhaar | 733f709 | 2017-10-31 16:43:19 -0700 | [diff] [blame] | 206 | string casename = "case " + Basename(c.filename); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 207 | string raw_perf_data; |
| 208 | GetContents(c.filename, &raw_perf_data); |
| 209 | |
| 210 | // Test RawPerfData input. |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 211 | auto pps = RawPerfDataToProfiles( |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 212 | reinterpret_cast<const void*>(raw_perf_data.c_str()), |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 213 | raw_perf_data.size(), {}, kNoLabels, kNoOptions); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 214 | // Does not group by PID, Vector should only contain one element |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 215 | EXPECT_EQ(pps.size(), 1); |
| 216 | auto counts = GetMapCounts(pps); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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 | |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 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); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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( |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 246 | GetResource("testdata" |
| 247 | "/single-event-multi-process.perf.data")); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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); |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 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); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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; |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 262 | EXPECT_EQ(pids, pps.size()); |
| 263 | for (const auto& per_thread : pps) { |
| 264 | for (const auto& sample : per_thread->data.sample()) { |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 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) { |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 277 | string path = GetResource("testdata" |
| 278 | "/with-callchain.perf.data"); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 279 | string raw_perf_data; |
| 280 | GetContents(path, &raw_perf_data); |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 281 | const string want_build_id = "abcdabcd"; |
| 282 | std::map<string, string> build_ids = { |
| 283 | {"[kernel.kallsyms]", want_build_id}}; |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 284 | |
| 285 | // Test RawPerfData input. |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 286 | const ProcessProfiles pps = RawPerfDataToProfiles( |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 287 | reinterpret_cast<const void*>(raw_perf_data.c_str()), |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 288 | raw_perf_data.size(), build_ids); |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 289 | std::unordered_set<string> all_build_ids = AllBuildIDs(pps); |
vlankhaar | 733f709 | 2017-10-31 16:43:19 -0700 | [diff] [blame] | 290 | EXPECT_THAT(all_build_ids, Contains(want_build_id)); |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 291 | } |
| 292 | |
Alexey Alexandrov | e18bc45 | 2016-11-04 14:55:48 -0700 | [diff] [blame] | 293 | TEST_F(PerfDataConverterTest, HandlesKernelMmapOverlappingUserCode) { |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 294 | string path = GetResource("testdata" |
| 295 | "/perf-overlapping-kernel-mapping.pb_proto"); |
Alexey Alexandrov | e18bc45 | 2016-11-04 14:55:48 -0700 | [diff] [blame] | 296 | string asciiPb; |
| 297 | GetContents(path, &asciiPb); |
Googler | 3a8b0ae | 2017-07-11 18:19:34 -0700 | [diff] [blame] | 298 | PerfDataProto perf_data_proto; |
| 299 | ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(asciiPb, &perf_data_proto)); |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 300 | ProcessProfiles pps = PerfDataProtoToProfiles(&perf_data_proto); |
| 301 | EXPECT_EQ(1, pps.size()); |
| 302 | const auto& profile = pps[0]->data; |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 303 | EXPECT_EQ(3, profile.sample_size()); |
Alexey Alexandrov | e18bc45 | 2016-11-04 14:55:48 -0700 | [diff] [blame] | 304 | |
cjiang | 48a540d | 2017-05-26 12:14:22 -0700 | [diff] [blame] | 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()); |
Alexey Alexandrov | e18bc45 | 2016-11-04 14:55:48 -0700 | [diff] [blame] | 315 | } |
| 316 | |
vlankhaar | 733f709 | 2017-10-31 16:43:19 -0700 | [diff] [blame] | 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 | |
Raul Silvera | 0c3c35e | 2016-09-21 13:14:55 -0700 | [diff] [blame] | 335 | } // namespace perftools |
| 336 | |
| 337 | int main(int argc, char** argv) { |
| 338 | testing::InitGoogleTest(&argc, argv); |
| 339 | return RUN_ALL_TESTS(); |
| 340 | } |