// 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/sysmacros.h>
#include <sys/types.h>
#include <unistd.h>

#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <vector>

#include "base/logging.h"

#include "compat/string.h"
#include "compat/test.h"
#include "compat/thread.h"
#include "dso_test_utils.h"
#include "perf_data_utils.h"
#include "perf_parser.h"
#include "perf_reader.h"
#include "perf_serializer.h"
#include "perf_test_files.h"
#include "scoped_temp_path.h"
#include "test_perf_data.h"
#include "test_utils.h"

namespace quipper {

using SampleEvent = PerfDataProto_SampleEvent;
using SampleInfo = PerfDataProto_SampleInfo;
using PerfEvent = PerfDataProto_PerfEvent;

namespace {

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 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);

  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);
}

void CopyActualEvents(const std::vector<ParsedEvent> &events,
                      PerfDataProto *out) {
  for (const auto &ev : events) {
    if (ev.event_ptr == nullptr) {
      continue;
    }
    *out->add_events() = *ev.event_ptr;
  }
}

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

class PerfDataFiles : public ::testing::TestWithParam<const char *> {};
class PerfPipedDataFiles : public ::testing::TestWithParam<const char *> {};

TEST_P(PerfDataFiles, NormalPerfData) {
  ScopedTempDir output_dir;
  ASSERT_FALSE(output_dir.path().empty());
  string output_path = output_dir.path();

  int seed = 0;
  string test_file = GetParam();
  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_P(PerfPipedDataFiles, PipedModePerfData) {
  ScopedTempDir output_dir;
  ASSERT_FALSE(output_dir.path().empty());
  string output_path = output_dir.path();

  int seed = 0;
  const string test_file = GetParam();
  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;
}

INSTANTIATE_TEST_CASE_P(
    PerfParserTest, PerfDataFiles,
    ::testing::ValuesIn(perf_test_files::GetPerfDataFiles()));
INSTANTIATE_TEST_CASE_P(
    PerfParserTest, PerfPipedDataFiles,
    ::testing::ValuesIn(perf_test_files::GetPerfPipedDataFiles()));

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))
      .WriteTo(&input);  // 4
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c100a).Tid(1001))
      .WriteTo(&input);  // 5
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c3fff).Tid(1001))
      .WriteTo(&input);  // 6
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c2bad).Tid(1001))
      .WriteTo(&input);  // 7 (not mapped)
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000002c100a).Tid(1002))
      .WriteTo(&input);  // 8
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000002c5bad).Tid(1002))
      .WriteTo(&input);  // 9 (not mapped)
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000002c300b).Tid(1002))
      .WriteTo(&input);  // 10

  // not mapped yet:
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000002c400b).Tid(1002))
      .WriteTo(&input);  // 11
  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))
      .WriteTo(&input);  // 13

  //
  // 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))
      .WriteTo(&input);  // 2
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c300a).Tid(1001))
      .WriteTo(&input);  // 3

  //
  // 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_(std::move(tmpdir)),
        mntdir_(std::move(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))
      .WriteTo(&input);  // 2

  //
  // 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_(std::move(tmpdir)), mntdir_(std::move(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))
      .WriteTo(&input);  // 1

  //
  // 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))
      .WriteTo(&input);  // 1

  //
  // 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))
      .WriteTo(&input);  // 1

  //
  // 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))
      .WriteTo(&input);  // 1

  //
  // 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>: ""
//
// 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))
      .WriteTo(&input);  // 3
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c2000).Tid(pid, tid))
      .WriteTo(&input);  // 4
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c3000).Tid(pid, tid))
      .WriteTo(&input);  // 5

  //
  // 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))
      .WriteTo(&input);  // 3
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c300b).Tid(1001))
      .WriteTo(&input);  // 4
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c400c).Tid(1001))
      .WriteTo(&input);  // 5

  //
  // 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))
      .WriteTo(&input);  // 1

  //
  // 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(
      testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300020))
      .WriteTo(&input);  // 1
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300030))
      .WriteTo(&input);  // 2
  // PERF_RECORD_FINISHED_ROUND
  testing::FinishedRoundEvent().WriteTo(&input);  // N/A

  // PERF_RECORD_SAMPLE
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300050))
      .WriteTo(&input);  // 3
  testing::ExamplePerfSampleEvent(
      testing::SampleInfo().Ip(0x00000000001c1000).Tid(1001).Time(12300040))
      .WriteTo(&input);  // 4
  // 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, 0x1e8000, 0,
                            "/opt/google/chrome/chrome",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x40200000, 0x1c00000, 0, "//anon",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x41e00000, 0x4000000, 0x1de8000,
                            "/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.deduce_huge_page_mappings = true;
  options.combine_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());
}

