| |
| #include "perf_data_converter.h" |
| |
| #include <algorithm> |
| #include <limits> |
| #include <map> |
| #include <memory> |
| #include <set> |
| #include <unordered_map> |
| |
| #include <android-base/logging.h> |
| #include <android-base/macros.h> |
| #include <android-base/strings.h> |
| #include <perf_data_utils.h> |
| #include <perf_parser.h> |
| #include <perf_protobuf_io.h> |
| |
| #include "perfprofd_record.pb.h" |
| #include "perf_data.pb.h" |
| |
| #include "map_utils.h" |
| #include "quipper_helper.h" |
| #include "symbolizer.h" |
| |
| using std::map; |
| |
| namespace android { |
| namespace perfprofd { |
| |
| namespace { |
| |
| void AddSymbolInfo(PerfprofdRecord* record, |
| ::quipper::PerfParser& perf_parser, |
| ::perfprofd::Symbolizer* symbolizer, |
| bool symbolize_everything) { |
| std::unordered_set<std::string> filenames_w_build_id; |
| if (!symbolize_everything) { |
| for (auto& perf_build_id : record->build_ids()) { |
| filenames_w_build_id.insert(perf_build_id.filename()); |
| } |
| } |
| |
| std::unordered_set<std::string> files_wo_build_id; |
| { |
| quipper::MmapEventIterator it(*record); |
| for (; it != it.end(); ++it) { |
| const ::quipper::PerfDataProto_MMapEvent* mmap_event = &it->mmap_event(); |
| if (!mmap_event->has_filename() || !mmap_event->has_start() || !mmap_event->has_len()) { |
| // Don't care. |
| continue; |
| } |
| if (filenames_w_build_id.count(mmap_event->filename()) == 0) { |
| files_wo_build_id.insert(mmap_event->filename()); |
| } |
| } |
| } |
| if (files_wo_build_id.empty()) { |
| return; |
| } |
| |
| struct Dso { |
| uint64_t min_vaddr; |
| RangeMap<std::string, uint64_t> symbols; |
| explicit Dso(uint64_t min_vaddr_in) : min_vaddr(min_vaddr_in) { |
| } |
| }; |
| std::unordered_map<std::string, Dso> files; |
| |
| auto it = record->events().begin(); |
| auto end = record->events().end(); |
| auto parsed_it = perf_parser.parsed_events().begin(); |
| auto parsed_end = perf_parser.parsed_events().end(); |
| for (; it != end; ++it, ++parsed_it) { |
| CHECK(parsed_it != parsed_end); |
| if (!it->has_sample_event()) { |
| continue; |
| } |
| |
| const ::quipper::PerfDataProto_SampleEvent& sample_event = it->sample_event(); |
| |
| if (android::base::kEnableDChecks) { |
| // Check that the parsed_event and sample_event are consistent. |
| CHECK_EQ(parsed_it->callchain.size(), sample_event.callchain_size()); |
| } |
| |
| auto check_address = [&](const std::string& dso_name, uint64_t offset) { |
| if (files_wo_build_id.count(dso_name) == 0) { |
| return; |
| } |
| |
| // OK, that's a hit in the mmap segment (w/o build id). |
| |
| Dso* dso_data; |
| { |
| auto dso_it = files.find(dso_name); |
| constexpr uint64_t kNoMinAddr = std::numeric_limits<uint64_t>::max(); |
| if (dso_it == files.end()) { |
| uint64_t min_vaddr; |
| bool has_min_vaddr = symbolizer->GetMinExecutableVAddr(dso_name, &min_vaddr); |
| if (!has_min_vaddr) { |
| min_vaddr = kNoMinAddr; |
| } |
| auto it = files.emplace(dso_name, Dso(min_vaddr)); |
| dso_data = &it.first->second; |
| } else { |
| dso_data = &dso_it->second; |
| } |
| if (dso_data->min_vaddr == kNoMinAddr) { |
| return; |
| } |
| } |
| |
| // TODO: Is min_vaddr necessary here? |
| const uint64_t file_addr = offset; |
| |
| std::string symbol = symbolizer->Decode(dso_name, file_addr); |
| if (symbol.empty()) { |
| return; |
| } |
| |
| dso_data->symbols.Insert(symbol, file_addr); |
| }; |
| if (sample_event.has_ip() && parsed_it->dso_and_offset.dso_info_ != nullptr) { |
| check_address(parsed_it->dso_and_offset.dso_info_->name, parsed_it->dso_and_offset.offset_); |
| } |
| if (sample_event.callchain_size() > 0) { |
| for (auto& callchain_data: parsed_it->callchain) { |
| if (callchain_data.dso_info_ == nullptr) { |
| continue; |
| } |
| check_address(callchain_data.dso_info_->name, callchain_data.offset_); |
| } |
| } |
| } |
| |
| if (!files.empty()) { |
| // We have extra symbol info, create proto messages now. |
| size_t symbol_info_index = 0; |
| for (auto& file_data : files) { |
| const std::string& filename = file_data.first; |
| const Dso& dso = file_data.second; |
| if (dso.symbols.empty()) { |
| continue; |
| } |
| |
| auto* symbol_info = record->AddExtension(::quipper::symbol_info); |
| symbol_info->set_filename(filename); |
| symbol_info->set_filename_md5_prefix(::quipper::Md5Prefix(filename)); |
| symbol_info->set_min_vaddr(dso.min_vaddr); |
| for (auto& aggr_sym : dso.symbols) { |
| auto* symbol = symbol_info->add_symbols(); |
| symbol->set_addr(*aggr_sym.second.offsets.begin()); |
| symbol->set_size(*aggr_sym.second.offsets.rbegin() - *aggr_sym.second.offsets.begin() + 1); |
| symbol->set_name(aggr_sym.second.symbol); |
| symbol->set_name_md5_prefix(::quipper::Md5Prefix(aggr_sym.second.symbol)); |
| } |
| |
| ++symbol_info_index; |
| } |
| } |
| } |
| |
| } // namespace |
| |
| PerfprofdRecord* |
| RawPerfDataToAndroidPerfProfile(const string &perf_file, |
| ::perfprofd::Symbolizer* symbolizer, |
| bool symbolize_everything) { |
| std::unique_ptr<PerfprofdRecord> ret(new PerfprofdRecord()); |
| ret->SetExtension(::quipper::id, 0); // TODO. |
| |
| ::quipper::PerfParserOptions options = {}; |
| options.do_remap = true; |
| options.discard_unused_events = true; |
| options.read_missing_buildids = true; |
| |
| ::quipper::PerfReader reader; |
| if (!reader.ReadFile(perf_file)) return nullptr; |
| |
| ::quipper::PerfParser parser(&reader, options); |
| if (!parser.ParseRawEvents()) return nullptr; |
| |
| if (!reader.Serialize(ret.get())) return nullptr; |
| |
| // Append parser stats to protobuf. |
| ::quipper::PerfSerializer::SerializeParserStats(parser.stats(), ret.get()); |
| |
| // TODO: Symbolization. |
| if (symbolizer != nullptr) { |
| AddSymbolInfo(ret.get(), parser, symbolizer, symbolize_everything); |
| } |
| |
| return ret.release(); |
| } |
| |
| } // namespace perfprofd |
| } // namespace android |