/** * @file format_output.cpp * outputting format for symbol lists * * @remark Copyright 2002 OProfile authors * @remark Read the file COPYING * * @author Philippe Elie * @author John Levon */ /* older glibc has C99 INFINITY in _GNU_SOURCE */ #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #include <cassert> #include <sstream> #include <iomanip> #include <iostream> #include <cmath> #include "string_manip.h" #include "string_filter.h" #include "format_output.h" #include "profile_container.h" #include "callgraph_container.h" #include "diff_container.h" #include "arrange_profiles.h" #include "xml_output.h" #include "xml_utils.h" #include "cverb.h" using namespace std; namespace { string const get_linenr_info(file_location const floc, bool lf) { ostringstream out; string const & filename = lf ? debug_names.name(floc.filename) : debug_names.basename(floc.filename); if (!filename.empty()) out << filename << ":" << floc.linenr; else out << "(no location information)"; return out.str(); } string get_vma(bfd_vma vma, bool vma_64) { ostringstream out; int width = vma_64 ? 16 : 8; out << hex << setw(width) << setfill('0') << vma; return out.str(); } string get_percent(count_type dividend, count_type divisor) { double ratio = op_ratio(dividend, divisor); return ::format_percent(ratio * 100, percent_int_width, percent_fract_width); } bool extract_linenr_info(string const & info, string & file, size_t & line) { line = 0; file = ""; string::size_type colon_pos = info.find(":"); if (colon_pos == string::npos) return false; file = info.substr(0, colon_pos); istringstream is_info(info.substr(colon_pos+1)); is_info >> line; return true; } } // anonymous namespace namespace format_output { formatter::formatter(extra_images const & extra) : nr_classes(1), flags(ff_none), vma_64(false), long_filenames(false), need_header(true), extra_found_images(extra) { format_map[ff_vma] = field_description(9, "vma", &formatter::format_vma); format_map[ff_nr_samples] = field_description(9, "samples", &formatter::format_nr_samples); format_map[ff_nr_samples_cumulated] = field_description(14, "cum. samples", &formatter::format_nr_cumulated_samples); format_map[ff_percent] = field_description(9, "%", &formatter::format_percent); format_map[ff_percent_cumulated] = field_description(11, "cum. %", &formatter::format_cumulated_percent); format_map[ff_linenr_info] = field_description(28, "linenr info", &formatter::format_linenr_info); format_map[ff_image_name] = field_description(25, "image name", &formatter::format_image_name); format_map[ff_app_name] = field_description(25, "app name", &formatter::format_app_name); format_map[ff_symb_name] = field_description(30, "symbol name", &formatter::format_symb_name); format_map[ff_percent_details] = field_description(9, "%", &formatter::format_percent_details); format_map[ff_percent_cumulated_details] = field_description(10, "cum. %", &formatter::format_cumulated_percent_details); format_map[ff_diff] = field_description(10, "diff %", &formatter::format_diff); } formatter::~formatter() { } void formatter::set_nr_classes(size_t nr) { nr_classes = nr; } void formatter::add_format(format_flags flag) { flags = static_cast<format_flags>(flags | flag); } void formatter::show_header(bool on_off) { need_header = on_off; } void formatter::vma_format_64bit(bool on_off) { vma_64 = on_off; } void formatter::show_long_filenames(bool on_off) { long_filenames = on_off; } void formatter::show_global_percent(bool on_off) { global_percent = on_off; } void formatter::output_header(ostream & out) { if (!need_header) return; size_t padding = 0; // first output the vma field if (flags & ff_vma) padding = output_header_field(out, ff_vma, padding); // the field repeated for each profile class for (size_t pclass = 0 ; pclass < nr_classes; ++pclass) { if (flags & ff_nr_samples) padding = output_header_field(out, ff_nr_samples, padding); if (flags & ff_nr_samples_cumulated) padding = output_header_field(out, ff_nr_samples_cumulated, padding); if (flags & ff_percent) padding = output_header_field(out, ff_percent, padding); if (flags & ff_percent_cumulated) padding = output_header_field(out, ff_percent_cumulated, padding); if (flags & ff_diff) padding = output_header_field(out, ff_diff, padding); if (flags & ff_percent_details) padding = output_header_field(out, ff_percent_details, padding); if (flags & ff_percent_cumulated_details) padding = output_header_field(out, ff_percent_cumulated_details, padding); } // now the remaining field if (flags & ff_linenr_info) padding = output_header_field(out, ff_linenr_info, padding); if (flags & ff_image_name) padding = output_header_field(out, ff_image_name, padding); if (flags & ff_app_name) padding = output_header_field(out, ff_app_name, padding); if (flags & ff_symb_name) padding = output_header_field(out, ff_symb_name, padding); out << "\n"; } /// describe each possible field of colummned output. // FIXME: use % of the screen width here. sum of % equal to 100, then calculate // ratio between 100 and the selected % to grow non fixed field use also // lib[n?]curses to get the console width (look info source) (so on add a fixed // field flags) size_t formatter:: output_field(ostream & out, field_datum const & datum, format_flags fl, size_t padding, bool hide_immutable) { if (!hide_immutable) { out << string(padding, ' '); field_description const & field(format_map[fl]); string str = (this->*field.formatter)(datum); out << str; // at least one separator char padding = 1; if (str.length() < field.width) padding = field.width - str.length(); } else { field_description const & field(format_map[fl]); padding += field.width; } return padding; } size_t formatter:: output_header_field(ostream & out, format_flags fl, size_t padding) { out << string(padding, ' '); field_description const & field(format_map[fl]); out << field.header_name; // at least one separator char padding = 1; if (field.header_name.length() < field.width) padding = field.width - field.header_name.length(); return padding; } string formatter::format_vma(field_datum const & f) { return get_vma(f.sample.vma, vma_64); } string formatter::format_symb_name(field_datum const & f) { return symbol_names.demangle(f.symbol.name); } string formatter::format_image_name(field_datum const & f) { return get_image_name(f.symbol.image_name, long_filenames ? image_name_storage::int_real_filename : image_name_storage::int_real_basename, extra_found_images); } string formatter::format_app_name(field_datum const & f) { return get_image_name(f.symbol.app_name, long_filenames ? image_name_storage::int_real_filename : image_name_storage::int_real_basename, extra_found_images); } string formatter::format_linenr_info(field_datum const & f) { return get_linenr_info(f.sample.file_loc, long_filenames); } string formatter::format_nr_samples(field_datum const & f) { ostringstream out; out << f.sample.counts[f.pclass]; return out.str(); } string formatter::format_nr_cumulated_samples(field_datum const & f) { if (f.diff == -INFINITY) return "---"; ostringstream out; f.counts.cumulated_samples[f.pclass] += f.sample.counts[f.pclass]; out << f.counts.cumulated_samples[f.pclass]; return out.str(); } string formatter::format_percent(field_datum const & f) { if (f.diff == -INFINITY) return "---"; return get_percent(f.sample.counts[f.pclass], f.counts.total[f.pclass]); } string formatter::format_cumulated_percent(field_datum const & f) { if (f.diff == -INFINITY) return "---"; f.counts.cumulated_percent[f.pclass] += f.sample.counts[f.pclass]; return get_percent(f.counts.cumulated_percent[f.pclass], f.counts.total[f.pclass]); } string formatter::format_percent_details(field_datum const & f) { return get_percent(f.sample.counts[f.pclass], f.counts.total[f.pclass]); } string formatter::format_cumulated_percent_details(field_datum const & f) { f.counts.cumulated_percent_details[f.pclass] += f.sample.counts[f.pclass]; return get_percent(f.counts.cumulated_percent_details[f.pclass], f.counts.total[f.pclass]); } string formatter::format_diff(field_datum const & f) { if (f.diff == INFINITY) return "+++"; else if (f.diff == -INFINITY) return "---"; return ::format_percent(f.diff, percent_int_width, percent_fract_width, true); } void formatter:: do_output(ostream & out, symbol_entry const & symb, sample_entry const & sample, counts_t & c, diff_array_t const & diffs, bool hide_immutable) { size_t padding = 0; // first output the vma field field_datum datum(symb, sample, 0, c, extra_found_images); if (flags & ff_vma) padding = output_field(out, datum, ff_vma, padding, false); // repeated fields for each profile class for (size_t pclass = 0 ; pclass < nr_classes; ++pclass) { field_datum datum(symb, sample, pclass, c, extra_found_images, diffs[pclass]); if (flags & ff_nr_samples) padding = output_field(out, datum, ff_nr_samples, padding, false); if (flags & ff_nr_samples_cumulated) padding = output_field(out, datum, ff_nr_samples_cumulated, padding, false); if (flags & ff_percent) padding = output_field(out, datum, ff_percent, padding, false); if (flags & ff_percent_cumulated) padding = output_field(out, datum, ff_percent_cumulated, padding, false); if (flags & ff_diff) padding = output_field(out, datum, ff_diff, padding, false); if (flags & ff_percent_details) padding = output_field(out, datum, ff_percent_details, padding, false); if (flags & ff_percent_cumulated_details) padding = output_field(out, datum, ff_percent_cumulated_details, padding, false); } // now the remaining field if (flags & ff_linenr_info) padding = output_field(out, datum, ff_linenr_info, padding, false); if (flags & ff_image_name) padding = output_field(out, datum, ff_image_name, padding, hide_immutable); if (flags & ff_app_name) padding = output_field(out, datum, ff_app_name, padding, hide_immutable); if (flags & ff_symb_name) padding = output_field(out, datum, ff_symb_name, padding, hide_immutable); out << "\n"; } opreport_formatter::opreport_formatter(profile_container const & p) : formatter(p.extra_found_images), profile(p), need_details(false) { counts.total = profile.samples_count(); } void opreport_formatter::show_details(bool on_off) { need_details = on_off; } void opreport_formatter::output(ostream & out, symbol_entry const * symb) { do_output(out, *symb, symb->sample, counts); if (need_details) output_details(out, symb); } void opreport_formatter:: output(ostream & out, symbol_collection const & syms) { output_header(out); symbol_collection::const_iterator it = syms.begin(); symbol_collection::const_iterator end = syms.end(); for (; it != end; ++it) output(out, *it); } void opreport_formatter:: output_details(ostream & out, symbol_entry const * symb) { counts_t c = counts; if (!global_percent) c.total = symb->sample.counts; // cumulated percent are relative to current symbol. c.cumulated_samples = count_array_t(); c.cumulated_percent = count_array_t(); sample_container::samples_iterator it = profile.begin(symb); sample_container::samples_iterator end = profile.end(symb); for (; it != end; ++it) { out << " "; do_output(out, *symb, it->second, c, diff_array_t(), true); } } cg_formatter::cg_formatter(callgraph_container const & profile) : formatter(profile.extra_found_images) { counts.total = profile.samples_count(); } void cg_formatter::output(ostream & out, symbol_collection const & syms) { // amount of spacing prefixing child and parent lines string const child_parent_prefix(" "); output_header(out); out << string(79, '-') << endl; symbol_collection::const_iterator it; symbol_collection::const_iterator end = syms.end(); for (it = syms.begin(); it < end; ++it) { cg_symbol const * sym = dynamic_cast<cg_symbol const *>(*it); cg_symbol::children::const_iterator cit; cg_symbol::children::const_iterator cend = sym->callers.end(); counts_t c; if (global_percent) c.total = counts.total; else c.total = sym->total_caller_count; for (cit = sym->callers.begin(); cit != cend; ++cit) { out << child_parent_prefix; do_output(out, *cit, cit->sample, c); } do_output(out, *sym, sym->sample, counts); c = counts_t(); if (global_percent) c.total = counts.total; else c.total = sym->total_callee_count; cend = sym->callees.end(); for (cit = sym->callees.begin(); cit != cend; ++cit) { out << child_parent_prefix; do_output(out, *cit, cit->sample, c); } out << string(79, '-') << endl; } } diff_formatter::diff_formatter(diff_container const & profile, extra_images const & extra) : formatter(extra) { counts.total = profile.samples_count(); } void diff_formatter::output(ostream & out, diff_collection const & syms) { output_header(out); diff_collection::const_iterator it = syms.begin(); diff_collection::const_iterator end = syms.end(); for (; it != end; ++it) do_output(out, *it, it->sample, counts, it->diffs); } // local variables used in generation of XML // buffer details for output later ostringstream bytes_out; // module+symbol table for detecting duplicate symbols map<string, size_t> symbol_data_table; size_t symbol_data_index = 0; /* Return any existing index or add to the table */ size_t xml_get_symbol_index(string const & name) { size_t index = symbol_data_index; map<string, size_t>::iterator it = symbol_data_table.find(name); if (it == symbol_data_table.end()) { symbol_data_table[name] = symbol_data_index++; return index; } return it->second; } class symbol_details_t { public: symbol_details_t() { size = index = 0; id = -1; } int id; size_t size; size_t index; string details; }; typedef growable_vector<symbol_details_t> symbol_details_array_t; symbol_details_array_t symbol_details; size_t detail_table_index = 0; xml_formatter:: xml_formatter(profile_container const * p, symbol_collection & s, extra_images const & extra, string_filter const & sf) : formatter(extra), profile(p), symbols(s), need_details(false), symbol_filter(sf) { if (profile) counts.total = profile->samples_count(); } void xml_formatter:: show_details(bool on_off) { need_details = on_off; } void xml_formatter::output(ostream & out) { xml_support->build_subclasses(out); xml_support->output_program_structure(out); output_symbol_data(out); if (need_details) { out << open_element(DETAIL_TABLE); for (size_t i = 0; i < symbol_details.size(); ++i) { int id = symbol_details[i].id; if (id >= 0) { out << open_element(SYMBOL_DETAILS, true); out << init_attr(TABLE_ID, (size_t)id); out << close_element(NONE, true); out << symbol_details[i].details; out << close_element(SYMBOL_DETAILS); } } out << close_element(DETAIL_TABLE); // output bytesTable out << open_element(BYTES_TABLE); out << bytes_out.str(); out << close_element(BYTES_TABLE); } out << close_element(PROFILE); } bool xml_formatter::get_bfd_object(symbol_entry const * symb, op_bfd * & abfd) const { bool ok = true; string const & image_name = get_image_name(symb->image_name, image_name_storage::int_filename, extra_found_images); if (symb->spu_offset) { // FIXME: what about archive:tmp, actually it's not supported // for spu since oparchive doesn't archive the real file but // in future it would work ? string tmp = get_image_name(symb->embedding_filename, image_name_storage::int_filename, extra_found_images); if (abfd && abfd->get_filename() == tmp) return true; delete abfd; abfd = new op_bfd(symb->spu_offset, tmp, symbol_filter, extra_found_images, ok); } else { if (abfd && abfd->get_filename() == image_name) return true; delete abfd; abfd = new op_bfd(image_name, symbol_filter, extra_found_images, ok); } if (!ok) { report_image_error(image_name, image_format_failure, false, extra_found_images); delete abfd; abfd = 0; return false; } return true; } void xml_formatter:: output_the_symbol_data(ostream & out, symbol_entry const * symb, op_bfd * & abfd) { string const name = symbol_names.name(symb->name); assert(name.size() > 0); string const image = get_image_name(symb->image_name, image_name_storage::int_filename, extra_found_images); string const qname = image + ":" + name; map<string, size_t>::iterator sd_it = symbol_data_table.find(qname); if (sd_it != symbol_data_table.end()) { // first time we've seen this symbol out << open_element(SYMBOL_DATA, true); out << init_attr(TABLE_ID, sd_it->second); field_datum datum(*symb, symb->sample, 0, counts, extra_found_images); output_attribute(out, datum, ff_symb_name, NAME); if (flags & ff_linenr_info) { output_attribute(out, datum, ff_linenr_info, SOURCE_FILE); output_attribute(out, datum, ff_linenr_info, SOURCE_LINE); } if (name.size() > 0 && name[0] != '?') { output_attribute(out, datum, ff_vma, STARTING_ADDR); if (need_details) { get_bfd_object(symb, abfd); if (abfd && abfd->symbol_has_contents(symb->sym_index)) xml_support->output_symbol_bytes(bytes_out, symb, sd_it->second, *abfd); } } out << close_element(); // seen so remove (otherwise get several "no symbols") symbol_data_table.erase(qname); } } void xml_formatter::output_cg_children(ostream & out, cg_symbol::children const cg_symb, op_bfd * & abfd) { cg_symbol::children::const_iterator cit; cg_symbol::children::const_iterator cend = cg_symb.end(); for (cit = cg_symb.begin(); cit != cend; ++cit) { string const name = symbol_names.name(cit->name); string const image = get_image_name(cit->image_name, image_name_storage::int_filename, extra_found_images); string const qname = image + ":" + name; map<string, size_t>::iterator sd_it = symbol_data_table.find(qname); if (sd_it != symbol_data_table.end()) { symbol_entry const * child = &(*cit); output_the_symbol_data(out, child, abfd); } } } void xml_formatter::output_symbol_data(ostream & out) { op_bfd * abfd = NULL; sym_iterator it = symbols.begin(); sym_iterator end = symbols.end(); out << open_element(SYMBOL_TABLE); for ( ; it != end; ++it) { symbol_entry const * symb = *it; cg_symbol const * cg_symb = dynamic_cast<cg_symbol const *>(symb); output_the_symbol_data(out, symb, abfd); if (cg_symb) { /* make sure callers/callees are included in SYMBOL_TABLE */ output_cg_children(out, cg_symb->callers, abfd); output_cg_children(out, cg_symb->callees, abfd); } } out << close_element(SYMBOL_TABLE); delete abfd; } string xml_formatter:: output_symbol_details(symbol_entry const * symb, size_t & detail_index, size_t const lo, size_t const hi) { if (!has_sample_counts(symb->sample.counts, lo, hi)) return ""; sample_container::samples_iterator it = profile->begin(symb); sample_container::samples_iterator end = profile->end(symb); ostringstream str; for (; it != end; ++it) { counts_t c; for (size_t p = lo; p <= hi; ++p) { size_t count = it->second.counts[p]; if (count == 0) continue; str << open_element(DETAIL_DATA, true); str << init_attr(TABLE_ID, detail_index++); // first output the vma field field_datum datum(*symb, it->second, 0, c, extra_found_images, 0.0); output_attribute(str, datum, ff_vma, VMA); if (ff_linenr_info) { string sym_file; size_t sym_line; string samp_file; size_t samp_line; string sym_info = get_linenr_info(symb->sample.file_loc, true); string samp_info = get_linenr_info(it->second.file_loc, true); if (extract_linenr_info(samp_info, samp_file, samp_line)) { if (extract_linenr_info(sym_info, sym_file, sym_line)) { // only output source_file if it is different than the symbol's // source file. this can happen with inlined functions in // #included header files if (sym_file != samp_file) str << init_attr(SOURCE_FILE, samp_file); } str << init_attr(SOURCE_LINE, samp_line); } } str << close_element(NONE, true); // output buffered sample data output_sample_data(str, it->second, p); str << close_element(DETAIL_DATA); } } return str.str(); } void xml_formatter:: output_symbol(ostream & out, symbol_entry const * symb, size_t lo, size_t hi, bool is_module) { ostringstream str; // pointless reference to is_module, remove insane compiler warning size_t indx = is_module ? 0 : 1; // output symbol's summary data for each profile class bool got_samples = false; for (size_t p = lo; p <= hi; ++p) { got_samples |= xml_support->output_summary_data(str, symb->sample.counts, p); } if (!got_samples) return; if (cverb << vxml) out << "<!-- symbol_ref=" << symbol_names.name(symb->name) << " -->" << endl; out << open_element(SYMBOL, true); string const name = symbol_names.name(symb->name); assert(name.size() > 0); string const image = get_image_name(symb->image_name, image_name_storage::int_filename, extra_found_images); string const qname = image + ":" + name; indx = xml_get_symbol_index(qname); out << init_attr(ID_REF, indx); if (need_details) { ostringstream details; symbol_details_t & sd = symbol_details[indx]; size_t const detail_lo = sd.index; string detail_str = output_symbol_details(symb, sd.index, lo, hi); if (detail_str.size() > 0) { if (sd.id < 0) sd.id = indx; details << detail_str; } if (sd.index > detail_lo) { sd.details = sd.details + details.str(); out << init_attr(DETAIL_LO, detail_lo); out << init_attr(DETAIL_HI, sd.index-1); } } out << close_element(NONE, true); // output summary out << str.str(); out << close_element(SYMBOL); } void xml_formatter:: output_sample_data(ostream & out, sample_entry const & sample, size_t pclass) { out << open_element(COUNT, true); out << init_attr(CLASS, classes.v[pclass].name); out << close_element(NONE, true); out << sample.counts[pclass]; out << close_element(COUNT); } void xml_formatter:: output_attribute(ostream & out, field_datum const & datum, format_flags fl, tag_t tag) { field_description const & field(format_map[fl]); string str = (this->*field.formatter)(datum); if (!str.empty()) { if (fl == ff_linenr_info && (tag == SOURCE_LINE || tag == SOURCE_FILE)) { string file; size_t line; if (extract_linenr_info(str, file, line)) { if (tag == SOURCE_LINE) out << init_attr(tag, line); else out << init_attr(tag, file); } } else out << " " << init_attr(tag, str); } } xml_cg_formatter:: xml_cg_formatter(callgraph_container const & cg, symbol_collection & s, string_filter const & sf) : xml_formatter(0, s, cg.extra_found_images, sf), callgraph(cg) { counts.total = callgraph.samples_count(); } void xml_cg_formatter:: output_symbol_core(ostream & out, cg_symbol::children const cg_symb, string const selfname, string const qname, size_t lo, size_t hi, bool is_module, tag_t tag) { cg_symbol::children::const_iterator cit; cg_symbol::children::const_iterator cend = cg_symb.end(); for (cit = cg_symb.begin(); cit != cend; ++cit) { string const & module = get_image_name((cit)->image_name, image_name_storage::int_filename, extra_found_images); bool self = false; ostringstream str; size_t indx; // output symbol's summary data for each profile class for (size_t p = lo; p <= hi; ++p) xml_support->output_summary_data(str, cit->sample.counts, p); if (cverb << vxml) out << "<!-- symbol_ref=" << symbol_names.name(cit->name) << " -->" << endl; if (is_module) { out << open_element(MODULE, true); out << init_attr(NAME, module) << close_element(NONE, true); } out << open_element(SYMBOL, true); string const symname = symbol_names.name(cit->name); assert(symname.size() > 0); string const symqname = module + ":" + symname; // Find any self references and handle if ((symname == selfname) && (tag == CALLEES)) { self = true; indx = xml_get_symbol_index(qname); } else { indx = xml_get_symbol_index(symqname); } out << init_attr(ID_REF, indx); if (self) out << init_attr(SELFREF, "true"); out << close_element(NONE, true); out << str.str(); out << close_element(SYMBOL); if (is_module) out << close_element(MODULE); } } void xml_cg_formatter:: output_symbol(ostream & out, symbol_entry const * symb, size_t lo, size_t hi, bool is_module) { cg_symbol const * cg_symb = dynamic_cast<cg_symbol const *>(symb); ostringstream str; size_t indx; // output symbol's summary data for each profile class for (size_t p = lo; p <= hi; ++p) xml_support->output_summary_data(str, symb->sample.counts, p); if (cverb << vxml) out << "<!-- symbol_ref=" << symbol_names.name(symb->name) << " -->" << endl; out << open_element(SYMBOL, true); string const name = symbol_names.name(symb->name); assert(name.size() > 0); string const image = get_image_name(symb->image_name, image_name_storage::int_filename, extra_found_images); string const qname = image + ":" + name; string const selfname = symbol_names.demangle(symb->name) + " [self]"; indx = xml_get_symbol_index(qname); out << init_attr(ID_REF, indx); out << close_element(NONE, true); out << open_element(CALLERS); if (cg_symb) output_symbol_core(out, cg_symb->callers, selfname, qname, lo, hi, is_module, CALLERS); out << close_element(CALLERS); out << open_element(CALLEES); if (cg_symb) output_symbol_core(out, cg_symb->callees, selfname, qname, lo, hi, is_module, CALLEES); out << close_element(CALLEES); // output summary out << str.str(); out << close_element(SYMBOL); } } // namespace format_output