TEST(PerfParserTest, Regression62446346) {
  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 infers the filename is "file", but at offset 0 for
  // hugepage-backed, anonymous mappings.
  //
  // vaddr start   - vaddr end     vaddr-size    elf-offset
  // [0x55a685bfb000-55a685dfb000) (0x200000)   @ 0]:          file
  // [0x55a685dfb000-55a687c00000) (0x1e05000)  @ 0x200000]:   file
  // [0x55a687c00000-55a6a5200000) (0x1d600000) @ 0]:          file
  // [0x55a6a5200000-55a6a6400000) (0x1200000)  @ 0x1f605000]: file
  // [0x55a6a6400000-55a6a6600000) (0x200000)   @ 0]:          file
  // [0x55a6a6600000-55a6a8800000) (0x2200000)  @ 0x20a05000]: file
  // [0x55a6a8800000-55a6a8a00000) (0x200000)   @ 0]:          file
  // [0x55a6a8a00000-55a6a90ca000) (0x6ca000)   @ 0x22e05000]: file
  // [0x55a6a90ca000-55a6a90cb000) (0x1000)     @ 0x234cf000]: file
  testing::ExampleMmapEvent(1234, 0x55a685bfb000, 0x200000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a685dfb000, 0x1e05000, 0x200000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a687c00000, 0x1d600000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a6a5200000, 0x1200000, 0x1f605000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a6a6400000, 0x200000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a6a6600000, 0x2200000, 0x20a05000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a6a8800000, 0x200000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a6a8a00000, 0x6ca000, 0x22e05000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55a6a90ca000, 0x1000, 0x234cf000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);

  //
  // Parse input.
  //

  PerfReader reader;
  EXPECT_TRUE(reader.ReadFromString(input.str()));

  PerfParserOptions options;
  options.deduce_huge_page_mappings = true;
  options.combine_mappings = true;
  PerfParser parser(&reader, options);
  EXPECT_TRUE(parser.ParseRawEvents());

  EXPECT_EQ(1, parser.stats().num_mmap_events);
  EXPECT_EQ(0, parser.stats().num_sample_events);
  EXPECT_EQ(0, parser.stats().num_sample_events_mapped);

  const std::vector<ParsedEvent> &events = parser.parsed_events();

  {
    PerfDataProto expected;
    {
      auto *ev = expected.add_events();
      ev->mutable_header()->set_type(PERF_RECORD_MMAP);
      ev->mutable_mmap_event()->set_filename("file");
      ev->mutable_mmap_event()->set_start(0x55a685bfb000);
      ev->mutable_mmap_event()->set_len(0x234d0000);
      ev->mutable_mmap_event()->set_pgoff(0x0);
    }

    PerfDataProto actual;
    for (const auto &ev : events) {
      if (ev.event_ptr == nullptr) {
        continue;
      }

      *actual.add_events() = *ev.event_ptr;
    }

    EXPECT_TRUE(PartiallyEqualsProto(actual, expected));
  }
  ASSERT_EQ(1, events.size());

  // Verify the header().size() entry is large enough to deserialize/serialize.
  for (const auto &ev : events) {
    malloced_unique_ptr<event_t> e(
        CallocMemoryForEvent(ev.event_ptr->header().size()));

    PerfSerializer serializer;
    ASSERT_TRUE(
        serializer.DeserializeMMapEvent(ev.event_ptr->mmap_event(), e.get()));

    PerfDataProto_MMapEvent roundtrip;
    ASSERT_TRUE(serializer.SerializeMMapEvent(*e, &roundtrip));
    // sample_info does not roundtrip through an event_t.
    EXPECT_TRUE(PartiallyEqualsProto(ev.event_ptr->mmap_event(), roundtrip));
  }
}

