• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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