普通文本  |  691行  |  25.1 KB

/*
 * Copyright (c) 2016, Google Inc.
 * All rights reserved.
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */

#include "perf_data_converter.h"

#include <algorithm>
#include <deque>
#include <map>
#include <sstream>
#include <vector>

#include "int_compat.h"
#include "perf_data_handler.h"
#include "string_compat.h"
#include "builder.h"
#include "quipper/perf_data.pb.h"
#include "quipper/perf_parser.h"
#include "quipper/perf_reader.h"

namespace perftools {
namespace {

typedef perftools::profiles::Profile Profile;
typedef perftools::profiles::Builder ProfileBuilder;

typedef uint32 Pid;

enum ExecutionMode {
  Unknown,
  HostKernel,
  HostUser,
  GuestKernel,
  GuestUser,
  Hypervisor
};

const char* ExecModeString(ExecutionMode mode) {
  switch (mode) {
    case HostKernel:
      return ExecutionModeHostKernel;
    case HostUser:
      return ExecutionModeHostUser;
    case GuestKernel:
      return ExecutionModeGuestKernel;
    case GuestUser:
      return ExecutionModeGuestUser;
    case Hypervisor:
      return ExecutionModeHypervisor;
    default:
      std::cerr << "Execution mode not handled: " << mode << std::endl;
      return "";
  }
}

ExecutionMode PerfExecMode(const PerfDataHandler::SampleContext& sample) {
  if (sample.header.has_misc()) {
    switch (sample.header.misc() & PERF_RECORD_MISC_CPUMODE_MASK) {
      case PERF_RECORD_MISC_KERNEL:
        return HostKernel;
      case PERF_RECORD_MISC_USER:
        return HostUser;
      case PERF_RECORD_MISC_GUEST_KERNEL:
        return GuestKernel;
      case PERF_RECORD_MISC_GUEST_USER:
        return GuestUser;
      case PERF_RECORD_MISC_HYPERVISOR:
        return Hypervisor;
    }
  }
  return Unknown;
}

// Adds the string to the profile builder. If the UTF-8 library is included,
// this also ensures the string contains structurally valid UTF-8.
// In order to successfully unmarshal the proto in Go, all strings inserted into
// the profile string table must be valid UTF-8.
int64 UTF8StringId(const string& s, ProfileBuilder* builder) {
  return builder->StringId(s.c_str());
}

// Returns the file name of the mapping as either the real file path if it's
// present or the string representation of the file path MD5 checksum prefix
// when the real file path was stripped from the data for privacy reasons.
string MappingFilename(const PerfDataHandler::Mapping* m) {
  if (m->filename != nullptr && !m->filename->empty()) {
    return *m->filename;
  } else if (m->filename_md5_prefix != 0) {
    std::stringstream filename;
    filename << std::hex << m->filename_md5_prefix;
    return filename.str();
  }
  return "";
}

// List of profile location IDs, currently used to represent a call stack.
typedef std::vector<uint64> LocationIdVector;

// It is sufficient to key the location and mapping maps by PID.
// However, when Samples include labels, it is necessary to key their maps
// not only by PID, but also by anything their labels may contain, since labels
// are also distinguishing features.  This struct should contain everything
// required to uniquely identify a Sample: if two Samples you consider different
// end up with the same SampleKey, you should extend SampleKey til they don't.
//
// If any of these values are not used as labels, they should be set to 0.
struct SampleKey {
  Pid pid = 0;
  Pid tid = 0;
  uint64 time_ns = 0;
  ExecutionMode exec_mode = Unknown;
  // The index of the sample's command in the profile's string table.
  uint64 comm = 0;
  LocationIdVector stack;
};

struct SampleKeyEqualityTester {
  bool operator()(const SampleKey a, const SampleKey b) const {
    return ((a.pid == b.pid) && (a.tid == b.tid) && (a.time_ns == b.time_ns) &&
            (a.exec_mode == b.exec_mode) && (a.comm == b.comm) &&
            (a.stack == b.stack));
  }
};

struct SampleKeyHasher {
  size_t operator()(const SampleKey k) const {
    size_t hash = 0;
    hash ^= std::hash<int32>()(k.pid);
    hash ^= std::hash<int32>()(k.tid);
    hash ^= std::hash<uint64>()(k.time_ns);
    hash ^= std::hash<int>()(k.exec_mode);
    hash ^= std::hash<uint64>()(k.comm);
    for (const auto& id : k.stack) {
      hash ^= std::hash<uint64>()(id);
    }
    return hash;
  }
};

// While Locations and Mappings are per-address-space (=per-process), samples
// can be thread-specific.  If the requested sample labels include PID and
// TID, we'll need to maintain separate profile sample objects for samples
// that are identical except for TID.  Likewise, if the requested sample
// labels include timestamp_ns, then we'll need to have separate
// profile_proto::Samples for samples that are identical except for timestamp.
typedef std::unordered_map<SampleKey, perftools::profiles::Sample*,
                           SampleKeyHasher, SampleKeyEqualityTester> SampleMap;

// Map from a virtual address to a profile location ID. It only keys off the
// address, not also the mapping ID since the map / its portions are invalidated
// by Comm() and MMap() methods to force re-creation of those locations.
//
typedef std::map<uint64, uint64> LocationMap;

// Map from the handler mapping object to profile mapping ID. The mappings
// the handler creates are immutable and reasonably shared (as in no new mapping
// object is created per, say, each sample), so using the pointers is OK.
typedef std::unordered_map<const PerfDataHandler::Mapping*, uint64> MappingMap;

// Per-process (aggregated when no PID grouping requested) info.
// See docs on ProcessProfile in the header file for details on the fields.
class ProcessMeta {
 public:
  // Constructs the object for the specified PID.
  explicit ProcessMeta(Pid pid) : pid_(pid) {}

