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