blob: 85db5243ae4c0b046200e60d8e8d4ebe826a9bd7 [file] [log] [blame]
Raul Silvera0c3c35e2016-09-21 13:14:55 -07001/*
2 * Copyright (c) 2016, Google Inc.
3 * All rights reserved.
Googler3a8b0ae2017-07-11 18:19:34 -07004 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
Raul Silvera0c3c35e2016-09-21 13:14:55 -07006 */
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 Silvera0c3c35e2016-09-21 13:14:55 -070023#include "int_compat.h"
24#include "intervalmap.h"
Raul Silvera0c3c35e2016-09-21 13:14:55 -070025#include "string_compat.h"
26#include "test_compat.h"
lakshmana5d24e0b2017-08-14 14:18:43 -070027#include "quipper/perf_parser.h"
28#include "quipper/perf_reader.h"
Raul Silvera0c3c35e2016-09-21 13:14:55 -070029
cjiang48a540d2017-05-26 12:14:22 -070030using perftools::ProcessProfiles;
Raul Silvera0c3c35e2016-09-21 13:14:55 -070031using perftools::profiles::Location;
32using perftools::profiles::Mapping;
Googler3a8b0ae2017-07-11 18:19:34 -070033using quipper::PerfDataProto;
vlankhaar733f7092017-10-31 16:43:19 -070034using testing::Contains;
Raul Silvera0c3c35e2016-09-21 13:14:55 -070035
36namespace {
37
cjiang48a540d2017-05-26 12:14:22 -070038typedef std::unordered_map<string, std::pair<int64, int64>> MapCounts;
Raul Silvera0c3c35e2016-09-21 13:14:55 -070039
40// GetMapCounts returns a map keyed by a location identifier and
41// mapping to self and total counts for that location.
cjiang48a540d2017-05-26 12:14:22 -070042MapCounts GetMapCounts(const ProcessProfiles& pps) {
43 MapCounts map_counts;
44 for (const auto& pp : pps) {
45 const auto& profile = pp->data;
Raul Silvera0c3c35e2016-09-21 13:14:55 -070046 std::unordered_map<uint64, const Location*> locations;
47 perftools::IntervalMap<const Mapping*> mappings;
cjiang48a540d2017-05-26 12:14:22 -070048 if (profile.mapping_size() <= 0) {
49 std::cerr << "Invalid mapping size: " << profile.mapping_size()
Raul Silvera0c3c35e2016-09-21 13:14:55 -070050 << std::endl;
51 abort();
52 }
cjiang48a540d2017-05-26 12:14:22 -070053 const Mapping& main = profile.mapping(0);
54 for (const auto& mapping : profile.mapping()) {
Raul Silvera0c3c35e2016-09-21 13:14:55 -070055 mappings.Set(mapping.memory_start(), mapping.memory_limit(), &mapping);
56 }
cjiang48a540d2017-05-26 12:14:22 -070057 for (const auto& location : profile.location()) {
Raul Silvera0c3c35e2016-09-21 13:14:55 -070058 locations[location.id()] = &location;
59 }
cjiang48a540d2017-05-26 12:14:22 -070060 for (int i = 0; i < profile.sample_size(); ++i) {
61 const auto& sample = profile.sample(i);
Raul Silvera0c3c35e2016-09-21 13:14:55 -070062 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;
cjiang48a540d2017-05-26 12:14:22 -070070 key_stream << profile.string_table(main.filename()) << ":"
71 << profile.string_table(main.build_id());
Raul Silvera0c3c35e2016-09-21 13:14:55 -070072 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 }
cjiang48a540d2017-05-26 12:14:22 -070079 key_stream << "+" << profile.string_table(dso->filename()) << ":"
80 << profile.string_table(dso->build_id()) << std::hex
Raul Silvera0c3c35e2016-09-21 13:14:55 -070081 << (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
Googler3a8b0ae2017-07-11 18:19:34 -070099std::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}
vlankhaar733f7092017-10-31 16:43:19 -0700108
109std::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 Silvera0c3c35e2016-09-21 13:14:55 -0700118} // namespace
119
120namespace perftools {
121
122// Reads the content of the file at path into a string. Aborts if it is unable
123// to.
124void GetContents(const string& path, string* content) {
125 std::ifstream file(path);
cjiang48a540d2017-05-26 12:14:22 -0700126 ASSERT_EQ((file.rdstate() & std::ifstream::failbit), 0);
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700127 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.
133bool 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.
145inline string Basename(const string& path) {
146 return path.substr(path.find_last_of("/"));
147}
148
149// Assumes relpath does not begin with a '/'
150string GetResource(const string& relpath) {
151 string cwd;
152 GetCurrentDirectory(&cwd);
Googler3a8b0ae2017-07-11 18:19:34 -0700153 string resdir = cwd + "/" + relpath;
154 return resdir;
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700155}
156
cjiang48a540d2017-05-26 12:14:22 -0700157PerfDataProto ToPerfDataProto(const string& raw_perf_data) {
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700158 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
cjiang48a540d2017-05-26 12:14:22 -0700165 PerfDataProto perf_data_proto;
166 EXPECT_TRUE(reader->Serialize(&perf_data_proto));
167 return perf_data_proto;
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700168}
169
170class PerfDataConverterTest : public ::testing::Test {
171 protected:
172 PerfDataConverterTest() {}
173};
174
175struct 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.
185TEST_F(PerfDataConverterTest, Converts) {
186 string single_profile(
Googler3a8b0ae2017-07-11 18:19:34 -0700187 GetResource("testdata"
188 "/single-event-single-process.perf.data"));
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700189 string multi_pid_profile(
Googler3a8b0ae2017-07-11 18:19:34 -0700190 GetResource("testdata"
191 "/single-event-multi-process.perf.data"));
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700192 string multi_event_profile(
Googler3a8b0ae2017-07-11 18:19:34 -0700193 GetResource("testdata"
194 "/multi-event-single-process.perf.data"));
195 string stack_profile(
196 GetResource("testdata"
197 "/with-callchain.perf.data"));
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700198
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) {
vlankhaar733f7092017-10-31 16:43:19 -0700206 string casename = "case " + Basename(c.filename);
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700207 string raw_perf_data;
208 GetContents(c.filename, &raw_perf_data);
209
210 // Test RawPerfData input.
cjiang48a540d2017-05-26 12:14:22 -0700211 auto pps = RawPerfDataToProfiles(
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700212 reinterpret_cast<const void*>(raw_perf_data.c_str()),
cjiang48a540d2017-05-26 12:14:22 -0700213 raw_perf_data.size(), {}, kNoLabels, kNoOptions);
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700214 // Does not group by PID, Vector should only contain one element
cjiang48a540d2017-05-26 12:14:22 -0700215 EXPECT_EQ(pps.size(), 1);
216 auto counts = GetMapCounts(pps);
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700217 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
cjiang48a540d2017-05-26 12:14:22 -0700227 // 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 Silvera0c3c35e2016-09-21 13:14:55 -0700232 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
244TEST_F(PerfDataConverterTest, ConvertsGroupPid) {
245 string multiple_profile(
Googler3a8b0ae2017-07-11 18:19:34 -0700246 GetResource("testdata"
247 "/single-event-multi-process.perf.data"));
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700248
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);
cjiang48a540d2017-05-26 12:14:22 -0700253 // 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 Silvera0c3c35e2016-09-21 13:14:55 -0700257
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;
cjiang48a540d2017-05-26 12:14:22 -0700262 EXPECT_EQ(pids, pps.size());
263 for (const auto& per_thread : pps) {
264 for (const auto& sample : per_thread->data.sample()) {
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700265 // 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
276TEST_F(PerfDataConverterTest, Injects) {
Googler3a8b0ae2017-07-11 18:19:34 -0700277 string path = GetResource("testdata"
278 "/with-callchain.perf.data");
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700279 string raw_perf_data;
280 GetContents(path, &raw_perf_data);
Googler3a8b0ae2017-07-11 18:19:34 -0700281 const string want_build_id = "abcdabcd";
282 std::map<string, string> build_ids = {
283 {"[kernel.kallsyms]", want_build_id}};
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700284
285 // Test RawPerfData input.
Googler3a8b0ae2017-07-11 18:19:34 -0700286 const ProcessProfiles pps = RawPerfDataToProfiles(
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700287 reinterpret_cast<const void*>(raw_perf_data.c_str()),
cjiang48a540d2017-05-26 12:14:22 -0700288 raw_perf_data.size(), build_ids);
Googler3a8b0ae2017-07-11 18:19:34 -0700289 std::unordered_set<string> all_build_ids = AllBuildIDs(pps);
vlankhaar733f7092017-10-31 16:43:19 -0700290 EXPECT_THAT(all_build_ids, Contains(want_build_id));
Raul Silvera0c3c35e2016-09-21 13:14:55 -0700291}
292
Alexey Alexandrove18bc452016-11-04 14:55:48 -0700293TEST_F(PerfDataConverterTest, HandlesKernelMmapOverlappingUserCode) {
Googler3a8b0ae2017-07-11 18:19:34 -0700294 string path = GetResource("testdata"
295 "/perf-overlapping-kernel-mapping.pb_proto");
Alexey Alexandrove18bc452016-11-04 14:55:48 -0700296 string asciiPb;
297 GetContents(path, &asciiPb);
Googler3a8b0ae2017-07-11 18:19:34 -0700298 PerfDataProto perf_data_proto;
299 ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(asciiPb, &perf_data_proto));
cjiang48a540d2017-05-26 12:14:22 -0700300 ProcessProfiles pps = PerfDataProtoToProfiles(&perf_data_proto);
301 EXPECT_EQ(1, pps.size());
302 const auto& profile = pps[0]->data;
cjiang48a540d2017-05-26 12:14:22 -0700303 EXPECT_EQ(3, profile.sample_size());
Alexey Alexandrove18bc452016-11-04 14:55:48 -0700304
cjiang48a540d2017-05-26 12:14:22 -0700305 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 Alexandrove18bc452016-11-04 14:55:48 -0700315}
316
vlankhaar733f7092017-10-31 16:43:19 -0700317TEST_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 Silvera0c3c35e2016-09-21 13:14:55 -0700335} // namespace perftools
336
337int main(int argc, char** argv) {
338 testing::InitGoogleTest(&argc, argv);
339 return RUN_ALL_TESTS();
340}