  // Updates the bounding time interval ranges per specified timestamp.
  void UpdateTimestamps(int64 time_nsec) {
    if (min_sample_time_ns_ == 0 || time_nsec < min_sample_time_ns_) {
      min_sample_time_ns_ = time_nsec;
    }
    if (max_sample_time_ns_ == 0 || time_nsec > max_sample_time_ns_) {
      max_sample_time_ns_ = time_nsec;
    }
  }

  std::unique_ptr<ProcessProfile> makeProcessProfile(Profile* data) {
    ProcessProfile* pp = new ProcessProfile();
    pp->pid = pid_;
    pp->data.Swap(data);
    pp->min_sample_time_ns = min_sample_time_ns_;
    pp->max_sample_time_ns = max_sample_time_ns_;
    return std::unique_ptr<ProcessProfile>(pp);
  }

 private:
  Pid pid_;
  int64 min_sample_time_ns_ = 0;
  int64 max_sample_time_ns_ = 0;
};

class PerfDataConverter : public PerfDataHandler {
 public:
  explicit PerfDataConverter(const quipper::PerfDataProto& perf_data,
                             uint32 sample_labels = kNoLabels,
                             uint32 options = kGroupByPids)
      : perf_data_(perf_data),
        sample_labels_(sample_labels),
        options_(options) {}
  PerfDataConverter(const PerfDataConverter&) = delete;
  PerfDataConverter& operator=(const PerfDataConverter&) = delete;
  virtual ~PerfDataConverter() {}

  ProcessProfiles Profiles();

  // Callbacks for PerfDataHandler
  void Sample(const PerfDataHandler::SampleContext& sample) override;
  void Comm(const CommContext& comm) override;
  void MMap(const MMapContext& mmap) override;

 private:
  // Adds a new sample updating the event counters if such sample is not present
  // in the profile initializing its metrics. Updates the metrics associated
  // with the sample if the sample was added before.
  void AddOrUpdateSample(const PerfDataHandler::SampleContext& context,
                         const Pid& pid, const SampleKey& sample_key,
                         ProfileBuilder* builder);

  // Adds a new location to the profile if such location is not present in the
  // profile, returning the ID of the location. It also adds the profile mapping
  // corresponding to the specified handler mapping.
  uint64 AddOrGetLocation(const Pid& pid, uint64 addr,
                          const PerfDataHandler::Mapping* mapping,
                          ProfileBuilder* builder);

  // Adds a new mapping to the profile if such mapping is not present in the
  // profile, returning the ID of the mapping. It returns 0 to indicate that the
  // mapping was not added (only happens if smap == 0 currently).
  uint64 AddOrGetMapping(const Pid& pid, const PerfDataHandler::Mapping* smap,
                         ProfileBuilder* builder);

