| /* |
| * 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 <stdio.h> |
| #include <unistd.h> |
| |
| #include <memory> |
| #include <optional> |
| #include <regex> |
| #include <string> |
| |
| #include <android-base/parseint.h> |
| #include <android-base/strings.h> |
| |
| #include "ETMDecoder.h" |
| #include "cmd_inject_impl.h" |
| #include "command.h" |
| #include "record_file.h" |
| #include "system/extras/simpleperf/etm_branch_list.pb.h" |
| #include "thread_tree.h" |
| #include "utils.h" |
| |
| namespace simpleperf { |
| |
| std::string BranchToProtoString(const std::vector<bool>& branch) { |
| size_t bytes = (branch.size() + 7) / 8; |
| std::string res(bytes, '\0'); |
| for (size_t i = 0; i < branch.size(); i++) { |
| if (branch[i]) { |
| res[i >> 3] |= 1 << (i & 7); |
| } |
| } |
| return res; |
| } |
| |
| std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) { |
| std::vector<bool> branch(bit_size, false); |
| for (size_t i = 0; i < bit_size; i++) { |
| if (s[i >> 3] & (1 << (i & 7))) { |
| branch[i] = true; |
| } |
| } |
| return branch; |
| } |
| |
| namespace { |
| |
| constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList"; |
| |
| using AddrPair = std::pair<uint64_t, uint64_t>; |
| |
| struct AddrPairHash { |
| size_t operator()(const AddrPair& ap) const noexcept { |
| size_t seed = 0; |
| HashCombine(seed, ap.first); |
| HashCombine(seed, ap.second); |
| return seed; |
| } |
| }; |
| |
| enum class OutputFormat { |
| AutoFDO, |
| BranchList, |
| }; |
| |
| // When processing binary info in an input file, the binaries are identified by their path. |
| // But this isn't sufficient when merging binary info from multiple input files. Because |
| // binaries for the same path may be changed between generating input files. So after processing |
| // each input file, we create BinaryKeys to identify binaries, which consider path, build_id and |
| // kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in BranchListBinaryInfo |
| // are interpreted for vmlinux. |
| struct BinaryKey { |
| std::string path; |
| BuildId build_id; |
| uint64_t kernel_start_addr = 0; |
| |
| BinaryKey() {} |
| |
| BinaryKey(const std::string& path, BuildId build_id) : path(path), build_id(build_id) {} |
| |
| BinaryKey(Dso* dso, uint64_t kernel_start_addr) : path(dso->Path()) { |
| build_id = Dso::FindExpectedBuildIdForPath(dso->Path()); |
| if (dso->type() == DSO_KERNEL) { |
| this->kernel_start_addr = kernel_start_addr; |
| } |
| } |
| |
| bool operator==(const BinaryKey& other) const { |
| return path == other.path && build_id == other.build_id && |
| kernel_start_addr == other.kernel_start_addr; |
| } |
| }; |
| |
| struct BinaryKeyHash { |
| size_t operator()(const BinaryKey& key) const noexcept { |
| size_t seed = 0; |
| HashCombine(seed, key.path); |
| HashCombine(seed, key.build_id); |
| if (key.kernel_start_addr != 0) { |
| HashCombine(seed, key.kernel_start_addr); |
| } |
| return seed; |
| } |
| }; |
| |
| static void OverflowSafeAdd(uint64_t& dest, uint64_t add) { |
| if (__builtin_add_overflow(dest, add, &dest)) { |
| LOG(WARNING) << "Branch count overflow happened."; |
| dest = UINT64_MAX; |
| } |
| } |
| |
| struct AutoFDOBinaryInfo { |
| uint64_t first_load_segment_addr = 0; |
| std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map; |
| std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map; |
| |
| void AddInstrRange(const ETMInstrRange& instr_range) { |
| uint64_t total_count = instr_range.branch_taken_count; |
| OverflowSafeAdd(total_count, instr_range.branch_not_taken_count); |
| OverflowSafeAdd(range_count_map[AddrPair(instr_range.start_addr, instr_range.end_addr)], |
| total_count); |
| if (instr_range.branch_taken_count > 0) { |
| OverflowSafeAdd(branch_count_map[AddrPair(instr_range.end_addr, instr_range.branch_to_addr)], |
| instr_range.branch_taken_count); |
| } |
| } |
| |
| void Merge(const AutoFDOBinaryInfo& other) { |
| for (const auto& p : other.range_count_map) { |
| auto res = range_count_map.emplace(p.first, p.second); |
| if (!res.second) { |
| OverflowSafeAdd(res.first->second, p.second); |
| } |
| } |
| for (const auto& p : other.branch_count_map) { |
| auto res = branch_count_map.emplace(p.first, p.second); |
| if (!res.second) { |
| OverflowSafeAdd(res.first->second, p.second); |
| } |
| } |
| } |
| }; |
| |
| using UnorderedBranchMap = |
| std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>; |
| |
| struct BranchListBinaryInfo { |
| DsoType dso_type; |
| UnorderedBranchMap branch_map; |
| |
| void Merge(const BranchListBinaryInfo& other) { |
| for (auto& other_p : other.branch_map) { |
| auto it = branch_map.find(other_p.first); |
| if (it == branch_map.end()) { |
| branch_map[other_p.first] = std::move(other_p.second); |
| } else { |
| auto& map2 = it->second; |
| for (auto& other_p2 : other_p.second) { |
| auto it2 = map2.find(other_p2.first); |
| if (it2 == map2.end()) { |
| map2[other_p2.first] = other_p2.second; |
| } else { |
| OverflowSafeAdd(it2->second, other_p2.second); |
| } |
| } |
| } |
| } |
| } |
| |
| BranchMap GetOrderedBranchMap() const { |
| BranchMap result; |
| for (const auto& p : branch_map) { |
| uint64_t addr = p.first; |
| const auto& b_map = p.second; |
| result[addr] = std::map<std::vector<bool>, uint64_t>(b_map.begin(), b_map.end()); |
| } |
| return result; |
| } |
| }; |
| |
| using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>; |
| using BranchListBinaryCallback = std::function<void(const BinaryKey&, BranchListBinaryInfo&)>; |
| |
| class ThreadTreeWithFilter : public ThreadTree { |
| public: |
| void ExcludePid(pid_t pid) { exclude_pid_ = pid; } |
| |
| ThreadEntry* FindThread(int tid) const override { |
| ThreadEntry* thread = ThreadTree::FindThread(tid); |
| if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) { |
| return nullptr; |
| } |
| return thread; |
| } |
| |
| private: |
| std::optional<pid_t> exclude_pid_; |
| }; |
| |
| class DsoFilter { |
| public: |
| DsoFilter(const std::regex& binary_name_regex) : binary_name_regex_(binary_name_regex) {} |
| |
| bool FilterDso(Dso* dso) { |
| auto lookup = dso_filter_cache_.find(dso); |
| if (lookup != dso_filter_cache_.end()) { |
| return lookup->second; |
| } |
| bool match = std::regex_search(dso->Path(), binary_name_regex_); |
| dso_filter_cache_.insert({dso, match}); |
| return match; |
| } |
| |
| private: |
| std::regex binary_name_regex_; |
| std::unordered_map<Dso*, bool> dso_filter_cache_; |
| }; |
| |
| static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) { |
| ElfStatus status; |
| if (auto elf = ElfFile::Open(dso->GetDebugFilePath(), &status); elf) { |
| for (const auto& segment : elf->GetProgramHeader()) { |
| if (segment.is_load) { |
| return segment.vaddr; |
| } |
| } |
| } |
| return 0; |
| } |
| |
| // Read perf.data, and generate AutoFDOBinaryInfo or BranchListBinaryInfo. |
| // To avoid resetting data, it only processes one input file per instance. |
| class PerfDataReader { |
| public: |
| PerfDataReader(const std::string& filename, bool exclude_perf, ETMDumpOption etm_dump_option, |
| const std::regex& binary_name_regex) |
| : filename_(filename), |
| exclude_perf_(exclude_perf), |
| etm_dump_option_(etm_dump_option), |
| dso_filter_(binary_name_regex) {} |
| |
| void SetCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; } |
| void SetCallback(const BranchListBinaryCallback& callback) { branch_list_callback_ = callback; } |
| |
| bool Read() { |
| record_file_reader_ = RecordFileReader::CreateInstance(filename_); |
| if (!record_file_reader_) { |
| return false; |
| } |
| if (exclude_perf_) { |
| const auto& info_map = record_file_reader_->GetMetaInfoFeature(); |
| if (auto it = info_map.find("recording_process"); it == info_map.end()) { |
| LOG(ERROR) << filename_ << " doesn't support --exclude-perf"; |
| return false; |
| } else { |
| int pid; |
| if (!android::base::ParseInt(it->second, &pid, 0)) { |
| LOG(ERROR) << "invalid recording_process " << it->second << " in " << filename_; |
| return false; |
| } |
| thread_tree_.ExcludePid(pid); |
| } |
| } |
| record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_); |
| if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) { |
| return false; |
| } |
| if (etm_decoder_ && !etm_decoder_->FinishData()) { |
| return false; |
| } |
| if (autofdo_callback_) { |
| ProcessAutoFDOBinaryInfo(); |
| } else if (branch_list_callback_) { |
| ProcessBranchListBinaryInfo(); |
| } |
| return true; |
| } |
| |
| private: |
| bool ProcessRecord(Record* r) { |
| thread_tree_.Update(*r); |
| if (r->type() == PERF_RECORD_AUXTRACE_INFO) { |
| etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_); |
| if (!etm_decoder_) { |
| return false; |
| } |
| etm_decoder_->EnableDump(etm_dump_option_); |
| if (autofdo_callback_) { |
| etm_decoder_->RegisterCallback( |
| [this](const ETMInstrRange& range) { ProcessInstrRange(range); }); |
| } else if (branch_list_callback_) { |
| etm_decoder_->RegisterCallback( |
| [this](const ETMBranchList& branch) { ProcessBranchList(branch); }); |
| } |
| } else if (r->type() == PERF_RECORD_AUX) { |
| AuxRecord* aux = static_cast<AuxRecord*>(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 in " << filename_; |
| return false; |
| } |
| return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux->Unformatted(), |
| aux->Cpu()); |
| } |
| } else if (r->type() == PERF_RECORD_MMAP && r->InKernel()) { |
| auto& mmap_r = *static_cast<MmapRecord*>(r); |
| if (android::base::StartsWith(mmap_r.filename, DEFAULT_KERNEL_MMAP_NAME)) { |
| kernel_map_start_addr_ = mmap_r.data->addr; |
| } |
| } |
| return true; |
| } |
| |
| void ProcessInstrRange(const ETMInstrRange& instr_range) { |
| if (!dso_filter_.FilterDso(instr_range.dso)) { |
| return; |
| } |
| |
| autofdo_binary_map_[instr_range.dso].AddInstrRange(instr_range); |
| } |
| |
| void ProcessBranchList(const ETMBranchList& branch_list) { |
| if (!dso_filter_.FilterDso(branch_list.dso)) { |
| return; |
| } |
| |
| auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map; |
| ++branch_map[branch_list.addr][branch_list.branch]; |
| } |
| |
| void ProcessAutoFDOBinaryInfo() { |
| for (auto& p : autofdo_binary_map_) { |
| Dso* dso = p.first; |
| AutoFDOBinaryInfo& binary = p.second; |
| binary.first_load_segment_addr = GetFirstLoadSegmentVaddr(dso); |
| autofdo_callback_(BinaryKey(dso, 0), binary); |
| } |
| } |
| |
| void ProcessBranchListBinaryInfo() { |
| for (auto& p : branch_list_binary_map_) { |
| Dso* dso = p.first; |
| BranchListBinaryInfo& binary = p.second; |
| binary.dso_type = dso->type(); |
| BinaryKey key(dso, 0); |
| if (binary.dso_type == DSO_KERNEL) { |
| if (kernel_map_start_addr_ == 0) { |
| LOG(WARNING) << "Can't convert kernel ip addresses without kernel start addr. So remove " |
| "branches for the kernel."; |
| continue; |
| } |
| if (dso->GetDebugFilePath() == dso->Path()) { |
| // vmlinux isn't available. We still use kernel ip addr. Put kernel start addr in proto |
| // for address conversion later. |
| key.kernel_start_addr = kernel_map_start_addr_; |
| } |
| } |
| branch_list_callback_(key, binary); |
| } |
| } |
| |
| const std::string filename_; |
| bool exclude_perf_; |
| ETMDumpOption etm_dump_option_; |
| DsoFilter dso_filter_; |
| AutoFDOBinaryCallback autofdo_callback_; |
| BranchListBinaryCallback branch_list_callback_; |
| |
| std::vector<uint8_t> aux_data_buffer_; |
| std::unique_ptr<ETMDecoder> etm_decoder_; |
| std::unique_ptr<RecordFileReader> record_file_reader_; |
| ThreadTreeWithFilter thread_tree_; |
| uint64_t kernel_map_start_addr_ = 0; |
| // Store results for AutoFDO. |
| std::unordered_map<Dso*, AutoFDOBinaryInfo> autofdo_binary_map_; |
| // Store results for BranchList. |
| std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_; |
| }; |
| |
| // Read a protobuf file specified by etm_branch_list.proto, and generate BranchListBinaryInfo. |
| class BranchListReader { |
| public: |
| BranchListReader(const std::string& filename, const std::regex binary_name_regex) |
| : filename_(filename), binary_name_regex_(binary_name_regex) {} |
| |
| void SetCallback(const BranchListBinaryCallback& callback) { callback_ = callback; } |
| |
| bool Read() { |
| auto fd = FileHelper::OpenReadOnly(filename_); |
| if (!fd.ok()) { |
| PLOG(ERROR) << "failed to open " << filename_; |
| return false; |
| } |
| |
| proto::ETMBranchList branch_list_proto; |
| if (!branch_list_proto.ParseFromFileDescriptor(fd)) { |
| PLOG(ERROR) << "failed to read msg from " << filename_; |
| return false; |
| } |
| if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) { |
| PLOG(ERROR) << "file not in format etm_branch_list.proto: " << filename_; |
| return false; |
| } |
| |
| for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) { |
| const auto& binary_proto = branch_list_proto.binaries(i); |
| if (!std::regex_search(binary_proto.path(), binary_name_regex_)) { |
| continue; |
| } |
| BinaryKey key(binary_proto.path(), BuildId(binary_proto.build_id())); |
| if (binary_proto.has_kernel_info()) { |
| key.kernel_start_addr = binary_proto.kernel_info().kernel_start_addr(); |
| } |
| BranchListBinaryInfo binary; |
| auto dso_type = ToDsoType(binary_proto.type()); |
| if (!dso_type) { |
| LOG(ERROR) << "invalid binary type in " << filename_; |
| return false; |
| } |
| binary.dso_type = dso_type.value(); |
| binary.branch_map = BuildUnorderedBranchMap(binary_proto); |
| callback_(key, binary); |
| } |
| return true; |
| } |
| |
| private: |
| std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) { |
| switch (binary_type) { |
| case proto::ETMBranchList_Binary::ELF_FILE: |
| return DSO_ELF_FILE; |
| case proto::ETMBranchList_Binary::KERNEL: |
| return DSO_KERNEL; |
| case proto::ETMBranchList_Binary::KERNEL_MODULE: |
| return DSO_KERNEL_MODULE; |
| default: |
| LOG(ERROR) << "unexpected binary type " << binary_type; |
| return std::nullopt; |
| } |
| } |
| |
| UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) { |
| UnorderedBranchMap branch_map; |
| for (size_t i = 0; i < binary_proto.addrs_size(); i++) { |
| const auto& addr_proto = binary_proto.addrs(i); |
| auto& b_map = branch_map[addr_proto.addr()]; |
| for (size_t j = 0; j < addr_proto.branches_size(); j++) { |
| const auto& branch_proto = addr_proto.branches(j); |
| std::vector<bool> branch = |
| ProtoStringToBranch(branch_proto.branch(), branch_proto.branch_size()); |
| b_map[branch] = branch_proto.count(); |
| } |
| } |
| return branch_map; |
| } |
| |
| const std::string filename_; |
| const std::regex binary_name_regex_; |
| BranchListBinaryCallback callback_; |
| }; |
| |
| // Convert BranchListBinaryInfo into AutoFDOBinaryInfo. |
| class BranchListToAutoFDOConverter { |
| public: |
| std::unique_ptr<AutoFDOBinaryInfo> Convert(const BinaryKey& key, BranchListBinaryInfo& binary) { |
| BuildId build_id = key.build_id; |
| std::unique_ptr<Dso> dso = Dso::CreateDsoWithBuildId(binary.dso_type, key.path, build_id); |
| if (!dso || !CheckBuildId(dso.get(), key.build_id)) { |
| return nullptr; |
| } |
| std::unique_ptr<AutoFDOBinaryInfo> autofdo_binary(new AutoFDOBinaryInfo); |
| autofdo_binary->first_load_segment_addr = GetFirstLoadSegmentVaddr(dso.get()); |
| |
| if (dso->type() == DSO_KERNEL) { |
| ModifyBranchMapForKernel(dso.get(), key.kernel_start_addr, binary); |
| } |
| |
| auto process_instr_range = [&](const ETMInstrRange& range) { |
| CHECK_EQ(range.dso, dso.get()); |
| autofdo_binary->AddInstrRange(range); |
| }; |
| |
| auto result = |
| ConvertBranchMapToInstrRanges(dso.get(), binary.GetOrderedBranchMap(), process_instr_range); |
| if (!result.ok()) { |
| LOG(WARNING) << "failed to build instr ranges for binary " << dso->Path() << ": " |
| << result.error(); |
| return nullptr; |
| } |
| return autofdo_binary; |
| } |
| |
| private: |
| bool CheckBuildId(Dso* dso, const BuildId& expected_build_id) { |
| if (expected_build_id.IsEmpty()) { |
| return true; |
| } |
| BuildId build_id; |
| return GetBuildIdFromDsoPath(dso->GetDebugFilePath(), &build_id) && |
| build_id == expected_build_id; |
| } |
| |
| void ModifyBranchMapForKernel(Dso* dso, uint64_t kernel_start_addr, |
| BranchListBinaryInfo& binary) { |
| if (kernel_start_addr == 0) { |
| // vmlinux has been provided when generating branch lists. Addresses in branch lists are |
| // already vaddrs in vmlinux. |
| return; |
| } |
| // Addresses are still kernel ip addrs in memory. Need to convert them to vaddrs in vmlinux. |
| UnorderedBranchMap new_branch_map; |
| for (auto& p : binary.branch_map) { |
| uint64_t vaddr_in_file = dso->IpToVaddrInFile(p.first, kernel_start_addr, 0); |
| new_branch_map[vaddr_in_file] = std::move(p.second); |
| } |
| binary.branch_map = std::move(new_branch_map); |
| } |
| }; |
| |
| // Write instruction ranges to a file in AutoFDO text format. |
| class AutoFDOWriter { |
| public: |
| void AddAutoFDOBinary(const BinaryKey& key, AutoFDOBinaryInfo& binary) { |
| auto it = binary_map_.find(key); |
| if (it == binary_map_.end()) { |
| binary_map_[key] = std::move(binary); |
| } else { |
| it->second.Merge(binary); |
| } |
| } |
| |
| bool Write(const std::string& output_filename) { |
| std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "w"), fclose); |
| if (!output_fp) { |
| PLOG(ERROR) << "failed to write to " << output_filename; |
| return false; |
| } |
| // autofdo_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<BinaryKey> keys; |
| for (auto& p : binary_map_) { |
| keys.emplace_back(p.first); |
| } |
| std::sort(keys.begin(), keys.end(), |
| [](const BinaryKey& key1, const BinaryKey& key2) { return key1.path < key2.path; }); |
| if (keys.size() > 1) { |
| fprintf(output_fp.get(), |
| "// Please split this file. AutoFDO only accepts profile for one binary.\n"); |
| } |
| for (const auto& key : keys) { |
| const AutoFDOBinaryInfo& binary = binary_map_[key]; |
| // AutoFDO text format needs file_offsets instead of virtual addrs in a binary. And it uses |
| // below formula: vaddr = file_offset + GetFirstLoadSegmentVaddr(). |
| uint64_t first_load_segment_addr = binary.first_load_segment_addr; |
| |
| auto to_offset = [&](uint64_t vaddr) -> uint64_t { |
| if (vaddr == 0) { |
| return 0; |
| } |
| CHECK_GE(vaddr, first_load_segment_addr); |
| return vaddr - first_load_segment_addr; |
| }; |
| |
| // Write range_count_map. |
| std::map<AddrPair, uint64_t> 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", |
| to_offset(addr_range.first), to_offset(addr_range.second), count); |
| } |
| |
| // Write addr_count_map. |
| fprintf(output_fp.get(), "0\n"); |
| |
| // Write branch_count_map. |
| std::map<AddrPair, uint64_t> 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", to_offset(branch.first), |
| to_offset(branch.second), count); |
| } |
| |
| // Write the binary path in comment. |
| fprintf(output_fp.get(), "// %s\n\n", key.path.c_str()); |
| } |
| return true; |
| } |
| |
| private: |
| std::unordered_map<BinaryKey, AutoFDOBinaryInfo, BinaryKeyHash> binary_map_; |
| }; |
| |
| // Merge BranchListBinaryInfo. |
| struct BranchListMerger { |
| void AddBranchListBinary(const BinaryKey& key, BranchListBinaryInfo& binary) { |
| auto it = binary_map.find(key); |
| if (it == binary_map.end()) { |
| binary_map[key] = std::move(binary); |
| } else { |
| it->second.Merge(binary); |
| } |
| } |
| |
| std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash> binary_map; |
| }; |
| |
| // Write branch lists to a protobuf file specified by etm_branch_list.proto. |
| class BranchListWriter { |
| public: |
| bool Write(const std::string& output_filename, |
| const std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>& binary_map) { |
| // Don't produce empty output file. |
| if (binary_map.empty()) { |
| LOG(INFO) << "Skip empty output file."; |
| unlink(output_filename.c_str()); |
| return true; |
| } |
| std::unique_ptr<FILE, decltype(&fclose)> output_fp(fopen(output_filename.c_str(), "wb"), |
| fclose); |
| if (!output_fp) { |
| PLOG(ERROR) << "failed to write to " << output_filename; |
| return false; |
| } |
| |
| proto::ETMBranchList branch_list_proto; |
| branch_list_proto.set_magic(ETM_BRANCH_LIST_PROTO_MAGIC); |
| std::vector<char> branch_buf; |
| for (const auto& p : binary_map) { |
| const BinaryKey& key = p.first; |
| const BranchListBinaryInfo& binary = p.second; |
| auto binary_proto = branch_list_proto.add_binaries(); |
| |
| binary_proto->set_path(key.path); |
| if (!key.build_id.IsEmpty()) { |
| binary_proto->set_build_id(key.build_id.ToString().substr(2)); |
| } |
| auto opt_binary_type = ToProtoBinaryType(binary.dso_type); |
| if (!opt_binary_type.has_value()) { |
| return false; |
| } |
| binary_proto->set_type(opt_binary_type.value()); |
| |
| for (const auto& addr_p : binary.branch_map) { |
| auto addr_proto = binary_proto->add_addrs(); |
| addr_proto->set_addr(addr_p.first); |
| |
| for (const auto& branch_p : addr_p.second) { |
| const std::vector<bool>& branch = branch_p.first; |
| auto branch_proto = addr_proto->add_branches(); |
| |
| branch_proto->set_branch(BranchToProtoString(branch)); |
| branch_proto->set_branch_size(branch.size()); |
| branch_proto->set_count(branch_p.second); |
| } |
| } |
| |
| if (binary.dso_type == DSO_KERNEL) { |
| binary_proto->mutable_kernel_info()->set_kernel_start_addr(key.kernel_start_addr); |
| } |
| } |
| if (!branch_list_proto.SerializeToFileDescriptor(fileno(output_fp.get()))) { |
| PLOG(ERROR) << "failed to write to " << output_filename; |
| return false; |
| } |
| return true; |
| } |
| |
| private: |
| std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) { |
| switch (dso_type) { |
| case DSO_ELF_FILE: |
| return proto::ETMBranchList_Binary::ELF_FILE; |
| case DSO_KERNEL: |
| return proto::ETMBranchList_Binary::KERNEL; |
| case DSO_KERNEL_MODULE: |
| return proto::ETMBranchList_Binary::KERNEL_MODULE; |
| default: |
| LOG(ERROR) << "unexpected dso type " << dso_type; |
| return std::nullopt; |
| } |
| } |
| }; |
| |
| class InjectCommand : public Command { |
| public: |
| InjectCommand() |
| : Command("inject", "parse etm instruction tracing data", |
| // clang-format off |
| "Usage: simpleperf inject [options]\n" |
| "--binary binary_name Generate data only for binaries matching binary_name regex.\n" |
| "-i file1,file2,... Input files. Default is perf.data. Support below formats:\n" |
| " 1. perf.data generated by recording cs-etm event type.\n" |
| " 2. branch_list file generated by `inject --output branch-list`.\n" |
| " If a file name starts with @, it contains a list of input files.\n" |
| "-o <file> output file. Default is perf_inject.data.\n" |
| "--output <format> Select output file format:\n" |
| " autofdo -- text format accepted by TextSampleReader\n" |
| " of AutoFDO\n" |
| " branch-list -- protobuf file in etm_branch_list.proto\n" |
| " Default is autofdo.\n" |
| "--dump-etm type1,type2,... Dump etm data. A type is one of raw, packet and element.\n" |
| "--exclude-perf Exclude trace data for the recording process.\n" |
| "--symdir <dir> Look for binaries in a directory recursively.\n" |
| "\n" |
| "Examples:\n" |
| "1. Generate autofdo text output.\n" |
| "$ simpleperf inject -i perf.data -o autofdo.txt --output autofdo\n" |
| "\n" |
| "2. Generate branch list proto, then convert to autofdo text.\n" |
| "$ simpleperf inject -i perf.data -o branch_list.data --output branch-list\n" |
| "$ simpleperf inject -i branch_list.data -o autofdo.txt --output autofdo\n" |
| // clang-format on |
| ) {} |
| |
| bool Run(const std::vector<std::string>& args) override { |
| GOOGLE_PROTOBUF_VERIFY_VERSION; |
| if (!ParseOptions(args)) { |
| return false; |
| } |
| |
| CHECK(!input_filenames_.empty()); |
| if (IsPerfDataFile(input_filenames_[0])) { |
| switch (output_format_) { |
| case OutputFormat::AutoFDO: |
| return ConvertPerfDataToAutoFDO(); |
| case OutputFormat::BranchList: |
| return ConvertPerfDataToBranchList(); |
| } |
| } else { |
| switch (output_format_) { |
| case OutputFormat::AutoFDO: |
| return ConvertBranchListToAutoFDO(); |
| case OutputFormat::BranchList: |
| return ConvertBranchListToBranchList(); |
| } |
| } |
| } |
| |
| private: |
| bool ParseOptions(const std::vector<std::string>& args) { |
| const OptionFormatMap option_formats = { |
| {"--binary", {OptionValueType::STRING, OptionType::SINGLE}}, |
| {"--dump-etm", {OptionValueType::STRING, OptionType::SINGLE}}, |
| {"--exclude-perf", {OptionValueType::NONE, OptionType::SINGLE}}, |
| {"-i", {OptionValueType::STRING, OptionType::MULTIPLE}}, |
| {"-o", {OptionValueType::STRING, OptionType::SINGLE}}, |
| {"--output", {OptionValueType::STRING, OptionType::SINGLE}}, |
| {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}}, |
| }; |
| OptionValueMap options; |
| std::vector<std::pair<OptionName, OptionValue>> ordered_options; |
| if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) { |
| return false; |
| } |
| |
| if (auto value = options.PullValue("--binary"); value) { |
| binary_name_regex_ = *value->str_value; |
| } |
| if (auto value = options.PullValue("--dump-etm"); value) { |
| if (!ParseEtmDumpOption(*value->str_value, &etm_dump_option_)) { |
| return false; |
| } |
| } |
| exclude_perf_ = options.PullBoolValue("--exclude-perf"); |
| |
| for (const OptionValue& value : options.PullValues("-i")) { |
| std::vector<std::string> files = android::base::Split(*value.str_value, ","); |
| for (std::string& file : files) { |
| if (android::base::StartsWith(file, "@")) { |
| if (!ReadFileList(file.substr(1), &input_filenames_)) { |
| return false; |
| } |
| } else { |
| input_filenames_.emplace_back(file); |
| } |
| } |
| } |
| if (input_filenames_.empty()) { |
| input_filenames_.emplace_back("perf.data"); |
| } |
| options.PullStringValue("-o", &output_filename_); |
| if (auto value = options.PullValue("--output"); value) { |
| const std::string& output = *value->str_value; |
| if (output == "autofdo") { |
| output_format_ = OutputFormat::AutoFDO; |
| } else if (output == "branch-list") { |
| output_format_ = OutputFormat::BranchList; |
| } else { |
| LOG(ERROR) << "unknown format in --output option: " << output; |
| return false; |
| } |
| } |
| if (auto value = options.PullValue("--symdir"); value) { |
| if (!Dso::AddSymbolDir(*value->str_value)) { |
| return false; |
| } |
| // Symbol dirs are cleaned when Dso count is decreased to zero, which can happen between |
| // processing input files. To make symbol dirs always available, create a placeholder dso to |
| // prevent cleaning from happening. |
| placeholder_dso_ = Dso::CreateDso(DSO_UNKNOWN_FILE, "unknown"); |
| } |
| CHECK(options.values.empty()); |
| return true; |
| } |
| |
| bool ReadFileList(const std::string& path, std::vector<std::string>* file_list) { |
| std::string data; |
| if (!android::base::ReadFileToString(path, &data)) { |
| PLOG(ERROR) << "failed to read " << path; |
| return false; |
| } |
| std::vector<std::string> tokens = android::base::Tokenize(data, " \t\n\r"); |
| file_list->insert(file_list->end(), tokens.begin(), tokens.end()); |
| return true; |
| } |
| |
| bool ConvertPerfDataToAutoFDO() { |
| AutoFDOWriter autofdo_writer; |
| auto callback = [&](const BinaryKey& key, AutoFDOBinaryInfo& binary) { |
| autofdo_writer.AddAutoFDOBinary(key, binary); |
| }; |
| for (const auto& input_filename : input_filenames_) { |
| PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, binary_name_regex_); |
| reader.SetCallback(callback); |
| if (!reader.Read()) { |
| return false; |
| } |
| } |
| return autofdo_writer.Write(output_filename_); |
| } |
| |
| bool ConvertPerfDataToBranchList() { |
| BranchListMerger branch_list_merger; |
| auto callback = [&](const BinaryKey& key, BranchListBinaryInfo& binary) { |
| branch_list_merger.AddBranchListBinary(key, binary); |
| }; |
| for (const auto& input_filename : input_filenames_) { |
| PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, binary_name_regex_); |
| reader.SetCallback(callback); |
| if (!reader.Read()) { |
| return false; |
| } |
| } |
| BranchListWriter branch_list_writer; |
| return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map); |
| } |
| |
| bool ConvertBranchListToAutoFDO() { |
| // Step1 : Merge branch lists from all input files. |
| BranchListMerger branch_list_merger; |
| auto callback = [&](const BinaryKey& key, BranchListBinaryInfo& binary) { |
| branch_list_merger.AddBranchListBinary(key, binary); |
| }; |
| for (const auto& input_filename : input_filenames_) { |
| BranchListReader reader(input_filename, binary_name_regex_); |
| reader.SetCallback(callback); |
| if (!reader.Read()) { |
| return false; |
| } |
| } |
| |
| // Step2: Convert BranchListBinaryInfo to AutoFDOBinaryInfo. |
| AutoFDOWriter autofdo_writer; |
| BranchListToAutoFDOConverter converter; |
| for (auto& p : branch_list_merger.binary_map) { |
| const BinaryKey& key = p.first; |
| BranchListBinaryInfo& binary = p.second; |
| std::unique_ptr<AutoFDOBinaryInfo> autofdo_binary = converter.Convert(key, binary); |
| if (autofdo_binary) { |
| // Create new BinaryKey with kernel_start_addr = 0. Because AutoFDO output doesn't care |
| // kernel_start_addr. |
| autofdo_writer.AddAutoFDOBinary(BinaryKey(key.path, key.build_id), *autofdo_binary); |
| } |
| } |
| |
| // Step3: Write AutoFDOBinaryInfo. |
| return autofdo_writer.Write(output_filename_); |
| } |
| |
| bool ConvertBranchListToBranchList() { |
| // Step1 : Merge branch lists from all input files. |
| BranchListMerger branch_list_merger; |
| auto callback = [&](const BinaryKey& key, BranchListBinaryInfo& binary) { |
| branch_list_merger.AddBranchListBinary(key, binary); |
| }; |
| for (const auto& input_filename : input_filenames_) { |
| BranchListReader reader(input_filename, binary_name_regex_); |
| reader.SetCallback(callback); |
| if (!reader.Read()) { |
| return false; |
| } |
| } |
| // Step2: Write BranchListBinaryInfo. |
| BranchListWriter branch_list_writer; |
| return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map); |
| } |
| |
| std::regex binary_name_regex_{""}; // Default to match everything. |
| bool exclude_perf_ = false; |
| std::vector<std::string> input_filenames_; |
| std::string output_filename_ = "perf_inject.data"; |
| OutputFormat output_format_ = OutputFormat::AutoFDO; |
| ETMDumpOption etm_dump_option_; |
| |
| std::unique_ptr<Dso> placeholder_dso_; |
| }; |
| |
| } // namespace |
| |
| void RegisterInjectCommand() { |
| return RegisterCommand("inject", [] { return std::unique_ptr<Command>(new InjectCommand); }); |
| } |
| |
| } // namespace simpleperf |