blob: c24b8bc9d0c1dda67969d56a62be088d73722825 [file] [log] [blame]
// Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <sys/capability.h>
#include <sys/mount.h>
#include <sys/types.h>
#include <unistd.h>
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <vector>
#include "base/logging.h"
#include "chromiumos-wide-profiling/compat/string.h"
#include "chromiumos-wide-profiling/compat/test.h"
#include "chromiumos-wide-profiling/compat/thread.h"
#include "chromiumos-wide-profiling/dso_test_utils.h"
#include "chromiumos-wide-profiling/perf_parser.h"
#include "chromiumos-wide-profiling/perf_reader.h"
#include "chromiumos-wide-profiling/perf_test_files.h"
#include "chromiumos-wide-profiling/scoped_temp_path.h"
#include "chromiumos-wide-profiling/test_perf_data.h"
#include "chromiumos-wide-profiling/test_utils.h"
#include "chromiumos-wide-profiling/utils.h"
namespace quipper {
using SampleEvent = PerfDataProto_SampleEvent;
using SampleInfo = PerfDataProto_SampleInfo;
using PerfEvent = PerfDataProto_PerfEvent;
namespace {
// Any run of perf should have MMAPs with the following substrings.
const char* kExpectedFilenameSubstrings[] = {
"perf",
"kernel",
"libc",
};
void CheckChronologicalOrderOfEvents(const PerfReader& reader) {
if (reader.events().empty())
return;
const auto& events = reader.events();
uint64_t prev_time = GetTimeFromPerfEvent(events.Get(0));
for (int i = 1; i < events.size(); ++i) {
uint64_t new_time = GetTimeFromPerfEvent(events.Get(i));
CHECK_LE(prev_time, new_time);
prev_time = new_time;
}
}
void CheckNoDuplicates(const std::vector<string>& list) {
std::set<string> list_as_set(list.begin(), list.end());
if (list.size() != list_as_set.size())
ADD_FAILURE() << "Given list has at least one duplicate";
}
void CheckForElementWithSubstring(string substring_to_find,
const std::vector<string>& list) {
std::vector<string>::const_iterator iter;
for (iter = list.begin(); iter != list.end(); ++iter)
if (iter->find(substring_to_find) != string::npos)
return;
ADD_FAILURE() << substring_to_find
<< " is not present in any of the elements of the given list";
}
void CreateFilenameToBuildIDMap(
const std::vector<string>& filenames,
unsigned int seed,
std::map<string, string>* filenames_to_build_ids) {
srand(seed);
// Only use every other filename, so that half the filenames are unused.
for (size_t i = 0; i < filenames.size(); i += 2) {
u8 build_id[kBuildIDArraySize];
for (size_t j = 0; j < kBuildIDArraySize; ++j)
build_id[j] = rand_r(&seed);
(*filenames_to_build_ids)[filenames[i]] =
RawDataToHexString(build_id, kBuildIDArraySize);
}
}
// Given a PerfReader that has already consumed an input perf data file, inject
// new build IDs for the MMAP'd files in the perf data and check that they have
// been correctly injected.
void CheckFilenameAndBuildIDMethods(PerfReader* reader,
const string& output_perf_data_prefix,
unsigned int seed) {
// Check filenames.
std::vector<string> filenames;
reader->GetFilenames(&filenames);
ASSERT_FALSE(filenames.empty());
CheckNoDuplicates(filenames);
for (const char* substring : kExpectedFilenameSubstrings)
CheckForElementWithSubstring(substring, filenames);
std::set<string> filename_set;
reader->GetFilenamesAsSet(&filename_set);
// Make sure all MMAP filenames are in the set.
for (const auto& event : reader->events()) {
if (event.header().type() == PERF_RECORD_MMAP) {
EXPECT_TRUE(filename_set.find(event.mmap_event().filename()) !=
filename_set.end())
<< event.mmap_event().filename()
<< " is not present in the filename set";
}
}
std::map<string, string> expected_map;
reader->GetFilenamesToBuildIDs(&expected_map);
// Inject some made up build ids.
std::map<string, string> filenames_to_build_ids;
CreateFilenameToBuildIDMap(filenames, seed, &filenames_to_build_ids);
ASSERT_TRUE(reader->InjectBuildIDs(filenames_to_build_ids));
// Reader should now correctly populate the filenames to build ids map.
std::map<string, string>::const_iterator it;
for (it = filenames_to_build_ids.begin();
it != filenames_to_build_ids.end();
++it) {
expected_map[it->first] = it->second;
}
std::map<string, string> reader_map;
reader->GetFilenamesToBuildIDs(&reader_map);
ASSERT_EQ(expected_map, reader_map);
string output_perf_data1 = output_perf_data_prefix + ".parse.inject.out";
ASSERT_TRUE(reader->WriteFile(output_perf_data1));
// Perf should find the same build ids.
std::map<string, string> perf_build_id_map;
ASSERT_TRUE(GetPerfBuildIDMap(output_perf_data1, &perf_build_id_map));
ASSERT_EQ(expected_map, perf_build_id_map);
std::map<string, string> build_id_localizer;
// Only localize the first half of the files which have build ids.
for (size_t j = 0; j < filenames.size() / 2; ++j) {
string old_filename = filenames[j];
if (expected_map.find(old_filename) == expected_map.end())
continue;
string build_id = expected_map[old_filename];
string new_filename = old_filename + ".local";
filenames[j] = new_filename;
build_id_localizer[build_id] = new_filename;
expected_map[new_filename] = build_id;
expected_map.erase(old_filename);
}
reader->Localize(build_id_localizer);
// Filenames should be the same.
std::vector<string> new_filenames;
reader->GetFilenames(&new_filenames);
std::sort(filenames.begin(), filenames.end());
ASSERT_EQ(filenames, new_filenames);
// Build ids should be updated.
reader_map.clear();
reader->GetFilenamesToBuildIDs(&reader_map);
ASSERT_EQ(expected_map, reader_map);
string output_perf_data2 = output_perf_data_prefix + ".parse.localize.out";
ASSERT_TRUE(reader->WriteFile(output_perf_data2));
perf_build_id_map.clear();
ASSERT_TRUE(GetPerfBuildIDMap(output_perf_data2, &perf_build_id_map));
EXPECT_EQ(expected_map, perf_build_id_map);
std::map<string, string> filename_localizer;
// Only localize every third filename.
for (size_t j = 0; j < filenames.size(); j += 3) {
string old_filename = filenames[j];
string new_filename = old_filename + ".local2";
filenames[j] = new_filename;
filename_localizer[old_filename] = new_filename;
if (expected_map.find(old_filename) != expected_map.end()) {
string build_id = expected_map[old_filename];
expected_map[new_filename] = build_id;
expected_map.erase(old_filename);
}
}
reader->LocalizeUsingFilenames(filename_localizer);
// Filenames should be the same.
new_filenames.clear();
reader->GetFilenames(&new_filenames);
std::sort(filenames.begin(), filenames.end());
EXPECT_EQ(filenames, new_filenames);
// Build ids should be updated.
reader_map.clear();
reader->GetFilenamesToBuildIDs(&reader_map);
EXPECT_EQ(expected_map, reader_map);
string output_perf_data3 = output_perf_data_prefix + ".parse.localize2.out";
ASSERT_TRUE(reader->WriteFile(output_perf_data3));
perf_build_id_map.clear();
ASSERT_TRUE(GetPerfBuildIDMap(output_perf_data3, &perf_build_id_map));
EXPECT_EQ(expected_map, perf_build_id_map);
}
} // namespace
TEST(PerfParserTest, TestDSOAndOffsetConstructor) {
// DSOAndOffset contains a pointer to a dso info struct. Make sure this is
// initialized in a way such that DSOAndOffset::dso_name() executes without
// segfault and returns an empty string.
ParsedEvent::DSOAndOffset dso_and_offset;
EXPECT_TRUE(dso_and_offset.dso_name().empty());
}
TEST(PerfParserTest, NormalPerfData) {
ScopedTempDir output_dir;
ASSERT_FALSE(output_dir.path().empty());
string output_path = output_dir.path();
int seed = 0;
for (const char* test_file : perf_test_files::GetPerfDataFiles()) {
string input_perf_data = GetTestInputFilePath(test_file);
LOG(INFO) << "Testing " << input_perf_data;
PerfReader reader;
ASSERT_TRUE(reader.ReadFile(input_perf_data));
// Test the PerfReader stage of the processing before continuing.
string pr_output_perf_data = output_path + test_file + ".pr.out";
ASSERT_TRUE(reader.WriteFile(pr_output_perf_data));
EXPECT_TRUE(CheckPerfDataAgainstBaseline(pr_output_perf_data));
// Run it through PerfParser.
PerfParserOptions options = GetTestOptions();
options.sort_events_by_time = true;
PerfParser parser(&reader, options);
ASSERT_TRUE(parser.ParseRawEvents());
CHECK_GT(parser.parsed_events().size(), 0U);
CheckChronologicalOrderOfEvents(reader);
// Check perf event stats.
const PerfEventStats& stats = parser.stats();
EXPECT_GT(stats.num_sample_events, 0U);
EXPECT_GT(stats.num_mmap_events, 0U);
EXPECT_GT(stats.num_sample_events_mapped, 0U);
EXPECT_FALSE(stats.did_remap);
string parsed_perf_data = output_path + test_file + ".parse.out";
ASSERT_TRUE(reader.WriteFile(parsed_perf_data));
EXPECT_TRUE(CheckPerfDataAgainstBaseline(parsed_perf_data));
EXPECT_TRUE(ComparePerfBuildIDLists(input_perf_data, parsed_perf_data));
// Run the event parsing again, this time with remapping.
options = PerfParserOptions();
options.do_remap = true;
parser.set_options(options);
ASSERT_TRUE(parser.ParseRawEvents());
// Check perf event stats.
EXPECT_GT(stats.num_sample_events, 0U);
EXPECT_GT(stats.num_mmap_events, 0U);
EXPECT_GT(stats.num_sample_events_mapped, 0U);
EXPECT_TRUE(stats.did_remap);
// Remapped addresses should not match the original addresses.
string remapped_perf_data = output_path + test_file + ".parse.remap.out";
ASSERT_TRUE(reader.WriteFile(remapped_perf_data));
EXPECT_TRUE(CheckPerfDataAgainstBaseline(remapped_perf_data));
// Remapping again should produce the same addresses.
LOG(INFO) << "Reading in remapped data: " << remapped_perf_data;
PerfReader remap_reader;
ASSERT_TRUE(remap_reader.ReadFile(remapped_perf_data));
PerfParser remap_parser(&remap_reader, options);
ASSERT_TRUE(remap_parser.ParseRawEvents());
const PerfEventStats& remap_stats = remap_parser.stats();
EXPECT_GT(remap_stats.num_sample_events, 0U);
EXPECT_GT(remap_stats.num_mmap_events, 0U);
EXPECT_GT(remap_stats.num_sample_events_mapped, 0U);
EXPECT_TRUE(remap_stats.did_remap);
ASSERT_EQ(stats.num_sample_events, remap_stats.num_sample_events);
ASSERT_EQ(stats.num_mmap_events, remap_stats.num_mmap_events);
ASSERT_EQ(stats.num_sample_events_mapped,
remap_stats.num_sample_events_mapped);
string remapped_perf_data2 = output_path + test_file + ".parse.remap2.out";
ASSERT_TRUE(remap_reader.WriteFile(remapped_perf_data2));
// No need to call CheckPerfDataAgainstBaseline again. Just compare
// ParsedEvents.
const auto& parser_events = parser.parsed_events();
const auto& remap_parser_events = remap_parser.parsed_events();
EXPECT_EQ(parser_events.size(), remap_parser_events.size());
EXPECT_TRUE(std::equal(parser_events.begin(), parser_events.end(),
remap_parser_events.begin()));
EXPECT_TRUE(
ComparePerfBuildIDLists(remapped_perf_data, remapped_perf_data2));
// This must be called when |reader| is no longer going to be used, as it
// modifies the contents of |reader|.
CheckFilenameAndBuildIDMethods(&reader, output_path + test_file, seed);
++seed;
}
}
TEST(PerfParserTest, PipedModePerfData) {
ScopedTempDir output_dir;
ASSERT_FALSE(output_dir.path().empty());
string output_path = output_dir.path();
int seed = 0;
for (const char* test_file : perf_test_files::GetPerfPipedDataFiles()) {
string input_perf_data = GetTestInputFilePath(test_file);
LOG(INFO) << "Testing " << input_perf_data;
string output_perf_data = output_path + test_file + ".pr.out";
PerfReader reader;
ASSERT_TRUE(reader.ReadFile(input_perf_data));
// Check results from the PerfReader stage.
ASSERT_TRUE(reader.WriteFile(output_perf_data));
EXPECT_TRUE(CheckPerfDataAgainstBaseline(output_perf_data));
PerfParserOptions options = GetTestOptions();
options.do_remap = true;
options.sort_events_by_time = true;
PerfParser parser(&reader, options);
ASSERT_TRUE(parser.ParseRawEvents());
EXPECT_GT(parser.stats().num_sample_events, 0U);
EXPECT_GT(parser.stats().num_mmap_events, 0U);
EXPECT_GT(parser.stats().num_sample_events_mapped, 0U);
EXPECT_TRUE(parser.stats().did_remap);
// This must be called when |reader| is no longer going to be used, as it
// modifies the contents of |reader|.
CheckFilenameAndBuildIDMethods(&reader, output_path + test_file, seed);
++seed;
}
}
TEST(PerfParserTest, MapsSampleEventIp) {
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP |
PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP
testing::ExampleMmapEvent(
1001, 0x1c1000, 0x1000, 0, "/usr/lib/foo.so",
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 0
// becomes: 0x0000, 0x1000, 0
testing::ExampleMmapEvent(
1001, 0x1c3000, 0x2000, 0x2000, "/usr/lib/bar.so",
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 1
// becomes: 0x1000, 0x2000, 0
// PERF_RECORD_MMAP2
testing::ExampleMmap2Event(
1002, 0x2c1000, 0x2000, 0, "/usr/lib/baz.so",
testing::SampleInfo().Tid(1002)).WriteTo(&input); // 2
// becomes: 0x0000, 0x2000, 0
testing::ExampleMmap2Event(
1002, 0x2c3000, 0x1000, 0x3000, "/usr/lib/xyz.so",
testing::SampleInfo().Tid(1002)).WriteTo(&input); // 3
// becomes: 0x1000, 0x1000, 0
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001)) // 4
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c100a).Tid(1001)) // 5
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c3fff).Tid(1001)) // 6
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c2bad).Tid(1001)) // 7 (not mapped)
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000002c100a).Tid(1002)) // 8
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000002c5bad).Tid(1002)) // 9 (not mapped)
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000002c300b).Tid(1002)) // 10
.WriteTo(&input);
// not mapped yet:
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000002c400b).Tid(1002)) // 11
.WriteTo(&input);
testing::ExampleMmap2Event(
1002, 0x2c4000, 0x1000, 0, "/usr/lib/new.so",
testing::SampleInfo().Tid(1002)).WriteTo(&input); // 12
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000002c400b).Tid(1002)) // 13
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.sample_mapping_percentage_threshold = 0;
options.do_remap = true;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(5, parser.stats().num_mmap_events);
EXPECT_EQ(9, parser.stats().num_sample_events);
EXPECT_EQ(6, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(14, events.size());
// MMAPs
EXPECT_EQ(PERF_RECORD_MMAP, events[0].event_ptr->header().type());
EXPECT_EQ("/usr/lib/foo.so", events[0].event_ptr->mmap_event().filename());
EXPECT_EQ(0x0000, events[0].event_ptr->mmap_event().start());
EXPECT_EQ(0x1000, events[0].event_ptr->mmap_event().len());
EXPECT_EQ(0, events[0].event_ptr->mmap_event().pgoff());
EXPECT_EQ(PERF_RECORD_MMAP, events[1].event_ptr->header().type());
EXPECT_EQ("/usr/lib/bar.so", events[1].event_ptr->mmap_event().filename());
EXPECT_EQ(0x1000, events[1].event_ptr->mmap_event().start());
EXPECT_EQ(0x2000, events[1].event_ptr->mmap_event().len());
EXPECT_EQ(0x2000, events[1].event_ptr->mmap_event().pgoff());
EXPECT_EQ(PERF_RECORD_MMAP2, events[2].event_ptr->header().type());
EXPECT_EQ("/usr/lib/baz.so", events[2].event_ptr->mmap_event().filename());
EXPECT_EQ(0x0000, events[2].event_ptr->mmap_event().start());
EXPECT_EQ(0x2000, events[2].event_ptr->mmap_event().len());
EXPECT_EQ(0, events[2].event_ptr->mmap_event().pgoff());
EXPECT_EQ(PERF_RECORD_MMAP2, events[3].event_ptr->header().type());
EXPECT_EQ("/usr/lib/xyz.so", events[3].event_ptr->mmap_event().filename());
EXPECT_EQ(0x2000, events[3].event_ptr->mmap_event().start());
EXPECT_EQ(0x1000, events[3].event_ptr->mmap_event().len());
EXPECT_EQ(0x3000, events[3].event_ptr->mmap_event().pgoff());
// SAMPLEs
EXPECT_EQ(PERF_RECORD_SAMPLE, events[4].event_ptr->header().type());
EXPECT_EQ("/usr/lib/foo.so", events[4].dso_and_offset.dso_name());
EXPECT_EQ(0x0, events[4].dso_and_offset.offset());
EXPECT_EQ(0x0, events[4].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[5].event_ptr->header().type());
EXPECT_EQ("/usr/lib/foo.so", events[5].dso_and_offset.dso_name());
EXPECT_EQ(0xa, events[5].dso_and_offset.offset());
EXPECT_EQ(0xa, events[5].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[6].event_ptr->header().type());
EXPECT_EQ("/usr/lib/bar.so", events[6].dso_and_offset.dso_name());
EXPECT_EQ(0x2fff, events[6].dso_and_offset.offset());
EXPECT_EQ(0x1fff, events[6].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[7].event_ptr->header().type());
EXPECT_EQ(0x00000000001c2bad, events[7].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[8].event_ptr->header().type());
EXPECT_EQ("/usr/lib/baz.so", events[8].dso_and_offset.dso_name());
EXPECT_EQ(0xa, events[8].dso_and_offset.offset());
EXPECT_EQ(0xa, events[8].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[9].event_ptr->header().type());
EXPECT_EQ(0x00000000002c5bad, events[9].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[10].event_ptr->header().type());
EXPECT_EQ("/usr/lib/xyz.so", events[10].dso_and_offset.dso_name());
EXPECT_EQ(0x300b, events[10].dso_and_offset.offset());
EXPECT_EQ(0x200b, events[10].event_ptr->sample_event().ip());
// not mapped yet:
EXPECT_EQ(PERF_RECORD_SAMPLE, events[11].event_ptr->header().type());
EXPECT_EQ(0x00000000002c400b, events[11].event_ptr->sample_event().ip());
EXPECT_EQ(PERF_RECORD_MMAP2, events[12].event_ptr->header().type());
EXPECT_EQ("/usr/lib/new.so", events[12].event_ptr->mmap_event().filename());
EXPECT_EQ(0x3000, events[12].event_ptr->mmap_event().start());
EXPECT_EQ(0x1000, events[12].event_ptr->mmap_event().len());
EXPECT_EQ(0, events[12].event_ptr->mmap_event().pgoff());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[13].event_ptr->header().type());
EXPECT_EQ("/usr/lib/new.so", events[13].dso_and_offset.dso_name());
EXPECT_EQ(0xb, events[13].dso_and_offset.offset());
EXPECT_EQ(0x300b, events[13].event_ptr->sample_event().ip());
}
TEST(PerfParserTest, DsoInfoHasBuildId) {
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP |
PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP
testing::ExampleMmapEvent(
1001, 0x1c1000, 0x1000, 0, "/usr/lib/foo.so",
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 0
// becomes: 0x0000, 0x1000, 0
testing::ExampleMmapEvent(
1001, 0x1c3000, 0x2000, 0x2000, "/usr/lib/bar.so",
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 1
// becomes: 0x1000, 0x2000, 0
// PERF_RECORD_HEADER_BUILDID // N/A
string build_id_filename("/usr/lib/foo.so\0", 2*sizeof(u64));
ASSERT_EQ(0, build_id_filename.size() % sizeof(u64)) << "Sanity check";
const size_t event_size =
sizeof(struct build_id_event) +
build_id_filename.size();
const struct build_id_event event = {
.header = {
.type = PERF_RECORD_HEADER_BUILD_ID,
.misc = 0,
.size = static_cast<u16>(event_size),
},
.pid = -1,
.build_id = {0xde, 0xad, 0xf0, 0x0d },
};
input.write(reinterpret_cast<const char*>(&event), sizeof(event));
input.write(build_id_filename.data(), build_id_filename.size());
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001)) // 2
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c300a).Tid(1001)) // 3
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(2, parser.stats().num_mmap_events);
EXPECT_EQ(2, parser.stats().num_sample_events);
EXPECT_EQ(2, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(4, events.size());
EXPECT_EQ("/usr/lib/foo.so", events[2].dso_and_offset.dso_name());
EXPECT_EQ("deadf00d00000000000000000000000000000000",
events[2].dso_and_offset.build_id());
EXPECT_EQ("/usr/lib/bar.so", events[3].dso_and_offset.dso_name());
EXPECT_EQ("", events[3].dso_and_offset.build_id());
}
// Check the process has a Linux capability. See libcap(3) and capabilities(7).
bool HaveCapability(cap_value_t capability) {
cap_t capabilities = cap_get_proc();
cap_flag_value_t value;
CHECK_EQ(cap_get_flag(capabilities, capability, CAP_EFFECTIVE, &value), 0);
cap_free(capabilities);
return value == CAP_SET;
}
class RunInMountNamespaceThread : public quipper::Thread {
public:
explicit RunInMountNamespaceThread(string tmpdir, string mntdir)
: quipper::Thread("MntNamespace"),
tmpdir_(tmpdir), mntdir_(mntdir) {
}
void Start() override {
quipper::Thread::Start();
ready.Wait();
}
void Join() override {
exit.Notify();
quipper::Thread::Join();
}
private:
void Run() override {
CHECK_EQ(unshare(CLONE_NEWNS), 0);
CHECK_EQ(mount(tmpdir_.c_str(), mntdir_.c_str(),
nullptr, MS_BIND, nullptr), 0);
ready.Notify();
exit.Wait();
}
Notification ready;
Notification exit;
string tmpdir_;
string mntdir_;
};
// Root task <pid>/<pid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// "/tmp/quipper_mnt.../file_in_namespace" (Doesn't exist)
// Container task <pid>/<tid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: deadbeef ino: X
// <path> = marked with *
// MMAP2: <pid+10>/<tid+1>, <path>, ino: X
// MMAP2: <pid>/<tid>, <path>, ino: X
// Reject (doesn't exist): /proc/<tid+10>/root/<path>
// Reject (doesn't exist): /proc/<pid+1>/root/<path>
// Accept: /proc/<tid>/root/<path>
// (Not tried): /proc/<pid>/root/<path>
// (Not tried): /<path>
// Expected buildid for <path>: "deadbeef"
TEST(PerfParserTest, ReadsBuildidsInMountNamespace) {
if (!HaveCapability(CAP_SYS_ADMIN))
return; // Skip test.
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
ScopedTempDir mntdir("/tmp/quipper_mnt.");
RunInMountNamespaceThread thread(tmpdir.path(), mntdir.path());
thread.Start();
const pid_t pid = getpid();
const pid_t tid = thread.tid();
const string tmpfile = tmpdir.path() + "file_in_namespace";
const string tmpfile_in_ns = mntdir.path() + "file_in_namespace";
InitializeLibelf();
testing::WriteElfWithBuildid(tmpfile, ".note.gnu.build-id",
"\xde\xad\xbe\xef");
struct stat tmp_stat;
ASSERT_NE(stat(tmpfile_in_ns.c_str(), &tmp_stat), 0);
ASSERT_EQ(stat(tmpfile.c_str(), &tmp_stat), 0);
// Create perf.data
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP2
// - mmap from a process and thread that doesn't exist
testing::ExampleMmap2Event(
pid, tid, 0x1c1000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid+10, tid+1))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev),
tmp_stat.st_ino)
.WriteTo(&input); // 0
// - mmap from a running thread
testing::ExampleMmap2Event(
pid, tid, 0x1c2000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid, tid))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev),
tmp_stat.st_ino)
.WriteTo(&input); // 1
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(pid, tid)) // 2
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(2, parser.stats().num_mmap_events);
EXPECT_EQ(1, parser.stats().num_sample_events);
EXPECT_EQ(1, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(3, events.size());
EXPECT_EQ(tmpfile_in_ns, events[2].dso_and_offset.dso_name());
EXPECT_EQ("deadbeef", events[2].dso_and_offset.build_id());
thread.Join();
}
class RunInMountNamespaceProcess {
public:
RunInMountNamespaceProcess(string tmpdir, string mntdir)
: pid_(0), tmpdir_(tmpdir), mntdir_(mntdir) {
}
void Start() {
int pipe_fd[2];
int nonce = 0;
CHECK_EQ(pipe(pipe_fd), 0) << "pipe: " << strerror(errno);
pid_ = fork();
CHECK_NE(-1, pid_) << "fork: " << strerror(errno);
if (pid_ == 0) { // child
close(pipe_fd[0]);
CHECK_EQ(unshare(CLONE_NEWNS), 0);
CHECK_EQ(mount(tmpdir_.c_str(), mntdir_.c_str(),
nullptr, MS_BIND, nullptr), 0);
// Tell parent mounting is done.
CHECK_EQ(write(pipe_fd[1], &nonce, sizeof(nonce)),
static_cast<ssize_t>(sizeof(nonce)));
close(pipe_fd[1]);
pause(); // Wait to be killed. (So morbid.)
std::_Exit(-1);
}
close(pipe_fd[1]);
// Wait for child to tell us it has mounted the dir.
ssize_t sz = read(pipe_fd[0], &nonce, sizeof(nonce));
CHECK_EQ(sz, static_cast<ssize_t>(sizeof(nonce)))
<< "read: " << strerror(errno);
close(pipe_fd[0]);
}
void Exit() {
kill(pid_, SIGTERM);
waitpid(pid_, nullptr, 0);
}
pid_t pid() { return pid_; }
private:
pid_t pid_;
string tmpdir_;
string mntdir_;
};
// Root task <pid>/<pid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: baadf00d ino: Y
// Container task <pid2>/<pid2> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: deadbeef ino: X
// <path> = marked with *
// MMAP2: <pid2>/<pid2+1>, <path>, ino: X
// Reject (doesn't exist): /proc/<pid2+1>/root/<path>
// Accept: /proc/<pid2>/root/<path>
// (Not tried): /<path>
// Expected buildid for <path>: "deadbeef"
TEST(PerfParserTest, ReadsBuildidsInMountNamespace_TriesOwningProcess) {
if (!HaveCapability(CAP_SYS_ADMIN))
return; // Skip test.
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
ScopedTempDir mntdir("/tmp/quipper_mnt.");
RunInMountNamespaceProcess process(tmpdir.path(), mntdir.path());
process.Start();
// Pretend we launched a thread in the other process, it mapped the file,
// and then exited. Let's make up a tid for it that's not likely to exist.
const pid_t pid = process.pid();
const pid_t tid = pid + 1;
const string tmpfile = tmpdir.path() + "file_in_namespace";
const string tmpfile_in_ns = mntdir.path() + "file_in_namespace";
InitializeLibelf();
testing::WriteElfWithBuildid(tmpfile, ".note.gnu.build-id",
"\xde\xad\xbe\xef");
// It's a trap! If "baadf00d" is seen, we read the wrong file.
testing::WriteElfWithBuildid(tmpfile_in_ns, ".note.gnu.build-id",
"\xba\xad\xf0\x0d");
struct stat tmp_stat;
ASSERT_EQ(stat(tmpfile.c_str(), &tmp_stat), 0);
// Create perf.data
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP2
testing::ExampleMmap2Event(
pid, tid, 0x1c1000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid, tid))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev),
tmp_stat.st_ino)
.WriteTo(&input); // 0
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(pid, tid)) // 1
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(1, parser.stats().num_mmap_events);
EXPECT_EQ(1, parser.stats().num_sample_events);
EXPECT_EQ(1, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(2, events.size());
EXPECT_EQ(tmpfile_in_ns, events[1].dso_and_offset.dso_name());
EXPECT_EQ("deadbeef", events[1].dso_and_offset.build_id());
process.Exit();
}
// Root task <pid>/<pid> Filesystem:
// * "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// "/tmp/quipper_mnt.../file_in_namespace" buildid: baadf00d ino: Y
// Container task <pid>/<tid> Filesystem:
// * "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// "/tmp/quipper_mnt.../file_in_namespace" buildid: deadbeef ino: X
// <path> = marked with *
// MMAP2: <pid+10>/<tid+1>, <path>, ino: X
// Reject (doesn't exist): /proc/<tid+1>/root/<path>
// Reject (doesn't exist): /proc/<pid+10>/root/<path>
// Accept (same inode): /<path>
// Expected buildid for <path>: "deadbeef"
TEST(PerfParserTest, ReadsBuildidsInMountNamespace_TriesRootFs) {
if (!HaveCapability(CAP_SYS_ADMIN))
return; // Skip test.
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
ScopedTempDir mntdir("/tmp/quipper_mnt.");
RunInMountNamespaceThread thread(tmpdir.path(), mntdir.path());
thread.Start();
const pid_t pid = getpid();
const pid_t tid = thread.tid();
const string tmpfile = tmpdir.path() + "file_in_namespace";
const string tmpfile_in_ns = mntdir.path() + "file_in_namespace";
InitializeLibelf();
testing::WriteElfWithBuildid(tmpfile, ".note.gnu.build-id",
"\xde\xad\xbe\xef");
// It's a trap! If "baadf00d" is seen, we read the wrong file.
testing::WriteElfWithBuildid(tmpfile_in_ns, ".note.gnu.build-id",
"\xba\xad\xf0\x0d");
struct stat tmp_stat;
ASSERT_EQ(stat(tmpfile.c_str(), &tmp_stat), 0);
// Create perf.data
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP2
// - Process doesn't exist, but file exists in our own namespace.
testing::ExampleMmap2Event(
pid, pid, 0x1c1000, 0x1000, 0, tmpfile,
testing::SampleInfo().Tid(pid+10, tid+1))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev),
tmp_stat.st_ino)
.WriteTo(&input); // 0
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(pid)) // 1
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(1, parser.stats().num_mmap_events);
EXPECT_EQ(1, parser.stats().num_sample_events);
EXPECT_EQ(1, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(2, events.size());
EXPECT_EQ(tmpfile, events[1].dso_and_offset.dso_name());
// Finds file in root FS.
EXPECT_EQ("deadbeef", events[1].dso_and_offset.build_id());
thread.Join();
}
// Root task <pid>/<pid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// "/tmp/quipper_mnt.../file_in_namespace" buildid: baadf00d ino: Y
// Container task <pid>/<tid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: deadbeef ino: X
// <path> = marked with *
// MMAP2: <pid>/<tid>, <path>, ino: X+1
// Reject (wrong inode): /proc/<tid>/root/<path>
// Reject (wrong inode): /proc/<pid>/root/<path>
// Reject (wrong inode): /<path>
// Expected buildid for <path>: ""
TEST(PerfParserTest, ReadsBuildidsInMountNamespace_TriesRootFsRejectsInode) {
if (!HaveCapability(CAP_SYS_ADMIN))
return; // Skip test.
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
ScopedTempDir mntdir("/tmp/quipper_mnt.");
RunInMountNamespaceThread thread(tmpdir.path(), mntdir.path());
thread.Start();
const pid_t pid = getpid();
const pid_t tid = thread.tid();
const string tmpfile = tmpdir.path() + "file_in_namespace";
const string tmpfile_in_ns = mntdir.path() + "file_in_namespace";
InitializeLibelf();
testing::WriteElfWithBuildid(tmpfile, ".note.gnu.build-id",
"\xde\xad\xbe\xef");
// It's a trap! If "baadf00d" is seen, we read the wrong file.
testing::WriteElfWithBuildid(tmpfile_in_ns, ".note.gnu.build-id",
"\xba\xad\xf0\x0d");
struct stat tmp_stat;
struct stat tmp_in_ns_stat;
ASSERT_EQ(stat(tmpfile.c_str(), &tmp_stat), 0);
ASSERT_EQ(stat(tmpfile_in_ns.c_str(), &tmp_in_ns_stat), 0);
// inodes are often issued sequentially, so go backwards rather than forwards.
const ino_t bad_ino = tmp_stat.st_ino - 1;
ASSERT_NE(bad_ino, tmp_in_ns_stat.st_ino);
// Create perf.data
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP2
// - Process doesn't exist, but file exists in our own namespace.
testing::ExampleMmap2Event(
pid, pid, 0x1c1000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid, tid))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev), bad_ino)
.WriteTo(&input); // 0
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(pid)) // 1
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(1, parser.stats().num_mmap_events);
EXPECT_EQ(1, parser.stats().num_sample_events);
EXPECT_EQ(1, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(2, events.size());
EXPECT_EQ(tmpfile_in_ns, events[1].dso_and_offset.dso_name());
// Wrong inode, so rejects all candidates.
EXPECT_EQ("", events[1].dso_and_offset.build_id());
thread.Join();
}
// Root task <pid>/<pid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: baadf00d ino: Y
// Container task <pid>/<tid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: deadbeef ino: X
// <path> = marked with *
// MMAP: <pid+10>/<tid+1>, <path>, ino: Not available
// Reject (not found): /proc/<tid+1>/root/<path>
// Reject (not found): /proc/<pid+10>/root/<path>
// Accept (falsely): /<path>
// Expected buildid for <path>: "baadf00d" (even though incorrect)
TEST(PerfParserTest, ReadsBuildidsInMountNamespace_TriesRootFsNoInodeToReject) {
if (!HaveCapability(CAP_SYS_ADMIN))
return; // Skip test.
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
ScopedTempDir mntdir("/tmp/quipper_mnt.");
RunInMountNamespaceThread thread(tmpdir.path(), mntdir.path());
thread.Start();
const pid_t pid = getpid();
const pid_t tid = thread.tid();
const string tmpfile = tmpdir.path() + "file_in_namespace";
const string tmpfile_in_ns = mntdir.path() + "file_in_namespace";
InitializeLibelf();
testing::WriteElfWithBuildid(tmpfile, ".note.gnu.build-id",
"\xde\xad\xf0\x0d");
// It's a trap! If "baadf00d" is seen, we read the wrong file.
testing::WriteElfWithBuildid(tmpfile_in_ns, ".note.gnu.build-id",
"\xba\xad\xf0\x0d");
struct stat tmp_stat;
ASSERT_EQ(stat(tmpfile.c_str(), &tmp_stat), 0);
// Create perf.data
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP
// - Process & thread don't exist, but file exists in our own namespace.
testing::ExampleMmapEvent(
pid, 0x1c1000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid+10, tid+1))
.WriteTo(&input); // 0
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(pid)) // 1
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(1, parser.stats().num_mmap_events);
EXPECT_EQ(1, parser.stats().num_sample_events);
EXPECT_EQ(1, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(2, events.size());
EXPECT_EQ(tmpfile_in_ns, events[1].dso_and_offset.dso_name());
// We'll read the wrong file b/c we couldn't reject based on inode:
EXPECT_EQ("baadf00d", events[1].dso_and_offset.build_id());
thread.Join();
}
// Root task <pid>/<pid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: baadf00d ino: Y
// Container task <pid>/<tid> Filesystem:
// "/tmp/quipper_tmp.../file_in_namespace" buildid: deadbeef ino: X
// * "/tmp/quipper_mnt.../file_in_namespace" buildid: deadbeef ino: X
// <path> = marked with *
// MMAP2(0): <pid>/<tid>, <path>, <maj+1>/<min>, ino: X
// MMAP2(1): <pid>/<tid>, <path>, <maj>/<min+1>, ino: X
// MMAP2(2): <pid>/<tid>, <path>, <maj>/<min>, ino: X+1
// SAMPLE(3): <pid>/<tid>, addr in MMAP2(0)
// SAMPLE(4): <pid>/<tid>, addr in MMAP2(1)
// SAMPLE(5): <pid>/<tid>, addr in MMAP2(2)
// Expected buildid for <path>: ""
//
// TODO(dhsharp): This test runs into an odd situation: the same path is seen
// with multiple device/ino numbers. This is really a shortcoming of perf--
// it can only associate a buildid with a path. If the same path exists in
// multiple containers but refers to different files (device/inode), then
// it's hard to know what to do. Similarly, PerfParser associates a DSO name
// (path) with a single DSOInfo and device/inode info therein, although it
// tracks all threads the DSO name was seen in. This test is set up such that
// all MMAPs should be rejected, but in truth PerfParser only compares the
// device/inode of the file against the device/inode of one of the MMAPs.
// A better thing to do might be to track a
// map<tuple<maj,min,ino,path>, DSOInfo>, but even so, it will be impossible
// to store unambiguously in perf.data.
TEST(PerfParserTest, ReadsBuildidsInMountNamespace_DifferentDevOrIno) {
if (!HaveCapability(CAP_SYS_ADMIN))
return; // Skip test.
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
ScopedTempDir mntdir("/tmp/quipper_mnt.");
RunInMountNamespaceThread thread(tmpdir.path(), mntdir.path());
thread.Start();
const pid_t pid = getpid();
const pid_t tid = thread.tid();
const string tmpfile = tmpdir.path() + "file_in_namespace";
const string tmpfile_in_ns = mntdir.path() + "file_in_namespace";
InitializeLibelf();
testing::WriteElfWithBuildid(tmpfile, ".note.gnu.build-id",
"\xde\xad\xf0\x0d");
// It's a trap! If "baadf00d" is seen, we read the wrong file.
testing::WriteElfWithBuildid(tmpfile_in_ns, ".note.gnu.build-id",
"\xba\xad\xf0\x0d");
struct stat tmp_stat;
struct stat tmp_in_ns_stat;
ASSERT_EQ(stat(tmpfile.c_str(), &tmp_stat), 0);
ASSERT_EQ(stat(tmpfile_in_ns.c_str(), &tmp_in_ns_stat), 0);
// inodes are often issued sequentially, so go backwards rather than forwards.
const ino_t bad_ino = tmp_stat.st_ino - 1;
ASSERT_NE(bad_ino, tmp_in_ns_stat.st_ino);
// Create perf.data
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP2
// - Wrong major number
testing::ExampleMmap2Event(
pid, tid, 0x1c1000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid, tid))
.WithDeviceInfo(major(tmp_stat.st_dev)+1, minor(tmp_stat.st_dev),
tmp_stat.st_ino)
.WriteTo(&input); // 0
// - Wrong minor number
testing::ExampleMmap2Event(
pid, tid, 0x1c2000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid, tid))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev)+1,
tmp_stat.st_ino)
.WriteTo(&input); // 1
// - Wrong inode number
testing::ExampleMmap2Event(
pid, tid, 0x1c3000, 0x1000, 0, tmpfile_in_ns,
testing::SampleInfo().Tid(pid, tid))
.WithDeviceInfo(major(tmp_stat.st_dev), minor(tmp_stat.st_dev),
bad_ino)
.WriteTo(&input); // 2
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c1000).Tid(pid, tid)) // 3
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c2000).Tid(pid, tid)) // 4
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c3000).Tid(pid, tid)) // 5
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(3, parser.stats().num_mmap_events);
EXPECT_EQ(3, parser.stats().num_sample_events);
EXPECT_EQ(3, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(6, events.size());
// Buildid should not be found for any of the samples.
for (int i : {3, 4, 5}) {
EXPECT_EQ(tmpfile_in_ns, events[i].dso_and_offset.dso_name());
EXPECT_EQ("", events[i].dso_and_offset.build_id());
}
thread.Join();
}
TEST(PerfParserTest, OverwriteBuildidIfAlreadyKnown) {
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
const string known_file = tmpdir.path() + "buildid_already_known";
const string known_file_to_overwrite =
tmpdir.path() + "buildid_already_known_overwrite";
const string unknown_file = tmpdir.path() + "buildid_not_known";
InitializeLibelf();
testing::WriteElfWithBuildid(known_file_to_overwrite, ".note.gnu.build-id",
"\xf0\x01\x57\xea");
testing::WriteElfWithBuildid(unknown_file, ".note.gnu.build-id",
"\xc0\x01\xd0\x0d");
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP
testing::ExampleMmapEvent(
1001, 0x1c1000, 0x1000, 0, known_file,
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 0
// becomes: 0x0000, 0x1000, 0
testing::ExampleMmapEvent(
1001, 0x1c2000, 0x2000, 0, known_file_to_overwrite,
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 1
// becomes: 0x1000, 0x2000, 0
testing::ExampleMmapEvent(
1001, 0x1c4000, 0x2000, 0x2000, unknown_file,
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 2
// becomes: 0x3000, 0x2000, 0x2000
// PERF_RECORD_HEADER_BUILDID // N/A
{
string build_id_filename(known_file);
build_id_filename.resize(Align<u64>(known_file.size())); // null-pad
const size_t event_size =
sizeof(struct build_id_event) +
build_id_filename.size();
const struct build_id_event event = {
.header = {
.type = PERF_RECORD_HEADER_BUILD_ID,
.misc = 0,
.size = static_cast<u16>(event_size),
},
.pid = -1,
.build_id = {0xde, 0xad, 0xbe, 0xef},
};
input.write(reinterpret_cast<const char*>(&event), sizeof(event));
input.write(build_id_filename.data(), build_id_filename.size());
}
// PERF_RECORD_HEADER_BUILDID // N/A
{
string build_id_filename(known_file_to_overwrite);
// null-pad
build_id_filename.resize(Align<u64>(known_file_to_overwrite.size()));
const size_t event_size =
sizeof(struct build_id_event) +
build_id_filename.size();
const struct build_id_event event = {
.header = {
.type = PERF_RECORD_HEADER_BUILD_ID,
.misc = 0,
.size = static_cast<u16>(event_size),
},
.pid = -1,
.build_id = {0xca, 0xfe, 0xba, 0xbe},
};
input.write(reinterpret_cast<const char*>(&event), sizeof(event));
input.write(build_id_filename.data(), build_id_filename.size());
}
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c100a).Tid(1001)) // 3
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c300b).Tid(1001)) // 4
.WriteTo(&input);
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c400c).Tid(1001)) // 5
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(3, parser.stats().num_mmap_events);
EXPECT_EQ(3, parser.stats().num_sample_events);
EXPECT_EQ(3, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(6, events.size());
EXPECT_EQ(known_file, events[3].dso_and_offset.dso_name());
EXPECT_EQ("deadbeef00000000000000000000000000000000",
events[3].dso_and_offset.build_id());
EXPECT_EQ(known_file_to_overwrite, events[4].dso_and_offset.dso_name());
EXPECT_EQ("f00157ea",
events[4].dso_and_offset.build_id());
EXPECT_EQ(unknown_file, events[5].dso_and_offset.dso_name());
EXPECT_EQ("c001d00d", events[5].dso_and_offset.build_id());
}
TEST(PerfParserTest, OnlyReadsBuildidIfSampled) {
ScopedTempDir tmpdir("/tmp/quipper_tmp.");
const string unknown_file = tmpdir.path() + "buildid_not_known";
InitializeLibelf();
testing::WriteElfWithBuildid(unknown_file, ".note.gnu.build-id",
"\xc0\x01\xd0\x0d");
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP
testing::ExampleMmapEvent(
1001, 0x1c1000, 0x1000, 0, unknown_file,
testing::SampleInfo().Tid(1001)).WriteTo(&input); // 0
// becomes: 0x0000, 0x1000, 0
// PERF_RECORD_SAMPLE
// - Sample outside mmap
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x00000000001c300a).Tid(1001)) // 1
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.read_missing_buildids = true;
options.sample_mapping_percentage_threshold = 0;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(1, parser.stats().num_mmap_events);
EXPECT_EQ(1, parser.stats().num_sample_events);
EXPECT_EQ(0, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(2, events.size());
EXPECT_EQ("", events[1].dso_and_offset.dso_name());
EXPECT_EQ("", events[1].dso_and_offset.build_id());
std::map<string, string> filenames_to_build_ids;
reader.GetFilenamesToBuildIDs(&filenames_to_build_ids);
auto it = filenames_to_build_ids.find(unknown_file);
EXPECT_EQ(filenames_to_build_ids.end(), it) << it->first << " "<< it->second;
}
TEST(PerfParserTest, HandlesFinishedRoundEventsAndSortsByTime) {
// For now at least, we are ignoring PERF_RECORD_FINISHED_ROUND events.
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// data
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP |
PERF_SAMPLE_TID |
PERF_SAMPLE_TIME,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP
testing::ExampleMmapEvent(
1001, 0x1c1000, 0x1000, 0, "/usr/lib/foo.so",
testing::SampleInfo().Tid(1001).Time(12300010)).WriteTo(&input);
// becomes: 0x0000, 0x1000, 0
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent( // 1
testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300020))
.WriteTo(&input);
testing::ExamplePerfSampleEvent( // 2
testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300030))
.WriteTo(&input);
// PERF_RECORD_FINISHED_ROUND
testing::FinishedRoundEvent().WriteTo(&input); // N/A
// PERF_RECORD_SAMPLE
testing::ExamplePerfSampleEvent( // 3
testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300050))
.WriteTo(&input);
testing::ExamplePerfSampleEvent( // 4
testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300040))
.WriteTo(&input);
// PERF_RECORD_FINISHED_ROUND
testing::FinishedRoundEvent().WriteTo(&input); // N/A
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.sample_mapping_percentage_threshold = 0;
options.sort_events_by_time = true;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(1, parser.stats().num_mmap_events);
EXPECT_EQ(4, parser.stats().num_sample_events);
EXPECT_EQ(4, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(5, events.size());
EXPECT_EQ(PERF_RECORD_MMAP, events[0].event_ptr->header().type());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[1].event_ptr->header().type());
EXPECT_EQ(12300020, events[1].event_ptr->sample_event().sample_time_ns());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[2].event_ptr->header().type());
EXPECT_EQ(12300030, events[2].event_ptr->sample_event().sample_time_ns());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[3].event_ptr->header().type());
EXPECT_EQ(12300040, events[3].event_ptr->sample_event().sample_time_ns());
EXPECT_EQ(PERF_RECORD_SAMPLE, events[4].event_ptr->header().type());
EXPECT_EQ(12300050, events[4].event_ptr->sample_event().sample_time_ns());
}
TEST(PerfParserTest, MmapCoversEntireAddressSpace) {
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP, a kernel mapping that covers the whole space.
const uint32_t kKernelMmapPid = UINT32_MAX;
testing::ExampleMmapEvent(
kKernelMmapPid, 0, UINT64_MAX, 0, "[kernel.kallsyms]_text",
testing::SampleInfo().Tid(kKernelMmapPid, 0)).WriteTo(&input);
// PERF_RECORD_MMAP, a shared object library.
testing::ExampleMmapEvent(
1234, 0x7f008e000000, 0x2000000, 0, "/usr/lib/libfoo.so",
testing::SampleInfo().Tid(1234, 1234)).WriteTo(&input);
// PERF_RECORD_SAMPLE, within library.
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x7f008e123456).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within kernel.
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x8000819e).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within library.
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x7f008fdeadbe).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within kernel.
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0xffffffff8100cafe).Tid(1234, 1235))
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.do_remap = true;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(2, parser.stats().num_mmap_events);
EXPECT_EQ(4, parser.stats().num_sample_events);
EXPECT_EQ(4, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(6, events.size());
EXPECT_EQ(PERF_RECORD_MMAP, events[0].event_ptr->header().type());
EXPECT_EQ("[kernel.kallsyms]_text",
events[0].event_ptr->mmap_event().filename());
EXPECT_EQ(PERF_RECORD_MMAP, events[1].event_ptr->header().type());
EXPECT_EQ("/usr/lib/libfoo.so",
events[1].event_ptr->mmap_event().filename());
// Sample from library.
EXPECT_EQ(PERF_RECORD_SAMPLE, events[2].event_ptr->header().type());
EXPECT_EQ("/usr/lib/libfoo.so", events[2].dso_and_offset.dso_name());
EXPECT_EQ(0x123456, events[2].dso_and_offset.offset());
// Sample from kernel.
EXPECT_EQ(PERF_RECORD_SAMPLE, events[3].event_ptr->header().type());
EXPECT_EQ("[kernel.kallsyms]_text", events[3].dso_and_offset.dso_name());
EXPECT_EQ(0x8000819e, events[3].dso_and_offset.offset());
// Sample from library.
EXPECT_EQ(PERF_RECORD_SAMPLE, events[4].event_ptr->header().type());
EXPECT_EQ("/usr/lib/libfoo.so", events[4].dso_and_offset.dso_name());
EXPECT_EQ(0x1deadbe, events[4].dso_and_offset.offset());
// Sample from kernel.
EXPECT_EQ(PERF_RECORD_SAMPLE, events[5].event_ptr->header().type());
EXPECT_EQ("[kernel.kallsyms]_text", events[5].dso_and_offset.dso_name());
EXPECT_EQ(0xffffffff8100cafe, events[5].dso_and_offset.offset());
}
TEST(PerfParserTest, HugePagesMappings) {
std::stringstream input;
// header
testing::ExamplePipedPerfDataFileHeader().WriteTo(&input);
// PERF_RECORD_HEADER_ATTR
testing::ExamplePerfEventAttrEvent_Hardware(PERF_SAMPLE_IP | PERF_SAMPLE_TID,
true /*sample_id_all*/)
.WriteTo(&input);
// PERF_RECORD_MMAP, a normal mapping.
testing::ExampleMmapEvent(
1234, 0x40000000, 0x18000, 0, "/usr/lib/libfoo.so",
testing::SampleInfo().Tid(1234, 1234)).WriteTo(&input);
// PERF_RECORD_SAMPLE, within library.
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x40000100).Tid(1234, 1235))
.WriteTo(&input);
// Split Chrome mapping #1, with a huge pages section in the middle.
testing::ExampleMmapEvent(
1234, 0x40018000, 0x8000, 0, "/opt/google/chrome/chrome",
testing::SampleInfo().Tid(1234, 1234)).WriteTo(&input);
testing::ExampleMmapEvent(
1234, 0x40020000, 0x1e00000, 0, "//anon",
testing::SampleInfo().Tid(1234, 1234)).WriteTo(&input);
testing::ExampleMmapEvent(
1234, 0x41e20000, 0x3fe0000, 0x1e08000, "/opt/google/chrome/chrome",
testing::SampleInfo().Tid(1234, 1234)).WriteTo(&input);
// Split Chrome mapping #2, starting with a huge pages section (no preceding
// normal mapping).
testing::ExampleMmapEvent(
2345, 0x45e00000, 0x1e00000, 0, "//anon",
testing::SampleInfo().Tid(2345, 2346)).WriteTo(&input);
testing::ExampleMmapEvent(
2345, 0x47c00000, 0x4000000, 0x1e00000, "/opt/google/chrome/chrome",
testing::SampleInfo().Tid(2345, 2346)).WriteTo(&input);
// PERF_RECORD_SAMPLE, within Chrome #1 (before huge pages mapping).
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x40018300).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within Chrome #1 (within huge pages mapping).
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x40020400).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within Chrome #1 (after huge pages mapping).
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x41e20500).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within library.
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x40000700).Tid(1234, 1235))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within Chrome #2 (within huge pages mapping).
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x45e01300).Tid(2345, 2346))
.WriteTo(&input);
// PERF_RECORD_SAMPLE, within Chrome #2 (after huge pages mapping).
testing::ExamplePerfSampleEvent(
testing::SampleInfo().Ip(0x45e02f00).Tid(2345, 2346))
.WriteTo(&input);
//
// Parse input.
//
PerfReader reader;
EXPECT_TRUE(reader.ReadFromString(input.str()));
PerfParserOptions options;
options.combine_huge_pages_mappings = true;
PerfParser parser(&reader, options);
EXPECT_TRUE(parser.ParseRawEvents());
EXPECT_EQ(3, parser.stats().num_mmap_events);
EXPECT_EQ(7, parser.stats().num_sample_events);
EXPECT_EQ(7, parser.stats().num_sample_events_mapped);
const std::vector<ParsedEvent>& events = parser.parsed_events();
ASSERT_EQ(10, events.size());
EXPECT_EQ(PERF_RECORD_MMAP, events[0].event_ptr->header().type());
EXPECT_EQ("/usr/lib/libfoo.so", events[0].event_ptr->mmap_event().filename());
EXPECT_EQ(0x40000000, events[0].event_ptr->mmap_event().start());
EXPECT_EQ(0x18000, events[0].event_ptr->mmap_event().len());
EXPECT_EQ(0x0, events[0].event_ptr->mmap_event().pgoff());
// Sample from library.
EXPECT_EQ(PERF_RECORD_SAMPLE, events[1].event_ptr->header().type());
EXPECT_EQ("/usr/lib/libfoo.so", events[1].dso_and_offset.dso_name());
EXPECT_EQ(0x100, events[1].dso_and_offset.offset());
// The split Chrome mappings should have been combined.
EXPECT_EQ(PERF_RECORD_MMAP, events[2].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome",
events[2].event_ptr->mmap_event().filename());
EXPECT_EQ(0x40018000, events[2].event_ptr->mmap_event().start());
EXPECT_EQ(0x5de8000, events[2].event_ptr->mmap_event().len());
EXPECT_EQ(0x0, events[2].event_ptr->mmap_event().pgoff());
EXPECT_EQ(PERF_RECORD_MMAP, events[3].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome",
events[3].event_ptr->mmap_event().filename());
EXPECT_EQ(0x45e00000, events[3].event_ptr->mmap_event().start());
EXPECT_EQ(0x5e00000, events[3].event_ptr->mmap_event().len());
EXPECT_EQ(0x0, events[3].event_ptr->mmap_event().pgoff());
// Sample from Chrome (before huge pages mapping).
EXPECT_EQ(PERF_RECORD_SAMPLE, events[4].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome", events[4].dso_and_offset.dso_name());
EXPECT_EQ(0x300, events[4].dso_and_offset.offset());
// Sample from Chrome (within huge pages mapping).
EXPECT_EQ(PERF_RECORD_SAMPLE, events[5].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome", events[5].dso_and_offset.dso_name());
EXPECT_EQ(0x8400, events[5].dso_and_offset.offset());
// Sample from Chrome (after huge pages mapping).
EXPECT_EQ(PERF_RECORD_SAMPLE, events[6].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome", events[6].dso_and_offset.dso_name());
EXPECT_EQ(0x1e08500, events[6].dso_and_offset.offset());
// Sample from library.
EXPECT_EQ(PERF_RECORD_SAMPLE, events[7].event_ptr->header().type());
EXPECT_EQ("/usr/lib/libfoo.so", events[7].dso_and_offset.dso_name());
EXPECT_EQ(0x700, events[7].dso_and_offset.offset());
// Sample from Chrome #2 (within huge pages mapping).
EXPECT_EQ(PERF_RECORD_SAMPLE, events[8].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome", events[8].dso_and_offset.dso_name());
EXPECT_EQ(0x1300, events[8].dso_and_offset.offset());
// Sample from Chrome #2 (after huge pages mapping).
EXPECT_EQ(PERF_RECORD_SAMPLE, events[9].event_ptr->header().type());
EXPECT_EQ("/opt/google/chrome/chrome", events[9].dso_and_offset.dso_name());
EXPECT_EQ(0x2f00, events[9].dso_and_offset.offset());
}
} // namespace quipper