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.
GetMapCounts(const ProcessProfiles & pps)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
AllBuildIDs(const ProcessProfiles & pps)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
AllComments(const ProcessProfiles & pps)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.
GetContents(const string & path,string * content)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.
GetCurrentDirectory(string * dir)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.
Basename(const string & path)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 '/'
GetResource(const string & relpath)150 string GetResource(const string& relpath) {
151 string cwd;
152 GetCurrentDirectory(&cwd);
153 string resdir = cwd + "/" + relpath;
154 return resdir;
155 }
156
ToPerfDataProto(const string & raw_perf_data)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:
PerfDataConverterTest()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.
TEST_F(PerfDataConverterTest,Converts)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
TEST_F(PerfDataConverterTest,ConvertsGroupPid)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
TEST_F(PerfDataConverterTest,Injects)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
TEST_F(PerfDataConverterTest,HandlesKernelMmapOverlappingUserCode)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
TEST_F(PerfDataConverterTest,PerfInfoSavedInComment)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
main(int argc,char ** argv)337 int main(int argc, char** argv) {
338 testing::InitGoogleTest(&argc, argv);
339 return RUN_ALL_TESTS();
340 }
341