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