/* * Copyright (c) 2016, Google Inc. * All rights reserved. * Use of this source code is governed by a BSD-style license that can be * found in the LICENSE file. */ // Tests converting perf.data files to sets of Profile #include "perf_data_converter.h" #include <unistd.h> #include <cstdlib> #include <cstring> #include <fstream> #include <iostream> #include <sstream> #include <unordered_map> #include <unordered_set> #include <utility> #include <vector> #include "int_compat.h" #include "intervalmap.h" #include "string_compat.h" #include "test_compat.h" #include "quipper/perf_parser.h" #include "quipper/perf_reader.h" using perftools::ProcessProfiles; using perftools::profiles::Location; using perftools::profiles::Mapping; using quipper::PerfDataProto; using testing::Contains; namespace { typedef std::unordered_map<string, std::pair<int64, int64>> MapCounts; // GetMapCounts returns a map keyed by a location identifier and // mapping to self and total counts for that location. MapCounts GetMapCounts(const ProcessProfiles& pps) { MapCounts map_counts; for (const auto& pp : pps) { const auto& profile = pp->data; std::unordered_map<uint64, const Location*> locations; perftools::IntervalMap<const Mapping*> mappings; if (profile.mapping_size() <= 0) { std::cerr << "Invalid mapping size: " << profile.mapping_size() << std::endl; abort(); } const Mapping& main = profile.mapping(0); for (const auto& mapping : profile.mapping()) { mappings.Set(mapping.memory_start(), mapping.memory_limit(), &mapping); } for (const auto& location : profile.location()) { locations[location.id()] = &location; } for (int i = 0; i < profile.sample_size(); ++i) { const auto& sample = profile.sample(i); for (int id_index = 0; id_index < sample.location_id_size(); ++id_index) { uint64 id = sample.location_id(id_index); if (!locations[id]) { std::cerr << "No location for id: " << id << std::endl; abort(); } std::stringstream key_stream; key_stream << profile.string_table(main.filename()) << ":" << profile.string_table(main.build_id()); if (locations[id]->mapping_id() != 0) { const Mapping* dso; uint64 addr = locations[id]->address(); if (!mappings.Lookup(addr, &dso)) { std::cerr << "no mapping for id: " << std::hex << addr << std::endl; abort(); } key_stream << "+" << profile.string_table(dso->filename()) << ":" << profile.string_table(dso->build_id()) << std::hex << (addr - dso->memory_start()); } const auto& key = key_stream.str(); auto count = map_counts[key]; if (id_index == 0) { // Exclusive. ++count.first; } else { // Inclusive. ++count.second; } map_counts[key] = count; } } } return map_counts; } std::unordered_set<string> AllBuildIDs(const ProcessProfiles& pps) { std::unordered_set<string> ret; for (const auto& pp : pps) { for (const auto& it : pp->data.mapping()) { ret.insert(pp->data.string_table(it.build_id())); } } return ret; } std::unordered_set<string> AllComments(const ProcessProfiles& pps) { std::unordered_set<string> ret; for (const auto& pp : pps) { for (const auto& it : pp->data.comment()) { ret.insert(pp->data.string_table(it)); } } return ret; } } // namespace namespace perftools { // Reads the content of the file at path into a string. Aborts if it is unable // to. void GetContents(const string& path, string* content) { std::ifstream file(path); ASSERT_EQ((file.rdstate() & std::ifstream::failbit), 0); std::stringstream contents; contents << file.rdbuf(); *content = contents.str(); } // Set dir to the current directory, or return false if an error occurs. bool GetCurrentDirectory(string* dir) { std::unique_ptr<char, decltype(std::free)*> cwd(getcwd(nullptr, 0), std::free); if (cwd == nullptr) { return false; } *dir = cwd.get(); return true; } // Gets the string after the last '/' or returns the entire string if there are // no slashes. inline string Basename(const string& path) { return path.substr(path.find_last_of("/")); } // Assumes relpath does not begin with a '/' string GetResource(const string& relpath) { string cwd; GetCurrentDirectory(&cwd); string resdir = cwd + "/" + relpath; return resdir; } PerfDataProto ToPerfDataProto(const string& raw_perf_data) { std::unique_ptr<quipper::PerfReader> reader(new quipper::PerfReader); EXPECT_TRUE(reader->ReadFromString(raw_perf_data)); std::unique_ptr<quipper::PerfParser> parser; parser.reset(new quipper::PerfParser(reader.get())); EXPECT_TRUE(parser->ParseRawEvents()); PerfDataProto perf_data_proto; EXPECT_TRUE(reader->Serialize(&perf_data_proto)); return perf_data_proto; } class PerfDataConverterTest : public ::testing::Test { protected: PerfDataConverterTest() {} }; struct TestCase { string filename; int64 key_count; int64 total_exclusive; int64 total_inclusive; }; // Builds a set of counts for each sample in the profile. This is a // very high-level test -- major changes in the values should // be validated via manual inspection of new golden values. TEST_F(PerfDataConverterTest, Converts) { string single_profile( GetResource("testdata" "/single-event-single-process.perf.data")); string multi_pid_profile( GetResource("testdata" "/single-event-multi-process.perf.data")); string multi_event_profile( GetResource("testdata" "/multi-event-single-process.perf.data")); string stack_profile( GetResource("testdata" "/with-callchain.perf.data")); std::vector<TestCase> cases; cases.emplace_back(TestCase{single_profile, 1061, 1061, 0}); cases.emplace_back(TestCase{multi_pid_profile, 442, 730, 0}); cases.emplace_back(TestCase{multi_event_profile, 1124, 1124, 0}); cases.emplace_back(TestCase{stack_profile, 1138, 1210, 2247}); for (const auto& c : cases) { string casename = "case " + Basename(c.filename); string raw_perf_data; GetContents(c.filename, &raw_perf_data); // Test RawPerfData input. auto pps = RawPerfDataToProfiles( reinterpret_cast<const void*>(raw_perf_data.c_str()), raw_perf_data.size(), {}, kNoLabels, kNoOptions); // Does not group by PID, Vector should only contain one element EXPECT_EQ(pps.size(), 1); auto counts = GetMapCounts(pps); EXPECT_EQ(c.key_count, counts.size()) << casename; int64 total_exclusive = 0; int64 total_inclusive = 0; for (const auto& it : counts) { total_exclusive += it.second.first; total_inclusive += it.second.second; } EXPECT_EQ(c.total_exclusive, total_exclusive) << casename; EXPECT_EQ(c.total_inclusive, total_inclusive) << casename; // Test PerfDataProto input. const auto perf_data_proto = ToPerfDataProto(raw_perf_data); pps = PerfDataProtoToProfiles( &perf_data_proto, kNoLabels, kNoOptions); counts = GetMapCounts(pps); EXPECT_EQ(c.key_count, counts.size()) << casename; total_exclusive = 0; total_inclusive = 0; for (const auto& it : counts) { total_exclusive += it.second.first; total_inclusive += it.second.second; } EXPECT_EQ(c.total_exclusive, total_exclusive) << casename; EXPECT_EQ(c.total_inclusive, total_inclusive) << casename; } } TEST_F(PerfDataConverterTest, ConvertsGroupPid) { string multiple_profile( GetResource("testdata" "/single-event-multi-process.perf.data")); // Fetch the stdout_injected result and emit it to a profile.proto. Group by // PIDs so the inner vector will have multiple entries. string raw_perf_data; GetContents(multiple_profile, &raw_perf_data); // Test PerfDataProto input. const auto perf_data_proto = ToPerfDataProto(raw_perf_data); const auto pps = PerfDataProtoToProfiles( &perf_data_proto, kPidAndTidLabels, kGroupByPids); uint64 total_samples = 0; // Samples were collected for 6 pids in this case, so the outer vector should // contain 6 profiles, one for each pid. int pids = 6; EXPECT_EQ(pids, pps.size()); for (const auto& per_thread : pps) { for (const auto& sample : per_thread->data.sample()) { // Count only samples, which are the even numbers. Total event counts // are the odds. for (int x = 0; x < sample.value_size(); x += 2) { total_samples += sample.value(x); } } } // The perf.data file contained 19989 original samples. Still should. EXPECT_EQ(19989, total_samples); } TEST_F(PerfDataConverterTest, Injects) { string path = GetResource("testdata" "/with-callchain.perf.data"); string raw_perf_data; GetContents(path, &raw_perf_data); const string want_build_id = "abcdabcd"; std::map<string, string> build_ids = { {"[kernel.kallsyms]", want_build_id}}; // Test RawPerfData input. const ProcessProfiles pps = RawPerfDataToProfiles( reinterpret_cast<const void*>(raw_perf_data.c_str()), raw_perf_data.size(), build_ids); std::unordered_set<string> all_build_ids = AllBuildIDs(pps); EXPECT_THAT(all_build_ids, Contains(want_build_id)); } TEST_F(PerfDataConverterTest, HandlesKernelMmapOverlappingUserCode) { string path = GetResource("testdata" "/perf-overlapping-kernel-mapping.pb_proto"); string asciiPb; GetContents(path, &asciiPb); PerfDataProto perf_data_proto; ASSERT_TRUE(google::protobuf::TextFormat::ParseFromString(asciiPb, &perf_data_proto)); ProcessProfiles pps = PerfDataProtoToProfiles(&perf_data_proto); EXPECT_EQ(1, pps.size()); const auto& profile = pps[0]->data; EXPECT_EQ(3, profile.sample_size()); EXPECT_EQ(2, profile.mapping_size()); EXPECT_EQ(1000, profile.mapping(0).memory_start()); // user int64 user_mapping_id = profile.mapping(0).id(); EXPECT_EQ(0, profile.mapping(1).memory_start()); // kernel int64 kernel_mapping_id = profile.mapping(1).id(); EXPECT_EQ(3, profile.location_size()); EXPECT_EQ(kernel_mapping_id, profile.location(0).mapping_id()); EXPECT_EQ(user_mapping_id, profile.location(1).mapping_id()); EXPECT_EQ(kernel_mapping_id, profile.location(2).mapping_id()); } TEST_F(PerfDataConverterTest, PerfInfoSavedInComment) { string path = GetResource( "testdata" "/single-event-single-process.perf.data"); string raw_perf_data; GetContents(path, &raw_perf_data); const string want_version = "perf-version:3.16.7-ckt20"; const string want_command = "perf-command:/usr/bin/perf_3.16 record ./a.out"; // Test RawPerfData input. const ProcessProfiles pps = RawPerfDataToProfiles( reinterpret_cast<const void*>(raw_perf_data.c_str()), raw_perf_data.size(), {}); std::unordered_set<string> comments = AllComments(pps); EXPECT_THAT(comments, Contains(want_version)); EXPECT_THAT(comments, Contains(want_command)); } } // namespace perftools int main(int argc, char** argv) { testing::InitGoogleTest(&argc, argv); return RUN_ALL_TESTS(); }