| // 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" |
| |
| 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 |