  // Returns whether pid labels were requested for inclusion in the
  // profile.proto's Sample.Label field.
  bool IncludePidLabels() const { return (sample_labels_ & kPidLabel); }
  // Returns whether tid labels were requested for inclusion in the
  // profile.proto's Sample.Label field.
  bool IncludeTidLabels() const { return (sample_labels_ & kTidLabel); }
  // Returns whether timestamp_ns labels were requested for inclusion in the
  // profile.proto's Sample.Label field.
  bool IncludeTimestampNsLabels() const {
    return (sample_labels_ & kTimestampNsLabel);
  }
  // Returns whether execution_mode labels were requested for inclusion in the
  // profile.proto's Sample.Label field.
  bool IncludeExecutionModeLabels() const {
    return (sample_labels_ & kExecutionModeLabel);
  }
  // Returns whether comm labels were requested for inclusion in the
  // profile.proto's Sample.Label field.
  bool IncludeCommLabels() const { return (sample_labels_ & kCommLabel); }

  SampleKey MakeSampleKey(const PerfDataHandler::SampleContext& sample,
                          ProfileBuilder* builder);

  ProfileBuilder* GetOrCreateBuilder(
      const PerfDataHandler::SampleContext& sample);

  const quipper::PerfDataProto& perf_data_;
  // Using deque so that appends do not invalidate existing pointers.
  std::deque<ProfileBuilder> builders_;
  std::deque<ProcessMeta> process_metas_;

  struct PerPidInfo {
    ProfileBuilder* builder = nullptr;
    ProcessMeta* process_meta = nullptr;
    LocationMap location_map;
    MappingMap mapping_map;
    std::unordered_map<Pid, string> tid_to_comm_map;
    SampleMap sample_map;
    void clear() {
      builder = nullptr;
      process_meta = nullptr;
      location_map.clear();
      mapping_map.clear();
      tid_to_comm_map.clear();
      sample_map.clear();
    }
  };
  std::unordered_map<Pid, PerPidInfo> per_pid_;

