| |
| #include "perf_data_converter.h" |
| #include "quipper/perf_parser.h" |
| #include <map> |
| |
| using std::map; |
| |
| namespace wireless_android_logging_awp { |
| |
| typedef quipper::ParsedEvent::DSOAndOffset DSOAndOffset; |
| typedef std::vector<DSOAndOffset> callchain; |
| |
| struct callchain_lt { |
| bool operator()(const callchain *c1, const callchain *c2) const { |
| if (c1->size() != c2->size()) { |
| return c1->size() < c2->size(); |
| } |
| for (unsigned idx = 0; idx < c1->size(); ++idx) { |
| const DSOAndOffset *do1 = &(*c1)[idx]; |
| const DSOAndOffset *do2 = &(*c2)[idx]; |
| if (do1->offset() != do2->offset()) { |
| return do1->offset() < do2->offset(); |
| } |
| int rc = do1->dso_name().compare(do2->dso_name()); |
| if (rc) { |
| return rc < 0; |
| } |
| } |
| return false; |
| } |
| }; |
| |
| struct RangeTarget { |
| RangeTarget(uint64 start, uint64 end, uint64 to) |
| : start(start), end(end), to(to) {} |
| |
| bool operator<(const RangeTarget &r) const { |
| if (start != r.start) { |
| return start < r.start; |
| } else if (end != r.end) { |
| return end < r.end; |
| } else { |
| return to < r.to; |
| } |
| } |
| uint64 start; |
| uint64 end; |
| uint64 to; |
| }; |
| |
| struct BinaryProfile { |
| map<uint64, uint64> address_count_map; |
| map<RangeTarget, uint64> range_count_map; |
| map<const callchain *, uint64, callchain_lt> callchain_count_map; |
| }; |
| |
| wireless_android_play_playlog::AndroidPerfProfile |
| RawPerfDataToAndroidPerfProfile(const string &perf_file) { |
| wireless_android_play_playlog::AndroidPerfProfile ret; |
| quipper::PerfParser parser; |
| if (!parser.ReadFile(perf_file) || !parser.ParseRawEvents()) { |
| return ret; |
| } |
| |
| typedef map<string, BinaryProfile> ModuleProfileMap; |
| typedef map<string, ModuleProfileMap> ProgramProfileMap; |
| |
| // Note: the callchain_count_map member in BinaryProfile contains |
| // pointers into callchains owned by "parser" above, meaning |
| // that once the parser is destroyed, callchain pointers in |
| // name_profile_map will become stale (e.g. keep these two |
| // together in the same region). |
| ProgramProfileMap name_profile_map; |
| uint64 total_samples = 0; |
| bool seen_branch_stack = false; |
| bool seen_callchain = false; |
| for (const auto &event : parser.parsed_events()) { |
| if (!event.raw_event || |
| event.raw_event->header.type != PERF_RECORD_SAMPLE) { |
| continue; |
| } |
| string dso_name = event.dso_and_offset.dso_name(); |
| string program_name; |
| const string kernel_name = "[kernel.kallsyms]"; |
| if (dso_name.substr(0, kernel_name.length()) == kernel_name) { |
| dso_name = kernel_name; |
| program_name = "[kernel.kallsyms]"; |
| } else if (event.command() == "") { |
| program_name = "unknown_program"; |
| } else { |
| program_name = event.command(); |
| } |
| total_samples++; |
| // We expect to see either all callchain events, all branch stack |
| // events, or all flat sample events, not a mix. For callchains, |
| // however, it can be the case that none of the IPs in a chain |
| // are mappable, in which case the parsed/mapped chain will appear |
| // empty (appearing as a flat sample). |
| if (!event.callchain.empty()) { |
| CHECK(!seen_branch_stack && "examining callchain"); |
| seen_callchain = true; |
| const callchain *cc = &event.callchain; |
| name_profile_map[program_name][dso_name].callchain_count_map[cc]++; |
| } else if (!event.branch_stack.empty()) { |
| CHECK(!seen_callchain && "examining branch stack"); |
| seen_branch_stack = true; |
| name_profile_map[program_name][dso_name].address_count_map[ |
| event.dso_and_offset.offset()]++; |
| } else { |
| name_profile_map[program_name][dso_name].address_count_map[ |
| event.dso_and_offset.offset()]++; |
| } |
| for (size_t i = 1; i < event.branch_stack.size(); i++) { |
| if (dso_name == event.branch_stack[i - 1].to.dso_name()) { |
| uint64 start = event.branch_stack[i].to.offset(); |
| uint64 end = event.branch_stack[i - 1].from.offset(); |
| uint64 to = event.branch_stack[i - 1].to.offset(); |
| // The interval between two taken branches should not be too large. |
| if (end < start || end - start > (1 << 20)) { |
| LOG(WARNING) << "Bogus LBR data: " << start << "->" << end; |
| continue; |
| } |
| name_profile_map[program_name][dso_name].range_count_map[ |
| RangeTarget(start, end, to)]++; |
| } |
| } |
| } |
| |
| map<string, int> name_id_map; |
| for (const auto &program_profile : name_profile_map) { |
| for (const auto &module_profile : program_profile.second) { |
| name_id_map[module_profile.first] = 0; |
| } |
| } |
| int current_index = 0; |
| for (auto iter = name_id_map.begin(); iter != name_id_map.end(); ++iter) { |
| iter->second = current_index++; |
| } |
| |
| map<string, string> name_buildid_map; |
| parser.GetFilenamesToBuildIDs(&name_buildid_map); |
| ret.set_total_samples(total_samples); |
| for (const auto &name_id : name_id_map) { |
| auto load_module = ret.add_load_modules(); |
| load_module->set_name(name_id.first); |
| auto nbmi = name_buildid_map.find(name_id.first); |
| if (nbmi != name_buildid_map.end()) { |
| const std::string &build_id = nbmi->second; |
| if (build_id.size() == 40 && build_id.substr(32) == "00000000") { |
| load_module->set_build_id(build_id.substr(0, 32)); |
| } else { |
| load_module->set_build_id(build_id); |
| } |
| } |
| } |
| for (const auto &program_profile : name_profile_map) { |
| auto program = ret.add_programs(); |
| program->set_name(program_profile.first); |
| for (const auto &module_profile : program_profile.second) { |
| int32 module_id = name_id_map[module_profile.first]; |
| auto module = program->add_modules(); |
| module->set_load_module_id(module_id); |
| for (const auto &addr_count : module_profile.second.address_count_map) { |
| auto address_samples = module->add_address_samples(); |
| address_samples->add_address(addr_count.first); |
| address_samples->set_count(addr_count.second); |
| } |
| for (const auto &range_count : module_profile.second.range_count_map) { |
| auto range_samples = module->add_range_samples(); |
| range_samples->set_start(range_count.first.start); |
| range_samples->set_end(range_count.first.end); |
| range_samples->set_to(range_count.first.to); |
| range_samples->set_count(range_count.second); |
| } |
| for (const auto &callchain_count : |
| module_profile.second.callchain_count_map) { |
| auto address_samples = module->add_address_samples(); |
| address_samples->set_count(callchain_count.second); |
| for (const auto &d_o : *callchain_count.first) { |
| int32 module_id = name_id_map[d_o.dso_name()]; |
| address_samples->add_load_module_id(module_id); |
| address_samples->add_address(d_o.offset()); |
| } |
| } |
| } |
| } |
| return ret; |
| } |
| |
| } // namespace wireless_android_logging_awp |