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