TEST(PerfParserTest, Regression62446346_Perf3_12_0_11) {
  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 infers the filename is "file", but at offset 0 for
  // hugepage-backed, anonymous mappings.
  //
  // vaddr start   - vaddr end     vaddr-size    elf-offset
  // [0x55bd43879000-0x55bd45000000) (0x 1787000) @ 0x       0]: file
  // [0x55bd45000000-0x55bd58c00000) (0x13c00000) @ 0x       0]: file
  // [0x55bd58c00000-0x55bd59800000) (0x  c00000) @ 0x15387000]: file
  // [0x55bd59800000-0x55bd59a00000) (0x  200000) @ 0x       0]: file
  // [0x55bd59a00000-0x55bd5b600000) (0x 1c00000) @ 0x16187000]: file
  // [0x55bd5b600000-0x55bd5b800000) (0x  200000) @ 0x       0]: file
  // [0x55bd5b800000-0x55bd5bcb5000) (0x  4b5000) @ 0x17f87000]: file

  testing::ExampleMmapEvent(1234, 0x55bd43879000, 0x1787000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd45000000, 0x13c00000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd58c00000, 0xc00000, 0x15387000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd59800000, 0x200000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd59a00000, 0x1c00000, 0x16187000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd5b600000, 0x200000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd5b800000, 0x4b5000, 0x17f87000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);

  //
  // Parse input.
  //

  PerfReader reader;
  EXPECT_TRUE(reader.ReadFromString(input.str()));

  PerfParserOptions options;
  options.deduce_huge_page_mappings = true;
  options.combine_mappings = true;
  PerfParser parser(&reader, options);
  EXPECT_TRUE(parser.ParseRawEvents());

  EXPECT_EQ(1, parser.stats().num_mmap_events);
  EXPECT_EQ(0, parser.stats().num_sample_events);
  EXPECT_EQ(0, parser.stats().num_sample_events_mapped);

  PerfDataProto expected;
  {
    auto *ev = expected.add_events();
    ev->mutable_header()->set_type(PERF_RECORD_MMAP);
    ev->mutable_mmap_event()->set_filename("file");
    ev->mutable_mmap_event()->set_start(0x55bd43879000);
    ev->mutable_mmap_event()->set_len(0x55bd5bcb5000 - 0x55bd43879000);
    ev->mutable_mmap_event()->set_pgoff(0x0);
  }

  PerfDataProto actual;
  CopyActualEvents(parser.parsed_events(), &actual);
  EXPECT_TRUE(PartiallyEqualsProto(actual, expected));
  ASSERT_EQ(1, parser.parsed_events().size());
}

TEST(PerfParserTest, Regression62446346_Perf3_12_0_14) {
  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 infers the filename is "file", but at offset 0 for
  // hugepage-backed, anonymous mappings.
  //
  // vaddr start   - vaddr end     vaddr-size    elf-offset
  // [0x55bd43879000-0x55bd45000000) (0x 1787000) @ 0x       0]: file
  // [0x55bd45000000-0x55bd58c00000) (0x13c00000) @ 0x 1787000]: file
  // [0x55bd58c00000-0x55bd59800000) (0x  c00000) @ 0x15387000]: file
  // [0x55bd59800000-0x55bd59a00000) (0x  200000) @ 0x15f87000]: file
  // [0x55bd59a00000-0x55bd5b600000) (0x 1c00000) @ 0x16187000]: file
  // [0x55bd5b600000-0x55bd5b800000) (0x  200000) @ 0x17d87000]: file
  // [0x55bd5b800000-0x55bd5bcb5000) (0x  4b5000) @ 0x17f87000]: file

  testing::ExampleMmapEvent(1234, 0x55bd43879000, 0x1787000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd45000000, 0x13c00000, 0x1787000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd58c00000, 0xc00000, 0x15387000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd59800000, 0x200000, 0x15f87000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd59a00000, 0x1c00000, 0x16187000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd5b600000, 0x200000, 0x17d87000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x55bd5b800000, 0x4b5000, 0x17f87000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);

  //
  // Parse input.
  //

  PerfReader reader;
  EXPECT_TRUE(reader.ReadFromString(input.str()));

  PerfParserOptions options;
  options.deduce_huge_page_mappings = true;
  options.combine_mappings = true;
  PerfParser parser(&reader, options);
  EXPECT_TRUE(parser.ParseRawEvents());

  EXPECT_EQ(1, parser.stats().num_mmap_events);
  EXPECT_EQ(0, parser.stats().num_sample_events);
  EXPECT_EQ(0, parser.stats().num_sample_events_mapped);

  PerfDataProto expected;
  {
    auto *ev = expected.add_events();
    ev->mutable_header()->set_type(PERF_RECORD_MMAP);
    ev->mutable_mmap_event()->set_filename("file");
    ev->mutable_mmap_event()->set_start(0x55bd43879000);
    ev->mutable_mmap_event()->set_len(0x55bd5bcb5000 - 0x55bd43879000);
    ev->mutable_mmap_event()->set_pgoff(0x0);
  }

  PerfDataProto actual;
  CopyActualEvents(parser.parsed_events(), &actual);
  EXPECT_TRUE(PartiallyEqualsProto(actual, expected));
  EXPECT_EQ(1, parser.parsed_events().size());
}

