/*
* 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();
}