607 lines
16 KiB
C++
607 lines
16 KiB
C++
/**
|
|
* @file callgraph_container.cpp
|
|
* Container associating symbols and caller/caller symbols
|
|
*
|
|
* @remark Copyright 2004 OProfile authors
|
|
* @remark Read the file COPYING
|
|
*
|
|
* @author Philippe Elie
|
|
* @author John Levon
|
|
*/
|
|
|
|
#include <cstdlib>
|
|
|
|
#include <map>
|
|
#include <set>
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
#include <string>
|
|
#include <iostream>
|
|
#include <numeric>
|
|
|
|
#include "callgraph_container.h"
|
|
#include "cverb.h"
|
|
#include "parse_filename.h"
|
|
#include "profile_container.h"
|
|
#include "arrange_profiles.h"
|
|
#include "populate.h"
|
|
#include "string_filter.h"
|
|
#include "op_bfd.h"
|
|
#include "op_sample_file.h"
|
|
#include "locate_images.h"
|
|
|
|
using namespace std;
|
|
|
|
namespace {
|
|
|
|
bool operator==(cg_symbol const & lhs, cg_symbol const & rhs)
|
|
{
|
|
less_symbol cmp_symb;
|
|
return !cmp_symb(lhs, rhs) && !cmp_symb(rhs, lhs);
|
|
}
|
|
|
|
|
|
// we store {caller,callee} inside a single u64
|
|
odb_key_t caller_to_key(u32 value)
|
|
{
|
|
return odb_key_t(value) << 32;
|
|
}
|
|
|
|
|
|
u32 key_to_callee(odb_key_t key)
|
|
{
|
|
return key & 0xffffffff;
|
|
}
|
|
|
|
|
|
bool compare_by_callee_vma(pair<odb_key_t, count_type> const & lhs,
|
|
pair<odb_key_t, count_type> const & rhs)
|
|
{
|
|
return (key_to_callee(lhs.first)) < (key_to_callee(rhs.first));
|
|
}
|
|
|
|
|
|
/*
|
|
* We need 2 comparators for callgraph to get the desired output:
|
|
*
|
|
* caller_with_few_samples
|
|
* caller_with_many_samples
|
|
* function_with_many_samples
|
|
* callee_with_many_samples
|
|
* callee_with_few_samples
|
|
*/
|
|
|
|
bool
|
|
compare_arc_count(symbol_entry const & lhs, symbol_entry const & rhs)
|
|
{
|
|
return lhs.sample.counts[0] < rhs.sample.counts[0];
|
|
}
|
|
|
|
|
|
bool
|
|
compare_arc_count_reverse(symbol_entry const & lhs, symbol_entry const & rhs)
|
|
{
|
|
return rhs.sample.counts[0] < lhs.sample.counts[0];
|
|
}
|
|
|
|
|
|
// find the nearest bfd symbol for the given file offset and check it's
|
|
// in range
|
|
op_bfd_symbol const *
|
|
get_symbol_by_filepos(op_bfd const & bfd, u32 bfd_offset,
|
|
vma_t offset, symbol_index_t & i)
|
|
{
|
|
offset += bfd_offset;
|
|
op_bfd_symbol tmpsym(offset, 0, string());
|
|
|
|
// sorted by filepos so this will find the nearest
|
|
vector<op_bfd_symbol>::const_iterator it =
|
|
upper_bound(bfd.syms.begin(), bfd.syms.end(), tmpsym);
|
|
|
|
if (it != bfd.syms.begin())
|
|
--it;
|
|
|
|
if (it == bfd.syms.end()) {
|
|
cerr << "get_symbol_by_filepos: no symbols at all?" << endl;
|
|
abort();
|
|
}
|
|
|
|
// if the offset is past the end of the symbol, we didn't find one
|
|
u32 const end_offset = it->size() + it->filepos();
|
|
|
|
if (offset >= end_offset) {
|
|
// let's be verbose for now
|
|
cerr << "warning: dropping hyperspace sample at offset "
|
|
<< hex << offset << " >= " << end_offset
|
|
<< " for binary " << bfd.get_filename() << dec << endl;
|
|
return NULL;
|
|
}
|
|
|
|
i = distance(bfd.syms.begin(), it);
|
|
return &(*it);
|
|
}
|
|
|
|
|
|
/// temporary caller and callee data held during processing
|
|
class call_data {
|
|
public:
|
|
call_data(profile_container const & p, profile_t const & pr,
|
|
op_bfd const & bfd, u32 boff, image_name_id iid,
|
|
image_name_id aid, bool debug_info)
|
|
: pc(p), profile(pr), b(bfd), boffset(boff), image(iid),
|
|
app(aid), debug(debug_info) {}
|
|
|
|
/// point to a caller symbol
|
|
void caller_sym(symbol_index_t i) {
|
|
sym = symbol_entry();
|
|
|
|
unsigned long long start;
|
|
unsigned long long end;
|
|
b.get_symbol_range(i, start, end);
|
|
|
|
samples.clear();
|
|
|
|
// see profile_t::samples_range() for why we need this check
|
|
if (start > boffset) {
|
|
profile_t::iterator_pair p_it = profile.samples_range(
|
|
caller_to_key(start - boffset),
|
|
caller_to_key(end - boffset));
|
|
|
|
// Our odb_key_t contain (from_eip << 32 | to_eip),
|
|
// the range of keys we selected above contains one
|
|
// caller but different callees, and due to the
|
|
// ordering callee offsets are not consecutive: so
|
|
// we must sort them first.
|
|
|
|
for (; p_it.first != p_it.second; ++p_it.first) {
|
|
samples.push_back(make_pair(p_it.first.vma(),
|
|
p_it.first.count()));
|
|
}
|
|
|
|
sort(samples.begin(), samples.end(),
|
|
compare_by_callee_vma);
|
|
}
|
|
|
|
sym.size = end - start;
|
|
sym.name = symbol_names.create(b.syms[i].name());
|
|
sym.sample.vma = b.syms[i].vma();
|
|
|
|
finish_sym(i, start);
|
|
|
|
if (cverb << vdebug) {
|
|
cverb << vdebug << hex << "Caller sym: "
|
|
<< b.syms[i].name() << " filepos " << start
|
|
<< "-" << end << dec << endl;
|
|
}
|
|
}
|
|
|
|
/// point to a callee symbol
|
|
bool callee_sym(u32 off) {
|
|
sym = symbol_entry();
|
|
|
|
symbol_index_t i = 0;
|
|
op_bfd_symbol const * bfdsym =
|
|
get_symbol_by_filepos(b, boffset, off, i);
|
|
|
|
if (!bfdsym)
|
|
return false;
|
|
|
|
callee_end = bfdsym->size() + bfdsym->filepos() - boffset;
|
|
|
|
sym.size = bfdsym->size();
|
|
sym.name = symbol_names.create(bfdsym->name());
|
|
sym.sample.vma = bfdsym->vma();
|
|
|
|
finish_sym(i, bfdsym->filepos());
|
|
|
|
if (cverb << vdebug) {
|
|
cverb << vdebug << hex << "Callee sym: "
|
|
<< bfdsym->name() << " filepos "
|
|
<< bfdsym->filepos() << "-"
|
|
<< (bfdsym->filepos() + bfdsym->size())
|
|
<< dec << endl;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void verbose_bfd(string const & prefix) const {
|
|
cverb << vdebug << prefix << " " << b.get_filename()
|
|
<< " offset " << boffset << " app "
|
|
<< image_names.name(app) << endl;
|
|
}
|
|
|
|
typedef vector<pair<odb_key_t, count_type> > samples_t;
|
|
|
|
typedef samples_t::const_iterator const_iterator;
|
|
|
|
samples_t samples;
|
|
symbol_entry sym;
|
|
u32 callee_end;
|
|
|
|
private:
|
|
/// fill in the rest of the sym
|
|
void finish_sym(symbol_index_t i, unsigned long start) {
|
|
sym.image_name = image;
|
|
sym.app_name = app;
|
|
symbol_entry const * self = pc.find(sym);
|
|
if (self)
|
|
sym.sample.counts = self->sample.counts;
|
|
|
|
if (debug) {
|
|
string filename;
|
|
file_location & loc = sym.sample.file_loc;
|
|
if (b.get_linenr(i, start, filename, loc.linenr))
|
|
loc.filename = debug_names.create(filename);
|
|
}
|
|
}
|
|
|
|
profile_container const & pc;
|
|
profile_t const & profile;
|
|
op_bfd const & b;
|
|
u32 boffset;
|
|
image_name_id image;
|
|
image_name_id app;
|
|
bool debug;
|
|
};
|
|
|
|
|
|
/// accumulate all samples for a given caller/callee pair
|
|
count_type
|
|
accumulate_callee(call_data::const_iterator & it, call_data::const_iterator end,
|
|
u32 callee_end)
|
|
{
|
|
count_type count = 0;
|
|
call_data::const_iterator const start = it;
|
|
|
|
while (it != end) {
|
|
u32 offset = key_to_callee(it->first);
|
|
|
|
if (cverb << (vdebug & vlevel1)) {
|
|
cverb << (vdebug & vlevel1) << hex << "offset: "
|
|
<< offset << dec << endl;
|
|
}
|
|
|
|
// stop if we pass the end of the callee
|
|
if (offset >= callee_end)
|
|
break;
|
|
|
|
count += it->second;
|
|
++it;
|
|
}
|
|
|
|
// If we haven't advanced at all, then we'll get
|
|
// an infinite loop, so we must abort.
|
|
if (it == start) {
|
|
cerr << "failure to advance iterator\n";
|
|
abort();
|
|
}
|
|
|
|
return count;
|
|
}
|
|
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
void arc_recorder::
|
|
add(symbol_entry const & caller, symbol_entry const * callee,
|
|
count_array_t const & arc_count)
|
|
{
|
|
cg_data & data = sym_map[caller];
|
|
|
|
// If we have a callee, add it to the caller's list, then
|
|
// add the caller to the callee's list.
|
|
if (callee) {
|
|
data.callees[*callee] += arc_count;
|
|
|
|
cg_data & callee_data = sym_map[*callee];
|
|
|
|
callee_data.callers[caller] += arc_count;
|
|
}
|
|
}
|
|
|
|
|
|
void arc_recorder::process_children(cg_symbol & sym, double threshold)
|
|
{
|
|
// generate the synthetic self entry for the symbol
|
|
symbol_entry self = sym;
|
|
|
|
self.name = symbol_names.create(symbol_names.demangle(self.name)
|
|
+ " [self]");
|
|
|
|
sym.total_callee_count += self.sample.counts;
|
|
sym.callees.push_back(self);
|
|
|
|
sort(sym.callers.begin(), sym.callers.end(), compare_arc_count);
|
|
sort(sym.callees.begin(), sym.callees.end(), compare_arc_count_reverse);
|
|
|
|
// FIXME: this relies on sort always being sample count
|
|
|
|
cg_symbol::children::iterator cit = sym.callers.begin();
|
|
cg_symbol::children::iterator cend = sym.callers.end();
|
|
|
|
while (cit != cend && op_ratio(cit->sample.counts[0],
|
|
sym.total_caller_count[0]) < threshold)
|
|
++cit;
|
|
|
|
if (cit != cend)
|
|
sym.callers.erase(sym.callers.begin(), cit);
|
|
|
|
cit = sym.callees.begin();
|
|
cend = sym.callees.end();
|
|
|
|
while (cit != cend && op_ratio(cit->sample.counts[0],
|
|
sym.total_callee_count[0]) >= threshold)
|
|
++cit;
|
|
|
|
if (cit != cend)
|
|
sym.callees.erase(cit, sym.callees.end());
|
|
}
|
|
|
|
|
|
void arc_recorder::
|
|
process(count_array_t total, double threshold,
|
|
string_filter const & sym_filter)
|
|
{
|
|
map_t::const_iterator it;
|
|
map_t::const_iterator end = sym_map.end();
|
|
|
|
for (it = sym_map.begin(); it != end; ++it) {
|
|
cg_symbol sym((*it).first);
|
|
cg_data const & data = (*it).second;
|
|
|
|
// threshold out the main symbol if needed
|
|
if (op_ratio(sym.sample.counts[0], total[0]) < threshold)
|
|
continue;
|
|
|
|
// FIXME: slow?
|
|
if (!sym_filter.match(symbol_names.demangle(sym.name)))
|
|
continue;
|
|
|
|
cg_data::children::const_iterator cit;
|
|
cg_data::children::const_iterator cend = data.callers.end();
|
|
|
|
for (cit = data.callers.begin(); cit != cend; ++cit) {
|
|
symbol_entry csym = cit->first;
|
|
csym.sample.counts = cit->second;
|
|
sym.callers.push_back(csym);
|
|
sym.total_caller_count += cit->second;
|
|
}
|
|
|
|
cend = data.callees.end();
|
|
|
|
for (cit = data.callees.begin(); cit != cend; ++cit) {
|
|
symbol_entry csym = cit->first;
|
|
csym.sample.counts = cit->second;
|
|
sym.callees.push_back(csym);
|
|
sym.total_callee_count += cit->second;
|
|
}
|
|
|
|
process_children(sym, threshold);
|
|
|
|
// insert sym into cg_syms_objs
|
|
// then store pointer to sym in cg_syms
|
|
cg_syms.push_back(&(*cg_syms_objs.insert(cg_syms_objs.end(), sym)));
|
|
}
|
|
}
|
|
|
|
|
|
symbol_collection const & arc_recorder::get_symbols() const
|
|
{
|
|
return cg_syms;
|
|
}
|
|
|
|
|
|
void callgraph_container::populate(list<inverted_profile> const & iprofiles,
|
|
extra_images const & extra, bool debug_info, double threshold,
|
|
bool merge_lib, string_filter const & sym_filter)
|
|
{
|
|
this->extra_found_images = extra;
|
|
// non callgraph samples container, we record sample at symbol level
|
|
// not at vma level.
|
|
profile_container pc(debug_info, false, extra_found_images);
|
|
|
|
list<inverted_profile>::const_iterator it;
|
|
list<inverted_profile>::const_iterator const end = iprofiles.end();
|
|
for (it = iprofiles.begin(); it != end; ++it) {
|
|
// populate_caller_image take care about empty sample filename
|
|
populate_for_image(pc, *it, sym_filter, 0);
|
|
}
|
|
|
|
add_symbols(pc);
|
|
|
|
total_count = pc.samples_count();
|
|
|
|
for (it = iprofiles.begin(); it != end; ++it) {
|
|
for (size_t i = 0; i < it->groups.size(); ++i) {
|
|
populate(it->groups[i], it->image,
|
|
i, pc, debug_info, merge_lib);
|
|
}
|
|
}
|
|
|
|
recorder.process(total_count, threshold / 100.0, sym_filter);
|
|
}
|
|
|
|
|
|
void callgraph_container::populate(list<image_set> const & lset,
|
|
string const & app_image, size_t pclass,
|
|
profile_container const & pc, bool debug_info, bool merge_lib)
|
|
{
|
|
list<image_set>::const_iterator lit;
|
|
list<image_set>::const_iterator const lend = lset.end();
|
|
for (lit = lset.begin(); lit != lend; ++lit) {
|
|
list<profile_sample_files>::const_iterator pit;
|
|
list<profile_sample_files>::const_iterator pend
|
|
= lit->files.end();
|
|
for (pit = lit->files.begin(); pit != pend; ++pit) {
|
|
populate(pit->cg_files, app_image,
|
|
pclass, pc, debug_info, merge_lib);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void callgraph_container::populate(list<string> const & cg_files,
|
|
string const & app_image, size_t pclass,
|
|
profile_container const & pc, bool debug_info, bool merge_lib)
|
|
{
|
|
list<string>::const_iterator it;
|
|
list<string>::const_iterator const end = cg_files.end();
|
|
for (it = cg_files.begin(); it != end; ++it) {
|
|
cverb << vdebug << "samples file : " << *it << endl;
|
|
|
|
parsed_filename caller_file =
|
|
parse_filename(*it, extra_found_images);
|
|
string const app_name = caller_file.image;
|
|
|
|
image_error error;
|
|
extra_found_images.find_image_path(caller_file.lib_image,
|
|
error, false);
|
|
|
|
if (error != image_ok)
|
|
report_image_error(caller_file.lib_image,
|
|
error, false, extra_found_images);
|
|
|
|
bool caller_bfd_ok = true;
|
|
op_bfd caller_bfd(caller_file.lib_image,
|
|
string_filter(), extra_found_images, caller_bfd_ok);
|
|
if (!caller_bfd_ok)
|
|
report_image_error(caller_file.lib_image,
|
|
image_format_failure, false,
|
|
extra_found_images);
|
|
|
|
parsed_filename callee_file =
|
|
parse_filename(*it, extra_found_images);
|
|
|
|
extra_found_images.find_image_path(callee_file.cg_image,
|
|
error, false);
|
|
if (error != image_ok)
|
|
report_image_error(callee_file.cg_image,
|
|
error, false, extra_found_images);
|
|
|
|
bool callee_bfd_ok = true;
|
|
op_bfd callee_bfd(callee_file.cg_image,
|
|
string_filter(), extra_found_images, callee_bfd_ok);
|
|
if (!callee_bfd_ok)
|
|
report_image_error(callee_file.cg_image,
|
|
image_format_failure, false,
|
|
extra_found_images);
|
|
|
|
profile_t profile;
|
|
// We can't use start_offset support in profile_t, give
|
|
// it a zero offset and we will fix that in add()
|
|
profile.add_sample_file(*it);
|
|
add(profile, caller_bfd, caller_bfd_ok, callee_bfd,
|
|
merge_lib ? app_image : app_name, pc,
|
|
debug_info, pclass);
|
|
}
|
|
}
|
|
|
|
|
|
void callgraph_container::
|
|
add(profile_t const & profile, op_bfd const & caller_bfd, bool caller_bfd_ok,
|
|
op_bfd const & callee_bfd, string const & app_name,
|
|
profile_container const & pc, bool debug_info, size_t pclass)
|
|
{
|
|
string const image_name = caller_bfd.get_filename();
|
|
|
|
opd_header const & header = profile.get_header();
|
|
|
|
// We can't use kernel sample file w/o the binary else we will
|
|
// use it with a zero offset, the code below will abort because
|
|
// we will get incorrect callee sub-range and out of range
|
|
// callee vma. FIXME
|
|
if (header.is_kernel && !caller_bfd_ok)
|
|
return;
|
|
|
|
// We must handle start_offset, this offset can be different for the
|
|
// caller and the callee: kernel sample traversing the syscall barrier.
|
|
u32 caller_offset;
|
|
if (header.is_kernel)
|
|
caller_offset = caller_bfd.get_start_offset(0);
|
|
else
|
|
caller_offset = header.anon_start;
|
|
|
|
u32 callee_offset;
|
|
if (header.cg_to_is_kernel)
|
|
callee_offset = callee_bfd.get_start_offset(0);
|
|
else
|
|
callee_offset = header.cg_to_anon_start;
|
|
|
|
image_name_id image_id = image_names.create(image_name);
|
|
image_name_id callee_image_id = image_names.create(callee_bfd.get_filename());
|
|
image_name_id app_id = image_names.create(app_name);
|
|
|
|
call_data caller(pc, profile, caller_bfd, caller_offset, image_id,
|
|
app_id, debug_info);
|
|
call_data callee(pc, profile, callee_bfd, callee_offset,
|
|
callee_image_id, app_id, debug_info);
|
|
|
|
if (cverb << vdebug) {
|
|
caller.verbose_bfd("Caller:");
|
|
callee.verbose_bfd("Callee:");
|
|
}
|
|
|
|
// For each symbol in the caller bfd, process all arcs to
|
|
// callee bfd symbols
|
|
|
|
for (symbol_index_t i = 0; i < caller_bfd.syms.size(); ++i) {
|
|
|
|
caller.caller_sym(i);
|
|
|
|
call_data::const_iterator dit = caller.samples.begin();
|
|
call_data::const_iterator dend = caller.samples.end();
|
|
while (dit != dend) {
|
|
// if we can't find the callee, skip an arc
|
|
if (!callee.callee_sym(key_to_callee(dit->first))) {
|
|
++dit;
|
|
continue;
|
|
}
|
|
|
|
count_array_t arc_count;
|
|
arc_count[pclass] =
|
|
accumulate_callee(dit, dend, callee.callee_end);
|
|
|
|
recorder.add(caller.sym, &callee.sym, arc_count);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void callgraph_container::add_symbols(profile_container const & pc)
|
|
{
|
|
symbol_container::symbols_t::iterator it;
|
|
symbol_container::symbols_t::iterator const end = pc.end_symbol();
|
|
|
|
for (it = pc.begin_symbol(); it != end; ++it)
|
|
recorder.add(*it, 0, count_array_t());
|
|
}
|
|
|
|
|
|
column_flags callgraph_container::output_hint() const
|
|
{
|
|
column_flags output_hints = cf_none;
|
|
|
|
// FIXME: costly: must we access directly recorder map ?
|
|
symbol_collection syms = recorder.get_symbols();
|
|
|
|
symbol_collection::iterator it;
|
|
symbol_collection::iterator const end = syms.end();
|
|
for (it = syms.begin(); it != end; ++it)
|
|
output_hints = (*it)->output_hint(output_hints);
|
|
|
|
return output_hints;
|
|
}
|
|
|
|
|
|
count_array_t callgraph_container::samples_count() const
|
|
{
|
|
return total_count;
|
|
}
|
|
|
|
|
|
symbol_collection const & callgraph_container::get_symbols() const
|
|
{
|
|
return recorder.get_symbols();
|
|
}
|