/** * @file arrange_profiles.cpp * Classify and process a list of candidate sample files * into merged sets and classes. * * @remark Copyright 2003 OProfile authors * @remark Read the file COPYING * * @author John Levon */ #include <algorithm> #include <cstdlib> #include <iostream> #include <iterator> #include <map> #include <set> #include "string_manip.h" #include "op_header.h" #include "op_exception.h" #include "arrange_profiles.h" #include "format_output.h" #include "xml_utils.h" #include "parse_filename.h" #include "locate_images.h" using namespace std; namespace { int numeric_compare(string const & lhs, string const & rhs) { if (lhs == "all" && rhs == "all") return 0; // we choose an order arbitrarily if (lhs == "all") return 1; if (rhs == "all") return -1; unsigned int lhsval = op_lexical_cast<unsigned int>(lhs); unsigned int rhsval = op_lexical_cast<unsigned int>(rhs); if (lhsval == rhsval) return 0; if (lhsval < rhsval) return -1; return 1; } } // anonymous namespace // global to fix some C++ obscure corner case. bool operator<(profile_class const & lhs, profile_class const & rhs) { profile_template const & lt = lhs.ptemplate; profile_template const & rt = rhs.ptemplate; int comp; // The profile classes are used to traverse the sample data // arrays. We create XML elements for <process> and <thread> // that contain the sample data that can then be divided amongst // CPU, event, mask axes so it is more convenient to have the // process and thread classes be the outermost nesting level of // the sample data arrays if (!want_xml) { comp = numeric_compare(lt.cpu, rt.cpu); if (comp) return comp < 0; } comp = numeric_compare(lt.tgid, rt.tgid); if (comp) return comp < 0; comp = numeric_compare(lt.tid, rt.tid); if (comp) return comp < 0; comp = numeric_compare(lt.unitmask, rt.unitmask); if (comp) return comp < 0; if (want_xml) { if (lt.event != rt.event) return lt.event < rt.event; if (lt.count != rt.count) return lt.count < rt.count; return numeric_compare(lt.cpu, rt.cpu) < 0; } else { if (lt.event == rt.event) return lt.count < rt.count; return lt.event < rt.event; } } namespace { struct axis_t { string name; string suggestion; } axes[AXIS_MAX] = { { "event", "specify event:, count: or unitmask: (see also --merge=unitmask)" }, { "tgid", "specify tgid: or --merge tgid" }, { "tid", "specify tid: or --merge tid" }, { "cpu", "specify cpu: or --merge cpu" }, }; } // anonymous namespace bool profile_classes::matches(profile_classes const & classes) { if (v.size() != classes.v.size()) return false; axis_types const axis2 = classes.axis; switch (axis) { case AXIS_EVENT: break; case AXIS_TGID: case AXIS_TID: return axis2 == AXIS_TID || axis2 == AXIS_TGID; case AXIS_CPU: return axis2 == AXIS_CPU; case AXIS_MAX: return false; } // check that the events match (same event, count) vector<profile_class>::const_iterator it1 = v.begin(); vector<profile_class>::const_iterator end1 = v.end(); vector<profile_class>::const_iterator it2 = classes.v.begin(); while (it1 != end1) { if (it1->ptemplate.event != it2->ptemplate.event) return false; if (it1->ptemplate.count != it2->ptemplate.count) return false; // differing unit mask is considered comparable ++it1; ++it2; } return true; } namespace { typedef growable_vector<string> event_array_t; typedef growable_vector<string>::size_type event_index_t; bool new_event_index(string event, event_array_t & events, event_index_t & index) { event_index_t sz = events.size(); for (event_index_t i = 0; i != sz; ++i) { if (events[i] == event) { index = i; return false; } } index = sz; events[sz] = event; return true; } /// We have more than one axis of classification, tell the user. void report_error(profile_classes const & classes, axis_types newaxis) { string str = "Already displaying results for parameter "; str += axes[classes.axis].name; str += " with values:\n"; vector<profile_class>::const_iterator it = classes.v.begin(); vector<profile_class>::const_iterator const end = classes.v.end(); // We show error for the first conflicting axis but on this // axis we can get only a few different it->name, we display only // these different name. set <string> name_seen; size_t i = 5; for (; it != end && i; ++it) { if (name_seen.find(it->name) == name_seen.end()) { name_seen.insert(it->name); str += it->name + ","; --i; } } if (!i) { str += " and "; str += op_lexical_cast<string>(classes.v.size() - 5); str += " more,"; } str += "\nwhich conflicts with parameter "; str += axes[newaxis].name += ".\n"; str += "Suggestion: "; str += axes[classes.axis].suggestion; throw op_fatal_error(str); } /** * check that two different axes are OK - this is only * allowed if they are TGID,TID and for each class, * tid == tgid */ bool allow_axes(profile_classes const & classes, axis_types newaxis) { // No previous axis - OK if (classes.axis == AXIS_MAX) return true; if (classes.axis != AXIS_TID && classes.axis != AXIS_TGID) return false; if (newaxis != AXIS_TID && newaxis != AXIS_TGID) return false; vector<profile_class>::const_iterator it = classes.v.begin(); vector<profile_class>::const_iterator const end = classes.v.end(); for (; it != end; ++it) { if (it->ptemplate.tgid != it->ptemplate.tid) return false; } return true; } /// find the first sample file header in the class opd_header const get_first_header(profile_class const & pclass) { profile_set const & profile = *(pclass.profiles.begin()); string file; // could be only one main app, with no samples for the main image if (profile.files.empty()) { profile_dep_set const & dep = *(profile.deps.begin()); list<profile_sample_files> const & files = dep.files; profile_sample_files const & sample_files = *(files.begin()); if (!sample_files.sample_filename.empty()) file = sample_files.sample_filename; else file = *sample_files.cg_files.begin(); } else { profile_sample_files const & sample_files = *(profile.files.begin()); if (!sample_files.sample_filename.empty()) file = sample_files.sample_filename; else file = *sample_files.cg_files.begin(); } return read_header(file); } /// merge sample file header in the profile_sample_files void merge_header(profile_sample_files const & files, opd_header & header) { if (!files.sample_filename.empty()) { opd_header const temp = read_header(files.sample_filename); header.ctr_um |= temp.ctr_um; } list<string>::const_iterator it = files.cg_files.begin(); list<string>::const_iterator const end = files.cg_files.end(); for ( ; it != end; ++it) { opd_header const temp = read_header(*it); header.ctr_um |= temp.ctr_um; } } /// merge sample file header in the class opd_header const get_header(profile_class const & pclass, merge_option const & merge_by) { opd_header header = get_first_header(pclass); if (!merge_by.unitmask) return header; profile_set const & profile = *(pclass.profiles.begin()); typedef list<profile_sample_files>::const_iterator citerator; citerator it = profile.files.begin(); citerator const end = profile.files.end(); for ( ; it != end; ++it) merge_header(*it, header); list<profile_dep_set>::const_iterator dep_it = profile.deps.begin(); list<profile_dep_set>::const_iterator dep_end = profile.deps.end(); for ( ; dep_it != dep_end; ++dep_it) { citerator it = dep_it->files.begin(); citerator const end = dep_it->files.end(); for ( ; it != end; ++it) merge_header(*it, header); } return header; } /// Give human-readable names to each class. void name_classes(profile_classes & classes, merge_option const & merge_by) { opd_header header = get_header(classes.v[0], merge_by); classes.event = describe_header(header); classes.cpuinfo = describe_cpu(header); // If we're splitting on event anyway, clear out the // global event name if (classes.axis == AXIS_EVENT) classes.event.erase(); vector<profile_class>::iterator it = classes.v.begin(); vector<profile_class>::iterator const end = classes.v.end(); for (; it != end; ++it) { it->name = axes[classes.axis].name + ":"; switch (classes.axis) { case AXIS_EVENT: it->name = it->ptemplate.event + ":" + it->ptemplate.count; header = get_header(*it, merge_by); it->longname = describe_header(header); break; case AXIS_TGID: it->name += it->ptemplate.tgid; it->longname = "Processes with a thread group ID of "; it->longname += it->ptemplate.tgid; break; case AXIS_TID: it->name += it->ptemplate.tid; it->longname = "Processes with a thread ID of "; it->longname += it->ptemplate.tid; break; case AXIS_CPU: it->name += it->ptemplate.cpu; it->longname = "Samples on CPU " + it->ptemplate.cpu; break; case AXIS_MAX:; } } } /** * Name and verify classes. */ void identify_classes(profile_classes & classes, merge_option const & merge_by) { profile_template & ptemplate = classes.v[0].ptemplate; bool changed[AXIS_MAX] = { false, }; vector<profile_class>::iterator it = classes.v.begin(); ++it; vector<profile_class>::iterator end = classes.v.end(); // only one class, name it after the event if (it == end) changed[AXIS_EVENT] = true; for (; it != end; ++it) { if (it->ptemplate.event != ptemplate.event || it->ptemplate.count != ptemplate.count // unit mask are mergeable || (!merge_by.unitmask && it->ptemplate.unitmask != ptemplate.unitmask)) changed[AXIS_EVENT] = true; // we need the merge checks here because each // template is filled in from the first non // matching profile, so just because they differ // doesn't mean it's the axis we care about if (!merge_by.tgid && it->ptemplate.tgid != ptemplate.tgid) changed[AXIS_TGID] = true; if (!merge_by.tid && it->ptemplate.tid != ptemplate.tid) changed[AXIS_TID] = true; if (!merge_by.cpu && it->ptemplate.cpu != ptemplate.cpu) changed[AXIS_CPU] = true; } classes.axis = AXIS_MAX; for (size_t i = 0; i < AXIS_MAX; ++i) { if (!changed[i]) continue; if (!allow_axes(classes, axis_types(i))) report_error(classes, axis_types(i)); classes.axis = axis_types(i); /* do this early for report_error */ name_classes(classes, merge_by); } if (classes.axis == AXIS_MAX) { cerr << "Internal error - no equivalence class axis" << endl; abort(); } } void identify_xml_classes(profile_classes & classes, merge_option const & merge_by) { opd_header header = get_header(classes.v[0], merge_by); vector<profile_class>::iterator it = classes.v.begin(); vector<profile_class>::iterator end = classes.v.end(); event_index_t event_num; event_index_t event_max = 0; event_array_t event_array; size_t nr_cpus = 0; bool has_nonzero_mask = false; ostringstream event_setup; // fill in XML identifying each event, and replace event name by event_num for (; it != end; ++it) { string mask = it->ptemplate.unitmask; if (mask.find_first_of("x123456789abcdefABCDEF") != string::npos) has_nonzero_mask = true; if (new_event_index(it->ptemplate.event, event_array, event_num)) { // replace it->ptemplate.event with the event_num string // this is the first time we've seen this event header = get_header(*it, merge_by); event_setup << describe_header(header); event_max = event_num; } if (it->ptemplate.cpu != "all") { size_t cpu = atoi(it->ptemplate.cpu.c_str()); if (cpu > nr_cpus) nr_cpus = cpu; } ostringstream str; str << event_num; it->ptemplate.event = str.str(); } xml_utils::set_nr_cpus(++nr_cpus); xml_utils::set_nr_events(event_max+1); if (has_nonzero_mask) xml_utils::set_has_nonzero_masks(); classes.event = event_setup.str(); classes.cpuinfo = describe_cpu(header); } /// construct a class template from a profile profile_template const template_from_profile(parsed_filename const & parsed, merge_option const & merge_by) { profile_template ptemplate; ptemplate.event = parsed.event; ptemplate.count = parsed.count; if (!merge_by.unitmask) ptemplate.unitmask = parsed.unitmask; if (!merge_by.tgid) ptemplate.tgid = parsed.tgid; if (!merge_by.tid) ptemplate.tid = parsed.tid; if (!merge_by.cpu) ptemplate.cpu = parsed.cpu; return ptemplate; } /** * Find a matching class the sample file could go in, or generate * a new class if needed. * This is the heart of the merging and classification process. * The returned value is non-const reference but the ptemplate member * must be considered as const */ profile_class & find_class(set<profile_class> & classes, parsed_filename const & parsed, merge_option const & merge_by) { profile_class cls; cls.ptemplate = template_from_profile(parsed, merge_by); pair<set<profile_class>::iterator, bool> ret = classes.insert(cls); return const_cast<profile_class &>(*ret.first); } /** * Sanity check : we can't overwrite sample_filename, if we abort here it means * we fail to detect that parsed sample filename for two distinct samples * filename must go in two distinct profile_sample_files. This assumption is * false for callgraph samples files so this function is only called for non cg * files. */ void sanitize_profile_sample_files(profile_sample_files const & sample_files, parsed_filename const & parsed) { // We can't allow to overwrite sample_filename. if (!sample_files.sample_filename.empty()) { ostringstream out; out << "sanitize_profile_sample_files(): sample file " << "parsed twice ?\nsample_filename:\n" << sample_files.sample_filename << endl << parsed << endl; throw op_fatal_error(out.str()); } } /** * Add a sample filename (either cg or non cg files) to this profile. */ void add_to_profile_sample_files(profile_sample_files & sample_files, parsed_filename const & parsed) { if (parsed.cg_image.empty()) { // We can't allow to overwrite sample_filename. sanitize_profile_sample_files(sample_files, parsed); sample_files.sample_filename = parsed.filename; } else { sample_files.cg_files.push_back(parsed.filename); } } /** * we need to fix cg filename: a callgraph filename can occur before the binary * non callgraph samples filename occur so we must search. */ profile_sample_files & find_profile_sample_files(list<profile_sample_files> & files, parsed_filename const & parsed, extra_images const & extra) { list<profile_sample_files>::iterator it; list<profile_sample_files>::iterator const end = files.end(); for (it = files.begin(); it != end; ++it) { if (!it->sample_filename.empty()) { parsed_filename psample_filename = parse_filename(it->sample_filename, extra); if (psample_filename.lib_image == parsed.lib_image && psample_filename.image == parsed.image && psample_filename.profile_spec_equal(parsed)) return *it; } list<string>::const_iterator cit; list<string>::const_iterator const cend = it->cg_files.end(); for (cit = it->cg_files.begin(); cit != cend; ++cit) { parsed_filename pcg_filename = parse_filename(*cit, extra); if (pcg_filename.lib_image == parsed.lib_image && pcg_filename.image == parsed.image && pcg_filename.profile_spec_equal(parsed)) return *it; } } // not found, create a new one files.push_back(profile_sample_files()); return files.back(); } /** * Add a profile to particular profile set. If the new profile is * a dependent image, it gets added to the dep list, or just placed * on the normal list of profiles otherwise. */ void add_to_profile_set(profile_set & set, parsed_filename const & parsed, bool merge_by_lib, extra_images const & extra) { if (parsed.image == parsed.lib_image && !merge_by_lib) { profile_sample_files & sample_files = find_profile_sample_files(set.files, parsed, extra); add_to_profile_sample_files(sample_files, parsed); return; } list<profile_dep_set>::iterator it = set.deps.begin(); list<profile_dep_set>::iterator const end = set.deps.end(); for (; it != end; ++it) { if (it->lib_image == parsed.lib_image && !merge_by_lib && parsed.jit_dumpfile_exists == false) { profile_sample_files & sample_files = find_profile_sample_files(it->files, parsed, extra); add_to_profile_sample_files(sample_files, parsed); return; } } profile_dep_set depset; depset.lib_image = parsed.lib_image; profile_sample_files & sample_files = find_profile_sample_files(depset.files, parsed, extra); add_to_profile_sample_files(sample_files, parsed); set.deps.push_back(depset); } /** * Add a profile to a particular equivalence class. The previous matching * will have ensured the profile "fits", so now it's just a matter of * finding which sample file list it needs to go on. */ void add_profile(profile_class & pclass, parsed_filename const & parsed, bool merge_by_lib, extra_images const & extra) { list<profile_set>::iterator it = pclass.profiles.begin(); list<profile_set>::iterator const end = pclass.profiles.end(); for (; it != end; ++it) { if (it->image == parsed.image) { add_to_profile_set(*it, parsed, merge_by_lib, extra); return; } } profile_set set; set.image = parsed.image; add_to_profile_set(set, parsed, merge_by_lib, extra); pclass.profiles.push_back(set); } } // anon namespace profile_classes const arrange_profiles(list<string> const & files, merge_option const & merge_by, extra_images const & extra) { set<profile_class> temp_classes; list<string>::const_iterator it = files.begin(); list<string>::const_iterator const end = files.end(); for (; it != end; ++it) { parsed_filename parsed = parse_filename(*it, extra); if (parsed.lib_image.empty()) parsed.lib_image = parsed.image; // This simplifies the add of the profile later, // if we're lib-merging, then the app_image cannot // matter. After this, any non-dependent has // image == lib_image if (merge_by.lib) parsed.image = parsed.lib_image; profile_class & pclass = find_class(temp_classes, parsed, merge_by); add_profile(pclass, parsed, merge_by.lib, extra); } profile_classes classes; copy(temp_classes.begin(), temp_classes.end(), back_inserter(classes.v)); if (classes.v.empty()) return classes; // sort by template for nicely ordered columns stable_sort(classes.v.begin(), classes.v.end()); if (want_xml) identify_xml_classes(classes, merge_by); else identify_classes(classes, merge_by); classes.extra_found_images = extra; return classes; } ostream & operator<<(ostream & out, profile_sample_files const & sample_files) { out << "sample_filename: " << sample_files.sample_filename << endl; out << "callgraph filenames:\n"; copy(sample_files.cg_files.begin(), sample_files.cg_files.end(), ostream_iterator<string>(out, "\n")); return out; } ostream & operator<<(ostream & out, profile_dep_set const & pdep_set) { out << "lib_image: " << pdep_set.lib_image << endl; list<profile_sample_files>::const_iterator it; list<profile_sample_files>::const_iterator const end = pdep_set.files.end(); size_t i = 0; for (it = pdep_set.files.begin(); it != end; ++it) out << "profile_sample_files #" << i++ << ":\n" << *it; return out; } ostream & operator<<(ostream & out, profile_set const & pset) { out << "image: " << pset.image << endl; list<profile_sample_files>::const_iterator it; list<profile_sample_files>::const_iterator const end = pset.files.end(); size_t i = 0; for (it = pset.files.begin(); it != end; ++it) out << "profile_sample_files #" << i++ << ":\n" << *it; list<profile_dep_set>::const_iterator cit; list<profile_dep_set>::const_iterator const cend = pset.deps.end(); i = 0; for (cit = pset.deps.begin(); cit != cend; ++cit) out << "profile_dep_set #" << i++ << ":\n" << *cit; return out; } ostream & operator<<(ostream & out, profile_template const & ptemplate) { out << "event: " << ptemplate.event << endl << "count: " << ptemplate.count << endl << "unitmask: " << ptemplate.unitmask << endl << "tgid: " << ptemplate.tgid << endl << "tid: " << ptemplate.tid << endl << "cpu: " << ptemplate.cpu << endl; return out; } ostream & operator<<(ostream & out, profile_class const & pclass) { out << "name: " << pclass.name << endl << "longname: " << pclass.longname << endl << "ptemplate:\n" << pclass.ptemplate; size_t i = 0; list<profile_set>::const_iterator it; list<profile_set>::const_iterator const end = pclass.profiles.end(); for (it = pclass.profiles.begin(); it != end; ++it) out << "profiles_set #" << i++ << ":\n" << *it; return out; } ostream & operator<<(ostream & out, profile_classes const & pclasses) { out << "event: " << pclasses.event << endl << "cpuinfo: " << pclasses.cpuinfo << endl; for (size_t i = 0; i < pclasses.v.size(); ++i) out << "class #" << i << ":\n" << pclasses.v[i]; return out; } namespace { /// add the files to group of image sets void add_to_group(image_group_set & group, string const & app_image, list<profile_sample_files> const & files) { image_set set; set.app_image = app_image; set.files = files; group.push_back(set); } typedef map<string, inverted_profile> app_map_t; inverted_profile & get_iprofile(app_map_t & app_map, string const & image, size_t nr_classes) { app_map_t::iterator ait = app_map.find(image); if (ait != app_map.end()) return ait->second; inverted_profile ip; ip.image = image; ip.groups.resize(nr_classes); app_map[image] = ip; return app_map[image]; } /// Pull out all the images, removing any we can't access. void verify_and_fill(app_map_t & app_map, list<inverted_profile> & plist, extra_images const & extra) { app_map_t::iterator it = app_map.begin(); app_map_t::iterator const end = app_map.end(); for (; it != end; ++it) { plist.push_back(it->second); inverted_profile & ip = plist.back(); extra.find_image_path(ip.image, ip.error, false); } } } // anon namespace list<inverted_profile> const invert_profiles(profile_classes const & classes) { app_map_t app_map; size_t nr_classes = classes.v.size(); for (size_t i = 0; i < nr_classes; ++i) { list<profile_set>::const_iterator pit = classes.v[i].profiles.begin(); list<profile_set>::const_iterator pend = classes.v[i].profiles.end(); for (; pit != pend; ++pit) { // files can be empty if samples for a lib image // but none for the main image. Deal with it here // rather than later. if (pit->files.size()) { inverted_profile & ip = get_iprofile(app_map, pit->image, nr_classes); add_to_group(ip.groups[i], pit->image, pit->files); } list<profile_dep_set>::const_iterator dit = pit->deps.begin(); list<profile_dep_set>::const_iterator const dend = pit->deps.end(); for (; dit != dend; ++dit) { inverted_profile & ip = get_iprofile(app_map, dit->lib_image, nr_classes); add_to_group(ip.groups[i], pit->image, dit->files); } } } list<inverted_profile> inverted_list; verify_and_fill(app_map, inverted_list, classes.extra_found_images); return inverted_list; }