/**
* @file opreport.cpp
* Implement opreport utility
*
* @remark Copyright 2003 OProfile authors
* @remark Read the file COPYING
*
* @author John Levon
* @author Philippe Elie
*/
#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <sstream>
#include <numeric>
#include "op_exception.h"
#include "stream_util.h"
#include "string_manip.h"
#include "file_manip.h"
#include "opreport_options.h"
#include "op_header.h"
#include "profile.h"
#include "populate.h"
#include "arrange_profiles.h"
#include "profile_container.h"
#include "callgraph_container.h"
#include "diff_container.h"
#include "symbol_sort.h"
#include "format_output.h"
#include "xml_utils.h"
#include "image_errors.h"
using namespace std;
namespace {
static size_t nr_classes;
/// storage for a merged file summary
struct summary {
count_array_t counts;
string lib_image;
bool operator<(summary const & rhs) const {
return options::reverse_sort
? counts[0] < rhs.counts[0] : rhs.counts[0] < counts[0];
}
/// add a set of files to a summary
count_type add_files(list<profile_sample_files> const & files,
size_t pclass);
};
count_type summary::
add_files(list<profile_sample_files> const & files, size_t pclass)
{
count_type subtotal = 0;
list<profile_sample_files>::const_iterator it = files.begin();
list<profile_sample_files>::const_iterator const end = files.end();
for (; it != end; ++it) {
count_type count = profile_t::sample_count(it->sample_filename);
counts[pclass] += count;
subtotal += count;
if (!it->cg_files.empty()) {
throw op_runtime_error("opreport.cpp::add_files(): "
"unxpected non empty cg file set");
}
}
return subtotal;
}
/**
* Summary of an application: a set of image summaries
* for one application, i.e. an application image and all
* dependent images such as libraries.
*/
struct app_summary {
/// total count of us and all dependents
count_array_t counts;
/// the main image
string image;
/// our dependent images
vector<summary> deps;
/// construct and fill in the data
count_type add_profile(profile_set const & profile, size_t pclass);
bool operator<(app_summary const & rhs) const {
return options::reverse_sort
? counts[0] < rhs.counts[0] : rhs.counts[0] < counts[0];
}
private:
/// find a matching summary (including main app summary)
summary & find_summary(string const & image);
};
summary & app_summary::find_summary(string const & image)
{
vector<summary>::iterator sit = deps.begin();
vector<summary>::iterator const send = deps.end();
for (; sit != send; ++sit) {
if (sit->lib_image == image)
return *sit;
}
summary summ;
summ.lib_image = image;
deps.push_back(summ);
return deps.back();
}
count_type app_summary::add_profile(profile_set const & profile,
size_t pclass)
{
count_type group_total = 0;
// first the main image
summary & summ = find_summary(profile.image);
count_type app_count = summ.add_files(profile.files, pclass);
counts[pclass] += app_count;
group_total += app_count;
// now all dependent images if any
list<profile_dep_set>::const_iterator it = profile.deps.begin();
list<profile_dep_set>::const_iterator const end = profile.deps.end();
for (; it != end; ++it) {
summary & summ = find_summary(it->lib_image);
count_type lib_count = summ.add_files(it->files, pclass);
counts[pclass] += lib_count;
group_total += lib_count;
}
return group_total;
}
/// all the summaries
struct summary_container {
summary_container(vector<profile_class> const & pclasses);
/// all map summaries
vector<app_summary> apps;
/// total count of samples for all summaries
count_array_t total_counts;
};
summary_container::
summary_container(vector<profile_class> const & pclasses)
{
typedef map<string, app_summary> app_map_t;
app_map_t app_map;
for (size_t i = 0; i < pclasses.size(); ++i) {
list<profile_set>::const_iterator it
= pclasses[i].profiles.begin();
list<profile_set>::const_iterator const end
= pclasses[i].profiles.end();
for (; it != end; ++it) {
app_map_t::iterator ait = app_map.find(it->image);
if (ait == app_map.end()) {
app_summary app;
app.image = it->image;
total_counts[i] += app.add_profile(*it, i);
app_map[app.image] = app;
} else {
total_counts[i]
+= ait->second.add_profile(*it, i);
}
}
}
app_map_t::const_iterator it = app_map.begin();
app_map_t::const_iterator const end = app_map.end();
for (; it != end; ++it)
apps.push_back(it->second);
// sort by count
stable_sort(apps.begin(), apps.end());
vector<app_summary>::iterator ait = apps.begin();
vector<app_summary>::iterator const aend = apps.end();
for (; ait != aend; ++ait)
stable_sort(ait->deps.begin(), ait->deps.end());
}
void output_header()
{
if (!options::show_header)
return;
cout << classes.cpuinfo << endl;
if (!classes.event.empty())
cout << classes.event << endl;
for (vector<profile_class>::size_type i = 0;
i < classes.v.size(); ++i) {
cout << classes.v[i].longname << endl;
}
}
string get_filename(string const & filename)
{
return options::long_filenames ? filename : op_basename(filename);
}
/// Output a count and a percentage
void output_count(count_type total_count, count_type count)
{
cout << setw(9) << count << ' ';
double ratio = op_ratio(count, total_count);
cout << format_percent(ratio * 100, percent_int_width,
percent_fract_width) << ' ';
}
void output_col_headers(bool indent)
{
if (!options::show_header)
return;
if (indent)
cout << '\t';
size_t colwidth = 9 + 1 + percent_width;
for (size_t i = 0; i < classes.v.size(); ++i) {
string name = classes.v[i].name;
if (name.length() > colwidth)
name = name.substr(0, colwidth - 3)
+ "...";
io_state state(cout);
// gcc 2.95 doesn't know right io manipulator
cout.setf(ios::right, ios::adjustfield);
// gcc 2.95 doesn't honor setw() for std::string
cout << setw(colwidth) << name.c_str();
cout << '|';
}
cout << '\n';
if (indent)
cout << '\t';
for (size_t i = 0; i < classes.v.size(); ++i) {
cout << " samples| ";
io_state state(cout);
// gcc 2.95 doesn't know right io manipulator
cout.setf(ios::right, ios::adjustfield);
cout << setw(percent_width) << "%|";
}
cout << '\n';
if (indent)
cout << '\t';
for (size_t i = 0; i < classes.v.size(); ++i) {
cout << "-----------";
string str(percent_width, '-');
cout << str;
}
cout << '\n';
}
void
output_deps(summary_container const & summaries,
app_summary const & app)
{
// the app summary itself is *always* present
// (perhaps with zero counts) so this test
// is correct
if (app.deps.size() == 1)
return;
output_col_headers(true);
for (size_t j = 0 ; j < app.deps.size(); ++j) {
summary const & summ = app.deps[j];
if (summ.counts.zero())
continue;
cout << '\t';
for (size_t i = 0; i < nr_classes; ++i) {
count_type tot_count = options::global_percent
? summaries.total_counts[i] : app.counts[i];
output_count(tot_count, summ.counts[i]);
}
cout << get_filename(summ.lib_image);
cout << '\n';
}
}
/**
* Display all the given summary information
*/
void output_summaries(summary_container const & summaries)
{
output_col_headers(false);
for (size_t i = 0; i < summaries.apps.size(); ++i) {
app_summary const & app = summaries.apps[i];
if ((app.counts[0] * 100.0) / summaries.total_counts[0]
< options::threshold) {
continue;
}
for (size_t j = 0; j < nr_classes; ++j)
output_count(summaries.total_counts[j], app.counts[j]);
cout << get_filename(app.image) << '\n';
output_deps(summaries, app);
}
}
format_flags get_format_flags(column_flags const & cf)
{
format_flags flags(ff_none);
flags = format_flags(flags | ff_nr_samples);
flags = format_flags(flags | ff_percent | ff_symb_name);
if (options::show_address)
flags = format_flags(flags | ff_vma);
if (options::debug_info)
flags = format_flags(flags | ff_linenr_info);
if (options::accumulated) {
flags = format_flags(flags | ff_nr_samples_cumulated);
flags = format_flags(flags | ff_percent_cumulated);
}
if (classes2.v.size())
flags = format_flags(flags | ff_diff);
if (cf & cf_image_name)
flags = format_flags(flags | ff_image_name);
return flags;
}
void output_symbols(profile_container const & pc, bool multiple_apps)
{
profile_container::symbol_choice choice;
choice.threshold = options::threshold;
symbol_collection symbols = pc.select_symbols(choice);
options::sort_by.sort(symbols, options::reverse_sort,
options::long_filenames);
format_output::formatter * out;
format_output::xml_formatter * xml_out = 0;
format_output::opreport_formatter * text_out = 0;
if (options::xml) {
xml_out = new format_output::xml_formatter(&pc, symbols,
pc.extra_found_images, options::symbol_filter);
xml_out->show_details(options::details);
out = xml_out;
// for XML always output long filenames
out->show_long_filenames(true);
} else {
text_out = new format_output::opreport_formatter(pc);
text_out->show_details(options::details);
out = text_out;
out->show_long_filenames(options::long_filenames);
}
out->set_nr_classes(nr_classes);
out->show_header(options::show_header);
out->vma_format_64bit(choice.hints & cf_64bit_vma);
out->show_global_percent(options::global_percent);
format_flags flags = get_format_flags(choice.hints);
if (multiple_apps)
flags = format_flags(flags | ff_app_name);
out->add_format(flags);
if (options::xml) {
xml_support = new xml_utils(xml_out, symbols, nr_classes,
pc.extra_found_images);
xml_out->output(cout);
} else {
text_out->output(cout, symbols);
}
}
void output_diff_symbols(profile_container const & pc1,
profile_container const & pc2, bool multiple_apps)
{
diff_container dc(pc1, pc2);
profile_container::symbol_choice choice;
choice.threshold = options::threshold;
diff_collection symbols = dc.get_symbols(choice);
format_flags flags = get_format_flags(choice.hints);
if (multiple_apps)
flags = format_flags(flags | ff_app_name);
// With diff profile we output only filename coming from the first
// profile session, internally we use only name derived from the sample
// filename so image name can match.
format_output::diff_formatter out(dc, pc1.extra_found_images);
out.set_nr_classes(nr_classes);
out.show_long_filenames(options::long_filenames);
out.show_header(options::show_header);
out.show_global_percent(options::global_percent);
out.vma_format_64bit(choice.hints & cf_64bit_vma);
out.add_format(flags);
options::sort_by.sort(symbols, options::reverse_sort,
options::long_filenames);
out.output(cout, symbols);
}
void output_cg_symbols(callgraph_container const & cg, bool multiple_apps)
{
column_flags output_hints = cg.output_hint();
symbol_collection symbols = cg.get_symbols();
options::sort_by.sort(symbols, options::reverse_sort,
options::long_filenames);
format_output::formatter * out;
format_output::xml_cg_formatter * xml_out = 0;
format_output::cg_formatter * text_out = 0;
if (options::xml) {
xml_out = new format_output::xml_cg_formatter(cg, symbols,
options::symbol_filter);
xml_out->show_details(options::details);
out = xml_out;
// for XML always output long filenames
out->show_long_filenames(true);
} else {
text_out = new format_output::cg_formatter(cg);
out = text_out;
out->show_long_filenames(options::long_filenames);
}
out->set_nr_classes(nr_classes);
out->show_header(options::show_header);
out->vma_format_64bit(output_hints & cf_64bit_vma);
out->show_global_percent(options::global_percent);
format_flags flags = get_format_flags(output_hints);
if (multiple_apps)
flags = format_flags(flags | ff_app_name);
out->add_format(flags);
if (options::xml) {
xml_support = new xml_utils(xml_out, symbols, nr_classes,
cg.extra_found_images);
xml_out->output(cout);
} else {
text_out->output(cout, symbols);
}
}
int opreport(options::spec const & spec)
{
want_xml = options::xml;
handle_options(spec);
nr_classes = classes.v.size();
if (!options::symbols && !options::xml) {
summary_container summaries(classes.v);
output_header();
output_summaries(summaries);
return 0;
}
bool multiple_apps = false;
for (size_t i = 0; i < classes.v.size(); ++i) {
if (classes.v[i].profiles.size() > 1)
multiple_apps = true;
}
list<inverted_profile> iprofiles = invert_profiles(classes);
report_image_errors(iprofiles, classes.extra_found_images);
if (options::xml) {
xml_utils::output_xml_header(options::command_options,
classes.cpuinfo, classes.event);
} else {
output_header();
}
if (classes2.v.size()) {
for (size_t i = 0; i < classes2.v.size(); ++i) {
if (classes2.v[i].profiles.size() > 1)
multiple_apps |= true;
}
profile_container pc1(options::debug_info, options::details,
classes.extra_found_images);
list<inverted_profile>::iterator it = iprofiles.begin();
list<inverted_profile>::iterator const end = iprofiles.end();
for (; it != end; ++it)
populate_for_image(pc1, *it,
options::symbol_filter, 0);
list<inverted_profile> iprofiles2 = invert_profiles(classes2);
report_image_errors(iprofiles2, classes2.extra_found_images);
profile_container pc2(options::debug_info, options::details,
classes2.extra_found_images);
list<inverted_profile>::iterator it2 = iprofiles2.begin();
list<inverted_profile>::iterator const end2 = iprofiles2.end();
for (; it2 != end2; ++it2)
populate_for_image(pc2, *it2,
options::symbol_filter, 0);
output_diff_symbols(pc1, pc2, multiple_apps);
} else if (options::callgraph) {
callgraph_container cg_container;
cg_container.populate(iprofiles, classes.extra_found_images,
options::debug_info, options::threshold,
options::merge_by.lib, options::symbol_filter);
output_cg_symbols(cg_container, multiple_apps);
} else {
profile_container samples(options::debug_info,
options::details, classes.extra_found_images);
list<inverted_profile>::iterator it = iprofiles.begin();
list<inverted_profile>::iterator const end = iprofiles.end();
for (; it != end; ++it)
populate_for_image(samples, *it,
options::symbol_filter, 0);
output_symbols(samples, multiple_apps);
}
return 0;
}
} // anonymous namespace
int main(int argc, char const * argv[])
{
cout.tie(0);
return run_pp_tool(argc, argv, opreport);
}