Home | History | Annotate | Download | only in src
      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