TEST(PerfParserTest, DiscontiguousMappings) {
  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);

  // vaddr start   - vaddr end     vaddr-size    elf-offset
  // [0x7f489000-0x80200000) (0xd77000)   @ 0]:          file
  // [0x80200000-0x80400000) (0x200000)   @ 0]:          file
  // [0x80400000-0x80474000) (0x47000)    @ 0x1a00000]:  file
  testing::ExampleMmapEvent(1234, 0x7f489000, 0xd77000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x80200000, 0x200000, 0, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);
  testing::ExampleMmapEvent(1234, 0x80400000, 0x47000, 0x1a00000, "file",
                            testing::SampleInfo().Tid(1234, 1234))
      .WriteTo(&input);

  //
  // Parse input.
  //

  PerfReader reader;
  EXPECT_TRUE(reader.ReadFromString(input.str()));

  PerfParserOptions options;
  options.deduce_huge_page_mappings = true;
  options.combine_mappings = true;
  PerfParser parser(&reader, options);
  EXPECT_TRUE(parser.ParseRawEvents());

  EXPECT_EQ(3, parser.stats().num_mmap_events);
  EXPECT_EQ(0, parser.stats().num_sample_events);
  EXPECT_EQ(0, parser.stats().num_sample_events_mapped);

  // The first two mappings should not combine, since we cannot know if the
  // middle one follows the first, or preceeds the last in the binary.
  PerfDataProto expected;
  {
    auto *ev = expected.add_events();
    ev->mutable_header()->set_type(PERF_RECORD_MMAP);
    ev->mutable_mmap_event()->set_filename("file");
    ev->mutable_mmap_event()->set_start(0x7f489000);
    ev->mutable_mmap_event()->set_len(0xd77000);
    ev->mutable_mmap_event()->set_pgoff(0x0);
  }
  {
    auto *ev = expected.add_events();
    ev->mutable_header()->set_type(PERF_RECORD_MMAP);
    ev->mutable_mmap_event()->set_filename("file");
    ev->mutable_mmap_event()->set_start(0x80200000);
    ev->mutable_mmap_event()->set_len(0x200000);
    ev->mutable_mmap_event()->set_pgoff(0x0);
  }
  {
    auto *ev = expected.add_events();
    ev->mutable_header()->set_type(PERF_RECORD_MMAP);
    ev->mutable_mmap_event()->set_filename("file");
    ev->mutable_mmap_event()->set_start(0x80400000);
    ev->mutable_mmap_event()->set_len(0x47000);
    ev->mutable_mmap_event()->set_pgoff(0x1a00000);
  }

  PerfDataProto actual;
  CopyActualEvents(parser.parsed_events(), &actual);

  EXPECT_TRUE(PartiallyEqualsProto(actual, expected));
  EXPECT_EQ(3, parser.parsed_events().size());
}
}  // namespace quipper