  const uint32 sample_labels_;
  const uint32 options_;
};

SampleKey PerfDataConverter::MakeSampleKey(
    const PerfDataHandler::SampleContext& sample, ProfileBuilder* builder) {
  SampleKey sample_key;
  sample_key.pid = sample.sample.has_pid() ? sample.sample.pid() : 0;
  sample_key.tid =
      (IncludeTidLabels() && sample.sample.has_tid()) ? sample.sample.tid() : 0;
  sample_key.time_ns =
      (IncludeTimestampNsLabels() && sample.sample.has_sample_time_ns())
          ? sample.sample.sample_time_ns()
          : 0;
  if (IncludeExecutionModeLabels()) {
    sample_key.exec_mode = PerfExecMode(sample);
  }
  if (IncludeCommLabels() && sample.sample.has_pid() &&
      sample.sample.has_tid()) {
    Pid pid = sample.sample.pid();
    Pid tid = sample.sample.tid();
    const string& comm = per_pid_[pid].tid_to_comm_map[tid];
    if (!comm.empty()) {
      sample_key.comm = UTF8StringId(comm, builder);
    }
  }
  return sample_key;
}

ProfileBuilder* PerfDataConverter::GetOrCreateBuilder(
    const PerfDataHandler::SampleContext& sample) {
  Pid builder_pid = (options_ & kGroupByPids) ? sample.sample.pid() : 0;
  auto& per_pid = per_pid_[builder_pid];
  if (per_pid.builder == nullptr) {
    builders_.push_back(ProfileBuilder());
    per_pid.builder = &builders_.back();
    process_metas_.push_back(ProcessMeta(builder_pid));
    per_pid.process_meta = &process_metas_.back();

    ProfileBuilder* builder = per_pid.builder;
    Profile* profile = builder->mutable_profile();
    int unknown_event_idx = 0;
    for (int event_idx = 0; event_idx < perf_data_.file_attrs_size();
         ++event_idx) {
      // Come up with an event name for this event.  perf.data will usually
      // contain an event_types section of the same cardinality as its
      // file_attrs; in this case we can just use the name there.  Otherwise
      // we just give it an anonymous name.
      string event_name = "";
      if (perf_data_.file_attrs_size() == perf_data_.event_types_size()) {
        const auto& event_type = perf_data_.event_types(event_idx);
        if (event_type.has_name()) {
          event_name = event_type.name() + "_";
        }
      }
      if (event_name == "") {
        event_name = "event_" + std::to_string(unknown_event_idx++) + "_";
      }
      auto sample_type = profile->add_sample_type();
      sample_type->set_type(UTF8StringId(event_name + "sample", builder));
      sample_type->set_unit(builder->StringId("count"));
      sample_type = profile->add_sample_type();
      sample_type->set_type(UTF8StringId(event_name + "event", builder));
      sample_type->set_unit(builder->StringId("count"));
    }
    if (sample.main_mapping == nullptr) {
      auto fake_main = profile->add_mapping();
      fake_main->set_id(profile->mapping_size());
      fake_main->set_memory_start(0);
      fake_main->set_memory_limit(1);
    } else {
      AddOrGetMapping(sample.sample.pid(), sample.main_mapping, builder);
    }
    if (perf_data_.string_metadata().has_perf_version()) {
      string perf_version =
          "perf-version:" + perf_data_.string_metadata().perf_version().value();
      profile->add_comment(UTF8StringId(perf_version, builder));
    }
    if (perf_data_.string_metadata().has_perf_command_line_whole()) {
      string perf_command =
          "perf-command:" +
          perf_data_.string_metadata().perf_command_line_whole().value();
      profile->add_comment(UTF8StringId(perf_command, builder));
    }
  } else {
    Profile* profile = per_pid.builder->mutable_profile();
    if ((options_ & kGroupByPids) && sample.main_mapping != nullptr &&
        sample.main_mapping->filename != nullptr) {
      const string& filename =
          profile->string_table(profile->mapping(0).filename());
      const string& sample_filename = MappingFilename(sample.main_mapping);

      if (filename != sample_filename) {
        if (options_ & kFailOnMainMappingMismatch) {
          LOG(FATAL) << "main mapping mismatch: " << sample.sample.pid() << " "
                     << filename << " " << sample_filename;
        } else {
          LOG(WARNING) << "main mapping mismatch: " << sample.sample.pid()
                       << " " << filename << " " << sample_filename;
        }
      }
    }
  }
  if (sample.sample.sample_time_ns()) {
    per_pid.process_meta->UpdateTimestamps(sample.sample.sample_time_ns());
  }
  return per_pid.builder;
}

uint64 PerfDataConverter::AddOrGetMapping(const Pid& pid,
                                          const PerfDataHandler::Mapping* smap,
                                          ProfileBuilder* builder) {
  if (builder == nullptr) {
    std::cerr << "Cannot add mapping to null builder." << std::endl;
    abort();
  }

  if (smap == nullptr) {
    return 0;
  }

  MappingMap& mapmap = per_pid_[pid].mapping_map;
  auto it = mapmap.find(smap);
  if (it != mapmap.end()) {
    return it->second;
  }

  Profile* profile = builder->mutable_profile();
  auto mapping = profile->add_mapping();
  uint64 mapping_id = profile->mapping_size();
  mapping->set_id(mapping_id);
  mapping->set_memory_start(smap->start);
  mapping->set_memory_limit(smap->limit);
  mapping->set_file_offset(smap->file_offset);
  if (smap->build_id != nullptr && !smap->build_id->empty()) {
    mapping->set_build_id(UTF8StringId(*smap->build_id, builder));
  }
  mapping->set_filename(UTF8StringId(MappingFilename(smap), builder));
  if (mapping->memory_start() >= mapping->memory_limit()) {
    std::cerr << "The start of the mapping must be strictly less than its"
              << "limit in file: " << mapping->filename() << std::endl
              << "Start: " << mapping->memory_start() << std::endl
              << "Limit: " << mapping->memory_limit() << std::endl;
    abort();
  }
  mapmap.insert(std::make_pair(smap, mapping_id));
  return mapping_id;
}

void PerfDataConverter::AddOrUpdateSample(
    const PerfDataHandler::SampleContext& context, const Pid& pid,
    const SampleKey& sample_key,
    ProfileBuilder* builder) {

  perftools::profiles::Sample* sample = per_pid_[pid].sample_map[sample_key];

  if (sample == nullptr) {
    Profile* profile = builder->mutable_profile();
    sample = profile->add_sample();
    per_pid_[pid].sample_map[sample_key] = sample;
    for (const auto& location_id : sample_key.stack) {
      sample->add_location_id(location_id);
    }
    // Emit any requested labels.
    if (IncludePidLabels() && context.sample.has_pid()) {
      auto* label = sample->add_label();
      label->set_key(builder->StringId(PidLabelKey));
      label->set_num(static_cast<int64>(context.sample.pid()));
    }
    if (IncludeTidLabels() && context.sample.has_tid()) {
      auto* label = sample->add_label();
      label->set_key(builder->StringId(TidLabelKey));
      label->set_num(static_cast<int64>(context.sample.tid()));
    }
    if (IncludeCommLabels() && sample_key.comm != 0) {
      auto* label = sample->add_label();
      label->set_key(builder->StringId(CommLabelKey));
      label->set_str(sample_key.comm);
    }
    if (IncludeTimestampNsLabels() && context.sample.has_sample_time_ns()) {
      auto* label = sample->add_label();
      label->set_key(builder->StringId(TimestampNsLabelKey));
      int64 timestamp_ns_as_int64 =
          static_cast<int64>(context.sample.sample_time_ns());
      label->set_num(timestamp_ns_as_int64);
    }
    if (IncludeExecutionModeLabels() && sample_key.exec_mode != Unknown) {
      auto* label = sample->add_label();
      label->set_key(builder->StringId(ExecutionModeLabelKey));
      label->set_str(builder->StringId(ExecModeString(sample_key.exec_mode)));
    }
    // Two values per collected event: the first is sample counts, the second is
    // event counts (unsampled weight for each sample).
    for (int event_id = 0; event_id < perf_data_.file_attrs_size();
         ++event_id) {
      sample->add_value(0);
      sample->add_value(0);
    }
  }

  int64 weight = 1;
  // If the sample has a period, use that in preference
  if (context.sample.period() > 0) {
    weight = context.sample.period();
  } else if (context.file_attrs_index >= 0) {
    uint64 period =
        perf_data_.file_attrs(context.file_attrs_index).attr().sample_period();
    if (period > 0) {
      // If sampling used a fixed period, use that as the weight.
      weight = period;
    }
  }
  int event_index = context.file_attrs_index;
  sample->set_value(2 * event_index, sample->value(2 * event_index) + 1);
  sample->set_value(2 * event_index + 1,
                    sample->value(2 * event_index + 1) + weight);
}

uint64 PerfDataConverter::AddOrGetLocation(
    const Pid& pid, uint64 addr, const PerfDataHandler::Mapping* mapping,
    ProfileBuilder* builder) {
  LocationMap& loc_map = per_pid_[pid].location_map;
  auto loc_it = loc_map.find(addr);
  if (loc_it != loc_map.end()) {
    return loc_it->second;
  }

  Profile* profile = builder->mutable_profile();
  perftools::profiles::Location* loc = profile->add_location();
  uint64 loc_id = profile->location_size();
  loc->set_id(loc_id);
  loc->set_address(addr);
  uint64 mapping_id = AddOrGetMapping(pid, mapping, builder);
  if (mapping_id != 0) {
    loc->set_mapping_id(mapping_id);
  } else {
    if (addr != 0) {
      std::cerr << "Found unmapped address: " << addr << " in PID " << pid
                << std::endl;
      abort();
    }
  }
  loc_map[addr] = loc_id;
  return loc_id;
}

void PerfDataConverter::Comm(const CommContext& comm) {
  Pid pid = comm.comm->pid();
  Pid tid = comm.comm->tid();
  if (pid == tid) {
    // pid==tid means an exec() happened, so clear everything from the
    // existing pid.
    per_pid_[pid].clear();
  }

  per_pid_[pid].tid_to_comm_map[tid] = comm.comm->comm();
}

// Invalidates the locations in location_map in the mmap event's range.
void PerfDataConverter::MMap(const MMapContext& mmap) {
  LocationMap& loc_map = per_pid_[mmap.pid].location_map;
  loc_map.erase(loc_map.lower_bound(mmap.mapping->start),
                loc_map.lower_bound(mmap.mapping->limit));
}

void PerfDataConverter::Sample(const PerfDataHandler::SampleContext& sample) {
  if (sample.file_attrs_index < 0 ||
      sample.file_attrs_index >= perf_data_.file_attrs_size()) {
    LOG(WARNING) << "out of bounds file_attrs_index: "
                 << sample.file_attrs_index;
    return;
  }

  Pid event_pid = sample.sample.pid();
  ProfileBuilder *builder = GetOrCreateBuilder(sample);
  SampleKey sample_key = MakeSampleKey(sample, builder);

  uint64 ip = sample.sample_mapping != nullptr ? sample.sample.ip() : 0;
  if (ip != 0) {
    const auto start = sample.sample_mapping->start;
    const auto limit = sample.sample_mapping->limit;
    if (ip < start || ip >= limit) {
      std::cerr << "IP is out of bound of mapping." << std::endl
                << "IP: " << ip << std::endl
                << "Start: " << start << std::endl
                << "Limit: " << limit << std::endl;
    }
  }

  // Leaf at stack[0]
  sample_key.stack.push_back(
      AddOrGetLocation(event_pid, ip, sample.sample_mapping, builder));

  // LBR callstacks include only user call chains. If this is an LBR sample,
  // we get the kernel callstack from the sample's callchain, and the user
  // callstack from the sample's branch_stack.
  const bool lbr_sample = !sample.branch_stack.empty();
  bool skipped_dup = false;
  for (const auto& frame : sample.callchain) {
    if (lbr_sample && frame.ip == PERF_CONTEXT_USER) {
      break;
    }
    if (!skipped_dup && sample_key.stack.size() == 1 && frame.ip == ip) {
      skipped_dup = true;
      // Newer versions of perf_events include the IP at the leaf of
      // the callchain.
      continue;
    }
    if (frame.mapping == nullptr) {
      continue;
    }
    uint64 frame_ip = frame.ip;
    // Why <=? Because this is a return address, which should be
    // preceded by a call (the "real" context.)  If we're at the edge
    // of the mapping, we're really off its edge.
    if (frame_ip <= frame.mapping->start) {
      continue;
    }
    // these aren't real callchain entries, just hints as to kernel/user
    // addresses.
    if (frame_ip >= PERF_CONTEXT_MAX) {
      continue;
    }

    // subtract one so we point to the call instead of the return addr.
    frame_ip--;
    sample_key.stack.push_back(
        AddOrGetLocation(event_pid, frame_ip, frame.mapping, builder));
  }
  for (const auto& frame : sample.branch_stack) {
    // branch_stack entries are pairs of <from, to> locations corresponding to
    // addresses of call instructions and target addresses of those calls.
    // We need only the addresses of the function call instructions, stored in
    // the 'from' field, to recover the call chains.
    if (frame.from.mapping == nullptr) {
      continue;
    }
    // An LBR entry includes the address of the call instruction, so we don't
    // have to do any adjustments.
    if (frame.from.ip < frame.from.mapping->start) {
      continue;
    }
    sample_key.stack.push_back(AddOrGetLocation(event_pid, frame.from.ip,
                                                frame.from.mapping, builder));
  }
  AddOrUpdateSample(sample, event_pid, sample_key, builder);
}

ProcessProfiles PerfDataConverter::Profiles() {
  ProcessProfiles pps;
  for (int i = 0; i < builders_.size(); i++) {
    auto& b = builders_[i];
    b.Finalize();
    auto pp = process_metas_[i].makeProcessProfile(b.mutable_profile());
    pps.push_back(std::move(pp));
  }
  return pps;
}

}  // namespace

ProcessProfiles PerfDataProtoToProfiles(const quipper::PerfDataProto* perf_data,
                                        const uint32 sample_labels,
                                        const uint32 options) {
  PerfDataConverter converter(*perf_data, sample_labels, options);
  PerfDataHandler::Process(*perf_data, &converter);
  return converter.Profiles();
}

ProcessProfiles RawPerfDataToProfiles(const void* raw, const int raw_size,
                                      const std::map<string, string>& build_ids,
                                      const uint32 sample_labels,
                                      const uint32 options) {
  quipper::PerfReader reader;
  if (!reader.ReadFromPointer(reinterpret_cast<const char*>(raw), raw_size)) {
    LOG(ERROR) << "Could not read input perf.data";
    return ProcessProfiles();
  }

  reader.InjectBuildIDs(build_ids);
  // Perf populates info about the kernel using multiple pathways,
  // which don't actually all match up how they name kernel data; in
  // particular, buildids are reported by a different name than the
  // actual "mmap" info.  Normalize these names so our ProcessProfiles
  // will match kernel mappings to a buildid.
  reader.LocalizeUsingFilenames({
      {"[kernel.kallsyms]_text", "[kernel.kallsyms]"},
      {"[kernel.kallsyms]_stext", "[kernel.kallsyms]"},
  });

  // Use PerfParser to modify reader's events to have magic done to them such
  // as hugepage deduction and sorting events based on time, if timestamps are
  // present.
  quipper::PerfParserOptions opts;
  opts.sort_events_by_time = true;
  opts.deduce_huge_page_mappings = true;
  opts.combine_mappings = true;
  quipper::PerfParser parser(&reader, opts);
  if (!parser.ParseRawEvents()) {
    LOG(ERROR) << "Could not parse perf events.";
    return ProcessProfiles();
  }

  return PerfDataProtoToProfiles(&reader.proto(), sample_labels, options);
}

}  // namespace perftools