diff options
author | Yabin Cui <yabinc@google.com> | 2023-12-07 00:29:27 +0000 |
---|---|---|
committer | Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com> | 2023-12-07 00:29:27 +0000 |
commit | 5eace988fc59ecf7f7d8b64ccb316e3c1b06e1ac (patch) | |
tree | 6bc7df20335dca11488afadbc074cd16352271ea | |
parent | a67f253c6a5d4ad795cfbd01922e003b17512f26 (diff) | |
parent | 9707ab69181797e4496cfdfedbee941b4c99622d (diff) | |
download | extras-5eace988fc59ecf7f7d8b64ccb316e3c1b06e1ac.tar.gz |
Merge changes I58ae6a07,Icae497f9,I914c46b7 into main am: 9707ab6918
Original change: https://android-review.googlesource.com/c/platform/system/extras/+/2858330
Change-Id: I01af1f2cfc56489a8363ab32de86cb850b6e7ab1
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
-rw-r--r-- | simpleperf/Android.bp | 5 | ||||
-rw-r--r-- | simpleperf/BranchListFile.cpp (renamed from simpleperf/ETMBranchListFile.cpp) | 64 | ||||
-rw-r--r-- | simpleperf/BranchListFile.h (renamed from simpleperf/ETMBranchListFile.h) | 106 | ||||
-rw-r--r-- | simpleperf/BranchListFile_test.cpp (renamed from simpleperf/ETMBranchListFile_test.cpp) | 8 | ||||
-rw-r--r-- | simpleperf/ETMDecoder.cpp | 4 | ||||
-rw-r--r-- | simpleperf/ETMDecoder.h | 6 | ||||
-rw-r--r-- | simpleperf/branch_list.proto | 67 | ||||
-rw-r--r-- | simpleperf/cmd_dumprecord.cpp | 31 | ||||
-rw-r--r-- | simpleperf/cmd_inject.cpp | 574 | ||||
-rw-r--r-- | simpleperf/cmd_inject_test.cpp | 11 | ||||
-rw-r--r-- | simpleperf/cmd_record.cpp | 6 | ||||
-rw-r--r-- | simpleperf/etm_branch_list.proto | 66 | ||||
-rw-r--r-- | simpleperf/record_file.h | 1 | ||||
-rw-r--r-- | simpleperf/testdata/lbr/inject_lbr.data | 11 | ||||
-rw-r--r-- | simpleperf/testdata/lbr/perf_lbr.data | bin | 0 -> 23855 bytes |
15 files changed, 603 insertions, 357 deletions
diff --git a/simpleperf/Android.bp b/simpleperf/Android.bp index f1cbb1ea..bf33df45 100644 --- a/simpleperf/Android.bp +++ b/simpleperf/Android.bp @@ -220,8 +220,8 @@ cc_defaults { "cmd_report_sample.proto", "command.cpp", "dso.cpp", - "etm_branch_list.proto", - "ETMBranchListFile.cpp", + "branch_list.proto", + "BranchListFile.cpp", "event_attr.cpp", "event_type.cpp", "kallsyms.cpp", @@ -561,6 +561,7 @@ cc_defaults { "cmd_report_test.cpp", ], srcs: [ + "BranchListFile_test.cpp", "cmd_inject_test.cpp", "cmd_kmem_test.cpp", "cmd_merge_test.cpp", diff --git a/simpleperf/ETMBranchListFile.cpp b/simpleperf/BranchListFile.cpp index c92cf8cc..e8f6b22c 100644 --- a/simpleperf/ETMBranchListFile.cpp +++ b/simpleperf/BranchListFile.cpp @@ -14,16 +14,16 @@ * limitations under the License. */ -#include "ETMBranchListFile.h" +#include "BranchListFile.h" #include "ETMDecoder.h" -#include "system/extras/simpleperf/etm_branch_list.pb.h" +#include "system/extras/simpleperf/branch_list.pb.h" namespace simpleperf { static constexpr const char* ETM_BRANCH_LIST_PROTO_MAGIC = "simpleperf:EtmBranchList"; -std::string BranchToProtoString(const std::vector<bool>& branch) { +std::string ETMBranchToProtoString(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++) { @@ -34,7 +34,7 @@ std::string BranchToProtoString(const std::vector<bool>& branch) { return res; } -std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) { +std::vector<bool> ProtoStringToETMBranch(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))) { @@ -44,28 +44,28 @@ std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size) { return branch; } -static std::optional<proto::ETMBranchList_Binary::BinaryType> ToProtoBinaryType(DsoType dso_type) { +static std::optional<proto::ETMBinary::BinaryType> ToProtoBinaryType(DsoType dso_type) { switch (dso_type) { case DSO_ELF_FILE: - return proto::ETMBranchList_Binary::ELF_FILE; + return proto::ETMBinary::ELF_FILE; case DSO_KERNEL: - return proto::ETMBranchList_Binary::KERNEL; + return proto::ETMBinary::KERNEL; case DSO_KERNEL_MODULE: - return proto::ETMBranchList_Binary::KERNEL_MODULE; + return proto::ETMBinary::KERNEL_MODULE; default: LOG(ERROR) << "unexpected dso type " << dso_type; return std::nullopt; } } -bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s) { - proto::ETMBranchList branch_list_proto; +bool ETMBinaryMapToString(const ETMBinaryMap& binary_map, std::string& s) { + proto::BranchList 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(); + const ETMBinary& binary = p.second; + auto binary_proto = branch_list_proto.add_etm_data(); binary_proto->set_path(key.path); if (!key.build_id.IsEmpty()) { @@ -85,7 +85,7 @@ bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::str 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(ETMBranchToProtoString(branch)); branch_proto->set_branch_size(branch.size()); branch_proto->set_count(branch_p.second); } @@ -102,13 +102,13 @@ bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::str return true; } -static std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType binary_type) { +static std::optional<DsoType> ToDsoType(proto::ETMBinary::BinaryType binary_type) { switch (binary_type) { - case proto::ETMBranchList_Binary::ELF_FILE: + case proto::ETMBinary::ELF_FILE: return DSO_ELF_FILE; - case proto::ETMBranchList_Binary::KERNEL: + case proto::ETMBinary::KERNEL: return DSO_KERNEL; - case proto::ETMBranchList_Binary::KERNEL_MODULE: + case proto::ETMBinary::KERNEL_MODULE: return DSO_KERNEL_MODULE; default: LOG(ERROR) << "unexpected binary type " << binary_type; @@ -116,46 +116,46 @@ static std::optional<DsoType> ToDsoType(proto::ETMBranchList_Binary::BinaryType } } -static UnorderedBranchMap BuildUnorderedBranchMap(const proto::ETMBranchList_Binary& binary_proto) { - UnorderedBranchMap branch_map; +static UnorderedETMBranchMap BuildUnorderedETMBranchMap(const proto::ETMBinary& binary_proto) { + UnorderedETMBranchMap 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()); + ProtoStringToETMBranch(branch_proto.branch(), branch_proto.branch_size()); b_map[branch] = branch_proto.count(); } } return branch_map; } -bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map) { - proto::ETMBranchList branch_list_proto; +bool StringToETMBinaryMap(const std::string& s, ETMBinaryMap& binary_map) { + proto::BranchList branch_list_proto; if (!branch_list_proto.ParseFromString(s)) { PLOG(ERROR) << "failed to read ETMBranchList msg"; return false; } if (branch_list_proto.magic() != ETM_BRANCH_LIST_PROTO_MAGIC) { - PLOG(ERROR) << "not in format etm_branch_list.proto"; + PLOG(ERROR) << "not in etm branch list format in branch_list.proto"; return false; } - for (size_t i = 0; i < branch_list_proto.binaries_size(); i++) { - const auto& binary_proto = branch_list_proto.binaries(i); + for (size_t i = 0; i < branch_list_proto.etm_data_size(); i++) { + const auto& binary_proto = branch_list_proto.etm_data(i); 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 = binary_map[key]; + ETMBinary& binary = binary_map[key]; auto dso_type = ToDsoType(binary_proto.type()); if (!dso_type) { LOG(ERROR) << "invalid binary type " << binary_proto.type(); return false; } binary.dso_type = dso_type.value(); - binary.branch_map = BuildUnorderedBranchMap(binary_proto); + binary.branch_map = BuildUnorderedETMBranchMap(binary_proto); } return true; } @@ -231,7 +231,7 @@ class ETMBranchListGeneratorImpl : public ETMBranchListGenerator { } bool ProcessRecord(const Record& r, bool& consumed) override; - BranchListBinaryMap GetBranchListBinaryMap() override; + ETMBinaryMap GetETMBinaryMap() override; private: struct AuxRecordData { @@ -257,7 +257,7 @@ class ETMBranchListGeneratorImpl : public ETMBranchListGenerator { BinaryFilter binary_filter_; std::map<uint32_t, PerCpuData> cpu_map_; std::unique_ptr<ETMDecoder> etm_decoder_; - std::unordered_map<Dso*, BranchListBinaryInfo> branch_list_binary_map_; + std::unordered_map<Dso*, ETMBinary> branch_list_binary_map_; }; bool ETMBranchListGeneratorImpl::ProcessRecord(const Record& r, bool& consumed) { @@ -353,11 +353,11 @@ void ETMBranchListGeneratorImpl::ProcessBranchList(const ETMBranchList& branch_l ++branch_map[branch_list.addr][branch_list.branch]; } -BranchListBinaryMap ETMBranchListGeneratorImpl::GetBranchListBinaryMap() { - BranchListBinaryMap binary_map; +ETMBinaryMap ETMBranchListGeneratorImpl::GetETMBinaryMap() { + ETMBinaryMap binary_map; for (auto& p : branch_list_binary_map_) { Dso* dso = p.first; - BranchListBinaryInfo& binary = p.second; + ETMBinary& binary = p.second; binary.dso_type = dso->type(); BuildId build_id; GetBuildId(*dso, build_id); diff --git a/simpleperf/ETMBranchListFile.h b/simpleperf/BranchListFile.h index fd595c56..031a026f 100644 --- a/simpleperf/ETMBranchListFile.h +++ b/simpleperf/BranchListFile.h @@ -27,7 +27,7 @@ namespace simpleperf { // 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 +// kernel_start_addr (for vmlinux). kernel_start_addr affects how addresses in ETMBinary // are interpreted for vmlinux. struct BinaryKey { std::string path; @@ -38,7 +38,7 @@ struct 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()) { + BinaryKey(const 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; @@ -63,14 +63,42 @@ struct BinaryKeyHash { } }; -using UnorderedBranchMap = +class BinaryFilter { + public: + BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {} + + void SetRegex(const RegEx* binary_name_regex) { + binary_name_regex_ = binary_name_regex; + dso_filter_cache_.clear(); + } + + bool Filter(const Dso* dso) { + auto lookup = dso_filter_cache_.find(dso); + if (lookup != dso_filter_cache_.end()) { + return lookup->second; + } + bool match = Filter(dso->Path()); + dso_filter_cache_.insert({dso, match}); + return match; + } + + bool Filter(const std::string& path) { + return binary_name_regex_ == nullptr || binary_name_regex_->Search(path); + } + + private: + const RegEx* binary_name_regex_; + std::unordered_map<const Dso*, bool> dso_filter_cache_; +}; + +using UnorderedETMBranchMap = std::unordered_map<uint64_t, std::unordered_map<std::vector<bool>, uint64_t>>; -struct BranchListBinaryInfo { +struct ETMBinary { DsoType dso_type; - UnorderedBranchMap branch_map; + UnorderedETMBranchMap branch_map; - void Merge(const BranchListBinaryInfo& other) { + void Merge(const ETMBinary& other) { for (auto& other_p : other.branch_map) { auto it = branch_map.find(other_p.first); if (it == branch_map.end()) { @@ -89,8 +117,8 @@ struct BranchListBinaryInfo { } } - BranchMap GetOrderedBranchMap() const { - BranchMap result; + ETMBranchMap GetOrderedBranchMap() const { + ETMBranchMap result; for (const auto& p : branch_map) { uint64_t addr = p.first; const auto& b_map = p.second; @@ -100,38 +128,10 @@ struct BranchListBinaryInfo { } }; -using BranchListBinaryMap = std::unordered_map<BinaryKey, BranchListBinaryInfo, BinaryKeyHash>; - -bool BranchListBinaryMapToString(const BranchListBinaryMap& binary_map, std::string& s); -bool StringToBranchListBinaryMap(const std::string& s, BranchListBinaryMap& binary_map); - -class BinaryFilter { - public: - BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {} - - void SetRegex(const RegEx* binary_name_regex) { - binary_name_regex_ = binary_name_regex; - dso_filter_cache_.clear(); - } +using ETMBinaryMap = std::unordered_map<BinaryKey, ETMBinary, BinaryKeyHash>; - bool Filter(Dso* dso) { - auto lookup = dso_filter_cache_.find(dso); - if (lookup != dso_filter_cache_.end()) { - return lookup->second; - } - bool match = Filter(dso->Path()); - dso_filter_cache_.insert({dso, match}); - return match; - } - - bool Filter(const std::string& path) { - return binary_name_regex_ == nullptr || binary_name_regex_->Search(path); - } - - private: - const RegEx* binary_name_regex_; - std::unordered_map<Dso*, bool> dso_filter_cache_; -}; +bool ETMBinaryMapToString(const ETMBinaryMap& binary_map, std::string& s); +bool StringToETMBinaryMap(const std::string& s, ETMBinaryMap& binary_map); // Convert ETM data into branch lists while recording. class ETMBranchListGenerator { @@ -142,11 +142,33 @@ class ETMBranchListGenerator { virtual void SetExcludePid(pid_t pid) = 0; virtual void SetBinaryFilter(const RegEx* binary_name_regex) = 0; virtual bool ProcessRecord(const Record& r, bool& consumed) = 0; - virtual BranchListBinaryMap GetBranchListBinaryMap() = 0; + virtual ETMBinaryMap GetETMBinaryMap() = 0; +}; + +struct LBRBranch { + // If from_binary_id >= 1, it refers to LBRData.binaries[from_binary_id - 1]. Otherwise, it's + // invalid. + uint32_t from_binary_id = 0; + // If to_binary_id >= 1, it refers to LBRData.binaries[to_binary_id - 1]. Otherwise, it's invalid. + uint32_t to_binary_id = 0; + uint64_t from_vaddr_in_file = 0; + uint64_t to_vaddr_in_file = 0; +}; + +struct LBRSample { + // If binary_id >= 1, it refers to LBRData.binaries[binary_id - 1]. Otherwise, it's invalid. + uint32_t binary_id = 0; + uint64_t vaddr_in_file = 0; + std::vector<LBRBranch> branches; +}; + +struct LBRData { + std::vector<LBRSample> samples; + std::vector<BinaryKey> binaries; }; // for testing -std::string BranchToProtoString(const std::vector<bool>& branch); -std::vector<bool> ProtoStringToBranch(const std::string& s, size_t bit_size); +std::string ETMBranchToProtoString(const std::vector<bool>& branch); +std::vector<bool> ProtoStringToETMBranch(const std::string& s, size_t bit_size); } // namespace simpleperf diff --git a/simpleperf/ETMBranchListFile_test.cpp b/simpleperf/BranchListFile_test.cpp index 227c68b3..759b0218 100644 --- a/simpleperf/ETMBranchListFile_test.cpp +++ b/simpleperf/BranchListFile_test.cpp @@ -16,20 +16,20 @@ #include <gtest/gtest.h> -#include "ETMBranchListFile.h" +#include "BranchListFile.h" using namespace simpleperf; -TEST(ETMBranchListFile, branch_to_proto_string) { +TEST(BranchListFile, etm_branch_to_proto_string) { std::vector<bool> branch; for (size_t i = 0; i < 100; i++) { branch.push_back(i % 2 == 0); - std::string s = BranchToProtoString(branch); + std::string s = ETMBranchToProtoString(branch); for (size_t j = 0; j <= i; j++) { bool b = s[j >> 3] & (1 << (j & 7)); ASSERT_EQ(b, branch[j]); } - std::vector<bool> branch2 = ProtoStringToBranch(s, branch.size()); + std::vector<bool> branch2 = ProtoStringToETMBranch(s, branch.size()); ASSERT_EQ(branch, branch2); } } diff --git a/simpleperf/ETMDecoder.cpp b/simpleperf/ETMDecoder.cpp index 9cf43851..822cc6cd 100644 --- a/simpleperf/ETMDecoder.cpp +++ b/simpleperf/ETMDecoder.cpp @@ -930,8 +930,8 @@ class BranchDecoder { InstructionDecoder instruction_decoder_; }; -android::base::expected<void, std::string> ConvertBranchMapToInstrRanges( - Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback) { +android::base::expected<void, std::string> ConvertETMBranchMapToInstrRanges( + Dso* dso, const ETMBranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback) { ETMInstrRange instr_range; instr_range.dso = dso; diff --git a/simpleperf/ETMDecoder.h b/simpleperf/ETMDecoder.h index e8ae7f7d..cb5c8dc8 100644 --- a/simpleperf/ETMDecoder.h +++ b/simpleperf/ETMDecoder.h @@ -90,9 +90,9 @@ class ETMDecoder { // Map from addrs to a map of (branch_list, count). // Use maps instead of unordered_maps. Because it helps locality by decoding instructions for sorted // addresses. -using BranchMap = std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>; +using ETMBranchMap = std::map<uint64_t, std::map<std::vector<bool>, uint64_t>>; -android::base::expected<void, std::string> ConvertBranchMapToInstrRanges( - Dso* dso, const BranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback); +android::base::expected<void, std::string> ConvertETMBranchMapToInstrRanges( + Dso* dso, const ETMBranchMap& branch_map, const ETMDecoder::InstrRangeCallbackFn& callback); } // namespace simpleperf
\ No newline at end of file diff --git a/simpleperf/branch_list.proto b/simpleperf/branch_list.proto new file mode 100644 index 00000000..29247933 --- /dev/null +++ b/simpleperf/branch_list.proto @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2020 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. + */ + +// The branch list file format is generated by the inject command. It contains +// a single BranchList message. + +syntax = "proto3"; + +package simpleperf.proto; + +message BranchList { + // Used to identify format in generated proto files. + // Should always be "simpleperf:EtmBranchList". + string magic = 1; + repeated ETMBinary etm_data = 2; +} + +message ETMBinary { + string path = 1; + string build_id = 2; + + message Address { + // vaddr in binary, instr addr before the first branch + uint64 addr = 1; + + message Branch { + // Each bit represents a branch: 0 for not branch, 1 for branch. + // Bit 0 comes first, bit 7 comes last. + bytes branch = 1; + uint32 branch_size = 2; + uint64 count = 3; + } + + repeated Branch branches = 2; + } + + repeated Address addrs = 3; + + enum BinaryType { + ELF_FILE = 0; + KERNEL = 1; + KERNEL_MODULE = 2; + } + BinaryType type = 4; + + message KernelBinaryInfo { + // kernel_start_addr is used to convert kernel ip address to vaddr in vmlinux. + // If it is zero, the Address in KERNEL binary has been converted to vaddr. Otherwise, + // the Address in KERNEL binary is still ip address, and need to be converted later. + uint64 kernel_start_addr = 1; + } + + KernelBinaryInfo kernel_info = 5; +} diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp index 0bbbb10b..a9c4635b 100644 --- a/simpleperf/cmd_dumprecord.cpp +++ b/simpleperf/cmd_dumprecord.cpp @@ -26,7 +26,7 @@ #include <android-base/stringprintf.h> #include <android-base/strings.h> -#include "ETMBranchListFile.h" +#include "BranchListFile.h" #include "ETMDecoder.h" #include "command.h" #include "dso.h" @@ -214,7 +214,8 @@ class DumpRecordCommand : public Command { bool ProcessRecord(Record* r); void ProcessSampleRecord(const SampleRecord& r); void ProcessCallChainRecord(const CallChainRecord& r); - SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, bool in_kernel); + SymbolInfo GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, + std::optional<bool> in_kernel = std::nullopt); bool ProcessTracingData(const TracingDataRecord& r); bool DumpAuxData(const AuxRecord& aux); bool DumpFeatureSection(); @@ -397,6 +398,19 @@ void DumpRecordCommand::ProcessSampleRecord(const SampleRecord& sr) { s.vaddr_in_file); } } + if (sr.sample_type & PERF_SAMPLE_BRANCH_STACK) { + PrintIndented(1, "branch_stack:\n"); + for (size_t i = 0; i < sr.branch_stack_data.stack_nr; ++i) { + uint64_t from_ip = sr.branch_stack_data.stack[i].from; + uint64_t to_ip = sr.branch_stack_data.stack[i].to; + SymbolInfo from_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, from_ip); + SymbolInfo to_symbol = GetSymbolInfo(sr.tid_data.pid, sr.tid_data.tid, to_ip); + PrintIndented(2, "%s (%s[+%" PRIx64 "]) -> %s (%s[+%" PRIx64 "])\n", + from_symbol.symbol->DemangledName(), from_symbol.dso->Path().c_str(), + from_symbol.vaddr_in_file, to_symbol.symbol->DemangledName(), + to_symbol.dso->Path().c_str(), to_symbol.vaddr_in_file); + } + } // Dump tracepoint fields. if (!events_.empty()) { size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&sr); @@ -422,9 +436,14 @@ void DumpRecordCommand::ProcessCallChainRecord(const CallChainRecord& cr) { } SymbolInfo DumpRecordCommand::GetSymbolInfo(uint32_t pid, uint32_t tid, uint64_t ip, - bool in_kernel) { + std::optional<bool> in_kernel) { ThreadEntry* thread = thread_tree_.FindThreadOrNew(pid, tid); - const MapEntry* map = thread_tree_.FindMap(thread, ip, in_kernel); + const MapEntry* map; + if (in_kernel.has_value()) { + map = thread_tree_.FindMap(thread, ip, in_kernel.value()); + } else { + map = thread_tree_.FindMap(thread, ip); + } SymbolInfo info; info.symbol = thread_tree_.FindSymbol(map, ip, &info.vaddr_in_file, &info.dso); return info; @@ -550,8 +569,8 @@ bool DumpRecordCommand::DumpFeatureSection() { if (!record_file_reader_->ReadFeatureSection(FEAT_ETM_BRANCH_LIST, &data)) { return false; } - BranchListBinaryMap binary_map; - if (!StringToBranchListBinaryMap(data, binary_map)) { + ETMBinaryMap binary_map; + if (!StringToETMBinaryMap(data, binary_map)) { return false; } PrintIndented(1, "etm_branch_list:\n"); diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp index d3f66cd7..be8f4173 100644 --- a/simpleperf/cmd_inject.cpp +++ b/simpleperf/cmd_inject.cpp @@ -25,12 +25,12 @@ #include <android-base/parseint.h> #include <android-base/strings.h> -#include "ETMBranchListFile.h" +#include "BranchListFile.h" #include "ETMDecoder.h" #include "RegEx.h" #include "command.h" #include "record_file.h" -#include "system/extras/simpleperf/etm_branch_list.pb.h" +#include "system/extras/simpleperf/branch_list.pb.h" #include "thread_tree.h" #include "utils.h" @@ -56,9 +56,20 @@ enum class OutputFormat { struct AutoFDOBinaryInfo { uint64_t first_load_segment_addr = 0; + std::unordered_map<uint64_t, uint64_t> address_count_map; std::unordered_map<AddrPair, uint64_t, AddrPairHash> range_count_map; std::unordered_map<AddrPair, uint64_t, AddrPairHash> branch_count_map; + void AddAddress(uint64_t addr) { OverflowSafeAdd(address_count_map[addr], 1); } + + void AddRange(uint64_t begin, uint64_t end) { + OverflowSafeAdd(range_count_map[std::make_pair(begin, end)], 1); + } + + void AddBranch(uint64_t from, uint64_t to) { + OverflowSafeAdd(branch_count_map[std::make_pair(from, to)], 1); + } + void AddInstrRange(const ETMInstrRange& instr_range) { uint64_t total_count = instr_range.branch_taken_count; OverflowSafeAdd(total_count, instr_range.branch_not_taken_count); @@ -71,6 +82,12 @@ struct AutoFDOBinaryInfo { } void Merge(const AutoFDOBinaryInfo& other) { + for (const auto& p : other.address_count_map) { + auto res = address_count_map.emplace(p.first, p.second); + if (!res.second) { + OverflowSafeAdd(res.first->second, p.second); + } + } for (const auto& p : other.range_count_map) { auto res = range_count_map.emplace(p.first, p.second); if (!res.second) { @@ -87,53 +104,10 @@ struct AutoFDOBinaryInfo { }; using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>; -using BranchListBinaryCallback = std::function<void(const BinaryKey&, BranchListBinaryInfo&)>; - -class ETMThreadTreeWithFilter : public ETMThreadTree { - public: - void ExcludePid(pid_t pid) { exclude_pid_ = pid; } - ThreadTree& GetThreadTree() { return thread_tree_; } - void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); } - - const ThreadEntry* FindThread(int tid) override { - const ThreadEntry* thread = thread_tree_.FindThread(tid); - if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) { - return nullptr; - } - return thread; - } - - const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); } - - private: - ThreadTree thread_tree_; - std::optional<pid_t> exclude_pid_; -}; - -class BinaryFilter { - public: - BinaryFilter(const RegEx* binary_name_regex) : binary_name_regex_(binary_name_regex) {} - - bool Filter(Dso* dso) { - auto lookup = dso_filter_cache_.find(dso); - if (lookup != dso_filter_cache_.end()) { - return lookup->second; - } - bool match = Filter(dso->Path()); - dso_filter_cache_.insert({dso, match}); - return match; - } - - bool Filter(const std::string& path) { - return binary_name_regex_ == nullptr || binary_name_regex_->Search(path); - } - - private: - const RegEx* binary_name_regex_; - std::unordered_map<Dso*, bool> dso_filter_cache_; -}; +using ETMBinaryCallback = std::function<void(const BinaryKey&, ETMBinary&)>; +using LBRDataCallback = std::function<void(LBRData&)>; -static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) { +static uint64_t GetFirstLoadSegmentVaddr(const Dso* dso) { ElfStatus status; if (auto elf = ElfFile::Open(dso->GetDebugFilePath(), &status); elf) { for (const auto& segment : elf->GetProgramHeader()) { @@ -145,90 +119,132 @@ static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) { return 0; } -// Read perf.data, and generate AutoFDOBinaryInfo or BranchListBinaryInfo. -// To avoid resetting data, it only processes one input file per instance. +// Base class for reading perf.data and generating AutoFDO or branch list data. class PerfDataReader { public: - PerfDataReader(const std::string& filename, bool exclude_perf, ETMDumpOption etm_dump_option, + static std::string GetDataType(RecordFileReader& reader) { + const EventAttrIds& attrs = reader.AttrSection(); + if (attrs.size() != 1) { + return "unknown"; + } + const perf_event_attr& attr = attrs[0].attr; + if (IsEtmEventType(attr.type)) { + return "etm"; + } + if (attr.sample_type & PERF_SAMPLE_BRANCH_STACK) { + return "lbr"; + } + return "unknown"; + } + + PerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf, const RegEx* binary_name_regex) - : filename_(filename), + : reader_(std::move(reader)), exclude_perf_(exclude_perf), - etm_dump_option_(etm_dump_option), binary_filter_(binary_name_regex) {} + virtual ~PerfDataReader() {} + + std::string GetDataType() const { return GetDataType(*reader_); } void SetCallback(const AutoFDOBinaryCallback& callback) { autofdo_callback_ = callback; } - void SetCallback(const BranchListBinaryCallback& callback) { branch_list_callback_ = callback; } + virtual void SetCallback(const ETMBinaryCallback&) {} + virtual void SetCallback(const LBRDataCallback&) {} - bool Read() { - record_file_reader_ = RecordFileReader::CreateInstance(filename_); - if (!record_file_reader_) { - return false; - } - if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) { - return ProcessETMBranchListFeature(); - } + virtual bool Read() { if (exclude_perf_) { - const auto& info_map = record_file_reader_->GetMetaInfoFeature(); + const auto& info_map = reader_->GetMetaInfoFeature(); if (auto it = info_map.find("recording_process"); it == info_map.end()) { - LOG(ERROR) << filename_ << " doesn't support --exclude-perf"; + LOG(ERROR) << reader_->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_; + LOG(ERROR) << "invalid recording_process " << it->second << " in " << reader_->FileName(); return false; } - thread_tree_.ExcludePid(pid); + exclude_pid_ = pid; } } - if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_.GetThreadTree())) { + + if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) { return false; } - if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) { + if (!reader_->ReadDataSection([this](auto r) { return ProcessRecord(*r); })) { return false; } - if (etm_decoder_ && !etm_decoder_->FinishData()) { - return false; + return PostProcess(); + } + + protected: + virtual bool ProcessRecord(Record& r) = 0; + virtual bool PostProcess() = 0; + + void ProcessAutoFDOBinaryInfo() { + for (auto& p : autofdo_binary_map_) { + const Dso* dso = p.first; + AutoFDOBinaryInfo& binary = p.second; + binary.first_load_segment_addr = GetFirstLoadSegmentVaddr(dso); + autofdo_callback_(BinaryKey(dso, 0), binary); } - if (autofdo_callback_) { - ProcessAutoFDOBinaryInfo(); - } else if (branch_list_callback_) { - ProcessBranchListBinaryInfo(); + } + + const std::string data_type_; + std::unique_ptr<RecordFileReader> reader_; + bool exclude_perf_; + BinaryFilter binary_filter_; + + std::optional<int> exclude_pid_; + ThreadTree thread_tree_; + AutoFDOBinaryCallback autofdo_callback_; + // Store results for AutoFDO. + std::unordered_map<const Dso*, AutoFDOBinaryInfo> autofdo_binary_map_; +}; + +class ETMThreadTreeWithFilter : public ETMThreadTree { + public: + ETMThreadTreeWithFilter(ThreadTree& thread_tree, std::optional<int>& exclude_pid) + : thread_tree_(thread_tree), exclude_pid_(exclude_pid) {} + + void DisableThreadExitRecords() override { thread_tree_.DisableThreadExitRecords(); } + + const ThreadEntry* FindThread(int tid) override { + const ThreadEntry* thread = thread_tree_.FindThread(tid); + if (thread != nullptr && exclude_pid_ && thread->pid == exclude_pid_) { + return nullptr; } - return true; + return thread; } + const MapSet& GetKernelMaps() override { return thread_tree_.GetKernelMaps(); } + private: - bool ProcessETMBranchListFeature() { - if (exclude_perf_) { - LOG(WARNING) << "--exclude-perf has no effect on perf.data with etm branch list"; - } - if (autofdo_callback_) { - LOG(ERROR) << "convert to autofdo format isn't support on perf.data with etm branch list"; - return false; - } - CHECK(branch_list_callback_); - std::string s; - if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_ETM_BRANCH_LIST, &s)) { - return false; - } - BranchListBinaryMap binary_map; - if (!StringToBranchListBinaryMap(s, binary_map)) { - return false; - } - for (auto& [key, binary] : binary_map) { - if (!binary_filter_.Filter(key.path)) { - continue; - } - branch_list_callback_(key, binary); + ThreadTree& thread_tree_; + std::optional<int>& exclude_pid_; +}; + +// Read perf.data with ETM data and generate AutoFDO or branch list data. +class ETMPerfDataReader : public PerfDataReader { + public: + ETMPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf, + const RegEx* binary_name_regex, ETMDumpOption etm_dump_option) + : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex), + etm_dump_option_(etm_dump_option), + etm_thread_tree_(thread_tree_, exclude_pid_) {} + + void SetCallback(const ETMBinaryCallback& callback) override { etm_binary_callback_ = callback; } + + bool Read() override { + if (reader_->HasFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST)) { + return ProcessETMBranchListFeature(); } - return true; + return PerfDataReader::Read(); } - bool ProcessRecord(Record* r) { - thread_tree_.GetThreadTree().Update(*r); - if (r->type() == PERF_RECORD_AUXTRACE_INFO) { - etm_decoder_ = ETMDecoder::Create(*static_cast<AuxTraceInfoRecord*>(r), thread_tree_); + private: + bool ProcessRecord(Record& r) override { + thread_tree_.Update(r); + if (r.type() == PERF_RECORD_AUXTRACE_INFO) { + etm_decoder_ = ETMDecoder::Create(static_cast<AuxTraceInfoRecord&>(r), etm_thread_tree_); if (!etm_decoder_) { return false; } @@ -236,32 +252,32 @@ class PerfDataReader { if (autofdo_callback_) { etm_decoder_->RegisterCallback( [this](const ETMInstrRange& range) { ProcessInstrRange(range); }); - } else if (branch_list_callback_) { + } else if (etm_binary_callback_) { etm_decoder_->RegisterCallback( - [this](const ETMBranchList& branch) { ProcessBranchList(branch); }); + [this](const ETMBranchList& branch) { ProcessETMBranchList(branch); }); } - } else if (r->type() == PERF_RECORD_AUX) { - AuxRecord* aux = static_cast<AuxRecord*>(r); - if (aux->data->aux_size > SIZE_MAX) { + } else if (r.type() == PERF_RECORD_AUX) { + AuxRecord& aux = static_cast<AuxRecord&>(r); + if (aux.data->aux_size > SIZE_MAX) { LOG(ERROR) << "invalid aux size"; return false; } - size_t aux_size = aux->data->aux_size; + size_t aux_size = aux.data->aux_size; if (aux_size > 0) { bool error = false; - if (!record_file_reader_->ReadAuxData(aux->Cpu(), aux->data->aux_offset, aux_size, - aux_data_buffer_, error)) { + if (!reader_->ReadAuxData(aux.Cpu(), aux.data->aux_offset, aux_size, aux_data_buffer_, + error)) { return !error; } if (!etm_decoder_) { LOG(ERROR) << "ETMDecoder isn't created"; return false; } - return etm_decoder_->ProcessData(aux_data_buffer_.data(), aux_size, !aux->Unformatted(), - aux->Cpu()); + 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); + } 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; } @@ -269,6 +285,44 @@ class PerfDataReader { return true; } + bool PostProcess() override { + if (etm_decoder_ && !etm_decoder_->FinishData()) { + return false; + } + if (autofdo_callback_) { + ProcessAutoFDOBinaryInfo(); + } else if (etm_binary_callback_) { + ProcessETMBinary(); + } + return true; + } + + bool ProcessETMBranchListFeature() { + if (exclude_perf_) { + LOG(WARNING) << "--exclude-perf has no effect on perf.data with etm branch list"; + } + if (autofdo_callback_) { + LOG(ERROR) << "convert to autofdo format isn't support on perf.data with etm branch list"; + return false; + } + CHECK(etm_binary_callback_); + std::string s; + if (!reader_->ReadFeatureSection(PerfFileFormat::FEAT_ETM_BRANCH_LIST, &s)) { + return false; + } + ETMBinaryMap binary_map; + if (!StringToETMBinaryMap(s, binary_map)) { + return false; + } + for (auto& [key, binary] : binary_map) { + if (!binary_filter_.Filter(key.path)) { + continue; + } + etm_binary_callback_(key, binary); + } + return true; + } + void ProcessInstrRange(const ETMInstrRange& instr_range) { if (!binary_filter_.Filter(instr_range.dso)) { return; @@ -277,28 +331,19 @@ class PerfDataReader { autofdo_binary_map_[instr_range.dso].AddInstrRange(instr_range); } - void ProcessBranchList(const ETMBranchList& branch_list) { + void ProcessETMBranchList(const ETMBranchList& branch_list) { if (!binary_filter_.Filter(branch_list.dso)) { return; } - auto& branch_map = branch_list_binary_map_[branch_list.dso].branch_map; + auto& branch_map = etm_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_) { + void ProcessETMBinary() { + for (auto& p : etm_binary_map_) { Dso* dso = p.first; - BranchListBinaryInfo& binary = p.second; + ETMBinary& binary = p.second; binary.dso_type = dso->type(); BinaryKey key(dso, 0); if (binary.dso_type == DSO_KERNEL) { @@ -313,35 +358,137 @@ class PerfDataReader { key.kernel_start_addr = kernel_map_start_addr_; } } - branch_list_callback_(key, binary); + etm_binary_callback_(key, binary); } } - const std::string filename_; - bool exclude_perf_; ETMDumpOption etm_dump_option_; - BinaryFilter binary_filter_; - AutoFDOBinaryCallback autofdo_callback_; - BranchListBinaryCallback branch_list_callback_; - + ETMBinaryCallback etm_binary_callback_; + ETMThreadTreeWithFilter etm_thread_tree_; std::vector<uint8_t> aux_data_buffer_; std::unique_ptr<ETMDecoder> etm_decoder_; - std::unique_ptr<RecordFileReader> record_file_reader_; - ETMThreadTreeWithFilter 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_; + // Store etm branch list data. + std::unordered_map<Dso*, ETMBinary> etm_binary_map_; }; -// Read a protobuf file specified by etm_branch_list.proto, and generate BranchListBinaryInfo. -class BranchListReader { +class LBRPerfDataReader : public PerfDataReader { public: - BranchListReader(const std::string& filename, const RegEx* binary_name_regex) + LBRPerfDataReader(std::unique_ptr<RecordFileReader> reader, bool exclude_perf, + const RegEx* binary_name_regex) + : PerfDataReader(std::move(reader), exclude_perf, binary_name_regex) {} + void SetCallback(const LBRDataCallback& callback) override { lbr_data_callback_ = callback; } + + private: + bool ProcessRecord(Record& r) override { + thread_tree_.Update(r); + if (r.type() == PERF_RECORD_SAMPLE) { + auto& sr = static_cast<SampleRecord&>(r); + ThreadEntry* thread = thread_tree_.FindThread(sr.tid_data.tid); + if (thread == nullptr) { + return true; + } + auto& stack = sr.branch_stack_data; + lbr_data_.samples.resize(lbr_data_.samples.size() + 1); + LBRSample& sample = lbr_data_.samples.back(); + std::pair<uint32_t, uint64_t> binary_addr = IpToBinaryAddr(*thread, sr.ip_data.ip); + sample.binary_id = binary_addr.first; + bool has_valid_binary_id = sample.binary_id != 0; + sample.vaddr_in_file = binary_addr.second; + sample.branches.resize(stack.stack_nr); + for (size_t i = 0; i < stack.stack_nr; ++i) { + uint64_t from_ip = stack.stack[i].from; + uint64_t to_ip = stack.stack[i].to; + LBRBranch& branch = sample.branches[i]; + binary_addr = IpToBinaryAddr(*thread, from_ip); + branch.from_binary_id = binary_addr.first; + branch.from_vaddr_in_file = binary_addr.second; + binary_addr = IpToBinaryAddr(*thread, to_ip); + branch.to_binary_id = binary_addr.first; + branch.to_vaddr_in_file = binary_addr.second; + if (branch.from_binary_id != 0 || branch.to_binary_id != 0) { + has_valid_binary_id = true; + } + } + if (!has_valid_binary_id) { + lbr_data_.samples.pop_back(); + } + } + return true; + } + + bool PostProcess() override { + if (autofdo_callback_) { + ConvertLBRDataToAutoFDO(); + ProcessAutoFDOBinaryInfo(); + } + return true; + } + + std::pair<uint32_t, uint64_t> IpToBinaryAddr(ThreadEntry& thread, uint64_t ip) { + const MapEntry* map = thread_tree_.FindMap(&thread, ip); + Dso* dso = map->dso; + if (thread_tree_.IsUnknownDso(dso) || !binary_filter_.Filter(dso)) { + return std::make_pair(0, 0); + } + uint32_t binary_id = GetBinaryId(dso); + uint64_t vaddr_in_file = dso->IpToVaddrInFile(ip, map->start_addr, map->pgoff); + return std::make_pair(binary_id, vaddr_in_file); + } + + uint32_t GetBinaryId(const Dso* dso) { + if (auto it = dso_map_.find(dso); it != dso_map_.end()) { + return it->second; + } + uint32_t binary_id = static_cast<uint32_t>(lbr_data_.binaries.size() + 1); + dso_map_[dso] = binary_id; + return binary_id; + } + + void ConvertLBRDataToAutoFDO() { + std::vector<AutoFDOBinaryInfo> binaries(dso_map_.size()); + for (const LBRSample& sample : lbr_data_.samples) { + if (sample.binary_id != 0) { + binaries[sample.binary_id - 1].AddAddress(sample.vaddr_in_file); + } + for (size_t i = 0; i < sample.branches.size(); ++i) { + const LBRBranch& branch = sample.branches[i]; + if (branch.from_binary_id == 0) { + continue; + } + if (branch.from_binary_id == branch.to_binary_id) { + binaries[branch.from_binary_id - 1].AddBranch(branch.from_vaddr_in_file, + branch.to_vaddr_in_file); + } + if (i > 0 && branch.from_binary_id == sample.branches[i - 1].to_binary_id) { + uint64_t begin = sample.branches[i - 1].to_vaddr_in_file; + uint64_t end = branch.from_vaddr_in_file; + // Use the same logic to skip bogus LBR data as AutoFDO. + if (end < begin || end - begin > (1 << 20)) { + continue; + } + binaries[branch.from_binary_id - 1].AddRange(begin, end); + } + } + } + for (const auto& [dso, binary_id] : dso_map_) { + autofdo_binary_map_[dso] = std::move(binaries[binary_id - 1]); + } + } + + LBRDataCallback lbr_data_callback_; + LBRData lbr_data_; + // Map from dso to binary_id in lbr_data_. + std::unordered_map<const Dso*, uint32_t> dso_map_; +}; + +// Read a protobuf file specified by etm_branch_list.proto, and generate ETMBinary. +class ETMBranchListReader { + public: + ETMBranchListReader(const std::string& filename, const RegEx* binary_name_regex) : filename_(filename), binary_filter_(binary_name_regex) {} - void SetCallback(const BranchListBinaryCallback& callback) { callback_ = callback; } + void SetCallback(const ETMBinaryCallback& callback) { callback_ = callback; } bool Read() { std::string s; @@ -349,8 +496,8 @@ class BranchListReader { PLOG(ERROR) << "failed to read " << filename_; return false; } - BranchListBinaryMap binary_map; - if (!StringToBranchListBinaryMap(s, binary_map)) { + ETMBinaryMap binary_map; + if (!StringToETMBinaryMap(s, binary_map)) { PLOG(ERROR) << "file is in wrong format: " << filename_; return false; } @@ -366,13 +513,13 @@ class BranchListReader { private: const std::string filename_; BinaryFilter binary_filter_; - BranchListBinaryCallback callback_; + ETMBinaryCallback callback_; }; -// Convert BranchListBinaryInfo into AutoFDOBinaryInfo. -class BranchListToAutoFDOConverter { +// Convert ETMBinary into AutoFDOBinaryInfo. +class ETMBranchListToAutoFDOConverter { public: - std::unique_ptr<AutoFDOBinaryInfo> Convert(const BinaryKey& key, BranchListBinaryInfo& binary) { + std::unique_ptr<AutoFDOBinaryInfo> Convert(const BinaryKey& key, ETMBinary& 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)) { @@ -390,8 +537,8 @@ class BranchListToAutoFDOConverter { autofdo_binary->AddInstrRange(range); }; - auto result = - ConvertBranchMapToInstrRanges(dso.get(), binary.GetOrderedBranchMap(), process_instr_range); + auto result = ConvertETMBranchMapToInstrRanges(dso.get(), binary.GetOrderedBranchMap(), + process_instr_range); if (!result.ok()) { LOG(WARNING) << "failed to build instr ranges for binary " << dso->Path() << ": " << result.error(); @@ -410,15 +557,14 @@ class BranchListToAutoFDOConverter { build_id == expected_build_id; } - void ModifyBranchMapForKernel(Dso* dso, uint64_t kernel_start_addr, - BranchListBinaryInfo& binary) { + void ModifyBranchMapForKernel(Dso* dso, uint64_t kernel_start_addr, ETMBinary& 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; + UnorderedETMBranchMap 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); @@ -486,7 +632,12 @@ class AutoFDOWriter { } // Write addr_count_map. - fprintf(output_fp.get(), "0\n"); + std::map<uint64_t, uint64_t> address_count_map(binary.address_count_map.begin(), + binary.address_count_map.end()); + fprintf(output_fp.get(), "%zu\n", address_count_map.size()); + for (const auto& [addr, count] : address_count_map) { + fprintf(output_fp.get(), "%" PRIx64 ":%" PRIu64 "\n", to_offset(addr), count); + } // Write branch_count_map. std::map<AddrPair, uint64_t> branch_count_map(binary.branch_count_map.begin(), @@ -511,9 +662,9 @@ class AutoFDOWriter { std::unordered_map<BinaryKey, AutoFDOBinaryInfo, BinaryKeyHash> binary_map_; }; -// Merge BranchListBinaryInfo. -struct BranchListMerger { - void AddBranchListBinary(const BinaryKey& key, BranchListBinaryInfo& binary) { +// Merge ETMBinary. +struct ETMBranchListMerger { + void AddETMBinary(const BinaryKey& key, ETMBinary& binary) { auto it = binary_map.find(key); if (it == binary_map.end()) { binary_map[key] = std::move(binary); @@ -522,13 +673,13 @@ struct BranchListMerger { } } - BranchListBinaryMap binary_map; + ETMBinaryMap binary_map; }; // Write branch lists to a protobuf file specified by etm_branch_list.proto. -class BranchListWriter { +class ETMBranchListWriter { public: - bool Write(const std::string& output_filename, const BranchListBinaryMap& binary_map) { + bool Write(const std::string& output_filename, const ETMBinaryMap& binary_map) { // Don't produce empty output file. if (binary_map.empty()) { LOG(INFO) << "Skip empty output file."; @@ -536,8 +687,8 @@ class BranchListWriter { return true; } std::string s; - if (!BranchListBinaryMapToString(binary_map, s)) { - LOG(ERROR) << "invalid BranchListBinaryMap"; + if (!ETMBinaryMapToString(binary_map, s)) { + LOG(ERROR) << "invalid ETMBinaryMap"; return false; } if (!android::base::WriteStringToFile(s, output_filename)) { @@ -684,59 +835,88 @@ class InjectCommand : public Command { return true; } + bool ReadPerfDataFiles(const std::function<void(PerfDataReader&)> reader_callback) { + if (input_filenames_.empty()) { + return true; + } + + std::string expected_data_type; + for (const auto& filename : input_filenames_) { + std::unique_ptr<RecordFileReader> file_reader = RecordFileReader::CreateInstance(filename); + if (!file_reader) { + return false; + } + std::string data_type = PerfDataReader::GetDataType(*file_reader); + if (expected_data_type.empty()) { + expected_data_type = data_type; + } else if (expected_data_type != data_type) { + LOG(ERROR) << "files have different data type: " << input_filenames_[0] << ", " << filename; + return false; + } + std::unique_ptr<PerfDataReader> reader; + if (data_type == "etm") { + reader.reset(new ETMPerfDataReader(std::move(file_reader), exclude_perf_, + binary_name_regex_.get(), etm_dump_option_)); + } else if (data_type == "lbr") { + reader.reset( + new LBRPerfDataReader(std::move(file_reader), exclude_perf_, binary_name_regex_.get())); + } else { + LOG(ERROR) << "unsupported data type " << data_type << " in " << filename; + return false; + } + reader_callback(*reader); + if (!reader->Read()) { + return false; + } + } + return true; + } + bool ConvertPerfDataToAutoFDO() { AutoFDOWriter autofdo_writer; - auto callback = [&](const BinaryKey& key, AutoFDOBinaryInfo& binary) { + auto afdo_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_.get()); - reader.SetCallback(callback); - if (!reader.Read()) { - return false; - } + auto reader_callback = [&](PerfDataReader& reader) { reader.SetCallback(afdo_callback); }; + if (!ReadPerfDataFiles(reader_callback)) { + 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); + ETMBranchListMerger branch_list_merger; + auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) { + branch_list_merger.AddETMBinary(key, binary); }; - for (const auto& input_filename : input_filenames_) { - PerfDataReader reader(input_filename, exclude_perf_, etm_dump_option_, - binary_name_regex_.get()); - reader.SetCallback(callback); - if (!reader.Read()) { - return false; - } + auto reader_callback = [&](PerfDataReader& reader) { reader.SetCallback(etm_callback); }; + if (!ReadPerfDataFiles(reader_callback)) { + return false; } - BranchListWriter branch_list_writer; + ETMBranchListWriter 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); + ETMBranchListMerger branch_list_merger; + auto callback = [&](const BinaryKey& key, ETMBinary& binary) { + branch_list_merger.AddETMBinary(key, binary); }; for (const auto& input_filename : input_filenames_) { - BranchListReader reader(input_filename, binary_name_regex_.get()); + ETMBranchListReader reader(input_filename, binary_name_regex_.get()); reader.SetCallback(callback); if (!reader.Read()) { return false; } } - // Step2: Convert BranchListBinaryInfo to AutoFDOBinaryInfo. + // Step2: Convert ETMBinary to AutoFDOBinaryInfo. AutoFDOWriter autofdo_writer; - BranchListToAutoFDOConverter converter; + ETMBranchListToAutoFDOConverter converter; for (auto& p : branch_list_merger.binary_map) { const BinaryKey& key = p.first; - BranchListBinaryInfo& binary = p.second; + ETMBinary& 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 @@ -751,19 +931,19 @@ class InjectCommand : public Command { 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); + ETMBranchListMerger branch_list_merger; + auto callback = [&](const BinaryKey& key, ETMBinary& binary) { + branch_list_merger.AddETMBinary(key, binary); }; for (const auto& input_filename : input_filenames_) { - BranchListReader reader(input_filename, binary_name_regex_.get()); + ETMBranchListReader reader(input_filename, binary_name_regex_.get()); reader.SetCallback(callback); if (!reader.Read()) { return false; } } - // Step2: Write BranchListBinaryInfo. - BranchListWriter branch_list_writer; + // Step2: Write ETMBinary. + ETMBranchListWriter branch_list_writer; return branch_list_writer.Write(output_filename_, branch_list_merger.binary_map); } diff --git a/simpleperf/cmd_inject_test.cpp b/simpleperf/cmd_inject_test.cpp index 23347b90..3b25553b 100644 --- a/simpleperf/cmd_inject_test.cpp +++ b/simpleperf/cmd_inject_test.cpp @@ -224,3 +224,14 @@ TEST(cmd_inject, accept_missing_aux_data) { close(tmpfile.release()); ASSERT_TRUE(RunInjectCmd({"--output", "branch-list", "-i", perf_data, "-o", tmpfile.path})); } + +TEST(cmd_inject, read_lbr_data) { + std::string data; + ASSERT_TRUE(RunInjectCmd({"-i", GetTestData("lbr/perf_lbr.data")}, &data)); + data.erase(std::remove(data.begin(), data.end(), '\r'), data.end()); + + std::string expected_data; + ASSERT_TRUE(android::base::ReadFileToString( + GetTestData(std::string("lbr") + OS_PATH_SEPARATOR + "inject_lbr.data"), &expected_data)); + ASSERT_NE(data.find(expected_data), data.npos); +} diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index ad1133ce..da1a31bf 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -47,8 +47,8 @@ #endif #include <unwindstack/Error.h> +#include "BranchListFile.h" #include "CallChainJoiner.h" -#include "ETMBranchListFile.h" #include "ETMRecorder.h" #include "IOEventLoop.h" #include "JITDebugReader.h" @@ -2214,9 +2214,9 @@ void RecordCommand::CollectHitFileInfo(const SampleRecord& r, std::unordered_set } bool RecordCommand::DumpETMBranchListFeature() { - BranchListBinaryMap binary_map = etm_branch_list_generator_->GetBranchListBinaryMap(); + ETMBinaryMap binary_map = etm_branch_list_generator_->GetETMBinaryMap(); std::string s; - if (!BranchListBinaryMapToString(binary_map, s)) { + if (!ETMBinaryMapToString(binary_map, s)) { return false; } return record_file_writer_->WriteFeature(PerfFileFormat::FEAT_ETM_BRANCH_LIST, s.data(), diff --git a/simpleperf/etm_branch_list.proto b/simpleperf/etm_branch_list.proto deleted file mode 100644 index c66b0d5e..00000000 --- a/simpleperf/etm_branch_list.proto +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (C) 2020 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. - */ - -syntax = "proto3"; - -package simpleperf.proto; - -message ETMBranchList { - - message Binary { - string path = 1; - string build_id = 2; - - message Address { - // vaddr in binary, instr addr before the first branch - uint64 addr = 1; - - message Branch { - // Each bit represents a branch: 0 for not branch, 1 for branch. - // Bit 0 comes first, bit 7 comes last. - bytes branch = 1; - uint32 branch_size = 2; - uint64 count = 3; - } - - repeated Branch branches = 2; - } - - repeated Address addrs = 3; - - enum BinaryType { - ELF_FILE = 0; - KERNEL = 1; - KERNEL_MODULE = 2; - } - BinaryType type = 4; - - message KernelBinaryInfo { - // kernel_start_addr is used to convert kernel ip address to vaddr in vmlinux. - // If it is zero, the Address in KERNEL binary has been converted to vaddr. Otherwise, - // the Address in KERNEL binary is still ip address, and need to be converted later. - uint64 kernel_start_addr = 1; - } - - KernelBinaryInfo kernel_info = 5; - - } - - // Used to identify format in generated proto files. - // Should always be "simpleperf:EtmBranchList". - string magic = 1; - repeated Binary binaries = 2; -} diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h index fa696269..76d982d3 100644 --- a/simpleperf/record_file.h +++ b/simpleperf/record_file.h @@ -139,6 +139,7 @@ class RecordFileReader { ~RecordFileReader(); + const std::string FileName() const { return filename_; } const PerfFileFormat::FileHeader& FileHeader() const { return header_; } const EventAttrIds& AttrSection() const { return event_attrs_; } diff --git a/simpleperf/testdata/lbr/inject_lbr.data b/simpleperf/testdata/lbr/inject_lbr.data new file mode 100644 index 00000000..ffc9b5b5 --- /dev/null +++ b/simpleperf/testdata/lbr/inject_lbr.data @@ -0,0 +1,11 @@ +2 +1910-191b:31 +1940-194d:341 +4 +1914:1 +1940:3 +1944:4 +1948:11 +2 +191b->1910:32 +194d->1940:353 diff --git a/simpleperf/testdata/lbr/perf_lbr.data b/simpleperf/testdata/lbr/perf_lbr.data Binary files differnew file mode 100644 index 00000000..81fafe62 --- /dev/null +++ b/simpleperf/testdata/lbr/perf_lbr.data |