/* * Copyright (C) 2019 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include "ETMDecoder.h" #include "command.h" #include "record_file.h" #include "thread_tree.h" #include "utils.h" using namespace simpleperf; namespace { using AddrPair = std::pair; struct AddrPairHash { size_t operator()(const AddrPair& ap) const noexcept { size_t seed = 0; HashCombine(seed, ap.first); HashCombine(seed, ap.second); return seed; } }; struct BinaryInfo { std::unordered_map range_count_map; std::unordered_map branch_count_map; }; class InjectCommand : public Command { public: InjectCommand() : Command("inject", "convert etm instruction tracing data into instr ranges", // clang-format off "Usage: simpleperf inject [options]\n" "--binary binary_name Generate data only for binaries matching binary_name regex.\n" "-i input perf.data, generated by recording cs-etm event type.\n" " Default is perf.data.\n" "-o output file. Default is perf_inject.data.\n" " The output is in text format accepted by AutoFDO.\n" "--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n" "--symdir Look for binaries in a directory recursively.\n" // clang-format on ), output_fp_(nullptr, fclose) {} bool Run(const std::vector& args) override { if (!ParseOptions(args)) { return false; } record_file_reader_ = RecordFileReader::CreateInstance(input_filename_); if (!record_file_reader_) { return false; } record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_); output_fp_.reset(fopen(output_filename_.c_str(), "w")); if (!output_fp_) { PLOG(ERROR) << "failed to write to " << output_filename_; return false; } if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) { return false; } if (etm_decoder_ && !etm_decoder_->FinishData()) { return false; } PostProcess(); output_fp_.reset(nullptr); return true; } private: bool ParseOptions(const std::vector& args) { for (size_t i = 0; i < args.size(); i++) { if (args[i] == "--binary") { if (!NextArgumentOrError(args, &i)) { return false; } binary_name_regex_ = args[i]; } else if (args[i] == "-i") { if (!NextArgumentOrError(args, &i)) { return false; } input_filename_ = args[i]; } else if (args[i] == "-o") { if (!NextArgumentOrError(args, &i)) { return false; } output_filename_ = args[i]; } else if (args[i] == "--dump-etm") { if (!NextArgumentOrError(args, &i) || !ParseEtmDumpOption(args[i], &etm_dump_option_)) { return false; } } else if (args[i] == "--symdir") { if (!NextArgumentOrError(args, &i) || !Dso::AddSymbolDir(args[i])) { return false; } } else { ReportUnknownOption(args, i); return false; } } return true; } bool ProcessRecord(Record* r) { thread_tree_.Update(*r); if (r->type() == PERF_RECORD_AUXTRACE_INFO) { auto instr_range_callback = [this](auto& range) { ProcessInstrRange(range); }; etm_decoder_ = ETMDecoder::Create(*static_cast(r), thread_tree_); if (!etm_decoder_) { return false; } etm_decoder_->EnableDump(etm_dump_option_); etm_decoder_->RegisterCallback(instr_range_callback); } else if (r->type() == PERF_RECORD_AUX) { AuxRecord* aux = static_cast(r); uint64_t aux_size = aux->data->aux_size; if (aux_size > 0) { if (aux_data_buffer_.size() < aux_size) { aux_data_buffer_.resize(aux_size); } if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset, aux_data_buffer_.data(), aux_size)) { LOG(ERROR) << "failed to read aux data"; return false; } return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size); } } return true; } std::unordered_map dso_filter_cache; bool FilterInstrRange(const ETMInstrRange& instr_range) { auto lookup = dso_filter_cache.find(instr_range.dso); if (lookup != dso_filter_cache.end()) { return lookup->second; } bool match = std::regex_search(instr_range.dso->GetDebugFilePath(), binary_name_regex_); dso_filter_cache.insert({instr_range.dso, match}); return match; } void ProcessInstrRange(const ETMInstrRange& instr_range) { if (!FilterInstrRange(instr_range)) { return; } auto& binary = binary_map_[instr_range.dso]; binary.range_count_map[AddrPair(instr_range.start_addr, instr_range.end_addr)] += instr_range.branch_taken_count + instr_range.branch_not_taken_count; if (instr_range.branch_taken_count > 0) { binary.branch_count_map[AddrPair(instr_range.end_addr, instr_range.branch_to_addr)] += instr_range.branch_taken_count; } } void PostProcess() { // binary_map is used to store instruction ranges, which can have a large amount. And it has // a larger access time (instruction ranges * executed time). So it's better to use // unorder_maps to speed up access time. But we also want a stable output here, to compare // output changes result from code changes. So generate a sorted output here. std::vector dso_v; for (auto& p : binary_map_) { dso_v.emplace_back(p.first); } std::sort(dso_v.begin(), dso_v.end(), [](Dso* d1, Dso* d2) { return d1->Path() < d2->Path(); }); for (auto dso : dso_v) { const BinaryInfo& binary = binary_map_[dso]; // Write range_count_map. std::map range_count_map(binary.range_count_map.begin(), binary.range_count_map.end()); fprintf(output_fp_.get(), "%zu\n", range_count_map.size()); for (const auto& pair2 : range_count_map) { const AddrPair& addr_range = pair2.first; uint64_t count = pair2.second; fprintf(output_fp_.get(), "%" PRIx64 "-%" PRIx64 ":%" PRIu64 "\n", addr_range.first, addr_range.second, count); } // Write addr_count_map. fprintf(output_fp_.get(), "0\n"); // Write branch_count_map. std::map branch_count_map(binary.branch_count_map.begin(), binary.branch_count_map.end()); fprintf(output_fp_.get(), "%zu\n", branch_count_map.size()); for (const auto& pair2 : branch_count_map) { const AddrPair& branch = pair2.first; uint64_t count = pair2.second; fprintf(output_fp_.get(), "%" PRIx64 "->%" PRIx64 ":%" PRIu64 "\n", branch.first, branch.second, count); } // Write the binary path in comment. fprintf(output_fp_.get(), "// %s\n\n", dso->Path().c_str()); } } std::regex binary_name_regex_{""}; // Default to match everything. std::string input_filename_ = "perf.data"; std::string output_filename_ = "perf_inject.data"; ThreadTree thread_tree_; std::unique_ptr record_file_reader_; ETMDumpOption etm_dump_option_; std::unique_ptr etm_decoder_; std::vector aux_data_buffer_; std::unique_ptr output_fp_; // Store results for AutoFDO. std::unordered_map binary_map_; }; } // namespace void RegisterInjectCommand() { return RegisterCommand("inject", [] { return std::unique_ptr(new InjectCommand); }); }