diff options
author | Yabin Cui <yabinc@google.com> | 2023-12-01 15:32:00 -0800 |
---|---|---|
committer | Yabin Cui <yabinc@google.com> | 2023-12-04 19:34:40 -0800 |
commit | 544fa56bbe864550fcc4585901adba8179eeb4b1 (patch) | |
tree | c090b1705d69dad1de26539eb437dbf3e4dd5160 | |
parent | 82d48057901353b9d6be37c3c3e6079e370c5076 (diff) | |
download | extras-544fa56bbe864550fcc4585901adba8179eeb4b1.tar.gz |
simpleperf: inject cmd: Support reading LBR data
1. In BranchListFile.h, add data structures representing
LBR data.
2. In cmd_inject.cpp, add LBRPerfDataReader to extract LBR
data from perf.data.
3. Add test checking reading perf.data with LBR data.
4. Also dump symbolized branch stack in the dump cmd.
Bug: 293953824
Test: run simpleperf_unit_test
Change-Id: Icae497f9f58b9523ae9feaba4bf10313e8f27d50
-rw-r--r-- | simpleperf/BranchListFile.h | 26 | ||||
-rw-r--r-- | simpleperf/cmd_dumprecord.cpp | 25 | ||||
-rw-r--r-- | simpleperf/cmd_inject.cpp | 366 | ||||
-rw-r--r-- | simpleperf/cmd_inject_test.cpp | 7 | ||||
-rw-r--r-- | simpleperf/record_file.h | 1 | ||||
-rw-r--r-- | simpleperf/testdata/perf_lbr.data | bin | 0 -> 23855 bytes |
6 files changed, 306 insertions, 119 deletions
diff --git a/simpleperf/BranchListFile.h b/simpleperf/BranchListFile.h index 3ed13003..b2d51b12 100644 --- a/simpleperf/BranchListFile.h +++ b/simpleperf/BranchListFile.h @@ -72,7 +72,7 @@ class BinaryFilter { dso_filter_cache_.clear(); } - bool Filter(Dso* dso) { + bool Filter(const Dso* dso) { auto lookup = dso_filter_cache_.find(dso); if (lookup != dso_filter_cache_.end()) { return lookup->second; @@ -88,7 +88,7 @@ class BinaryFilter { private: const RegEx* binary_name_regex_; - std::unordered_map<Dso*, bool> dso_filter_cache_; + std::unordered_map<const Dso*, bool> dso_filter_cache_; }; using UnorderedETMBranchMap = @@ -145,6 +145,28 @@ class ETMBranchListGenerator { 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 ETMBranchToProtoString(const std::vector<bool>& branch); std::vector<bool> ProtoStringToETMBranch(const std::string& s, size_t bit_size); diff --git a/simpleperf/cmd_dumprecord.cpp b/simpleperf/cmd_dumprecord.cpp index 013af562..a9c4635b 100644 --- a/simpleperf/cmd_dumprecord.cpp +++ b/simpleperf/cmd_dumprecord.cpp @@ -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; diff --git a/simpleperf/cmd_inject.cpp b/simpleperf/cmd_inject.cpp index 712f6ee0..7eafe6da 100644 --- a/simpleperf/cmd_inject.cpp +++ b/simpleperf/cmd_inject.cpp @@ -88,27 +88,7 @@ struct AutoFDOBinaryInfo { using AutoFDOBinaryCallback = std::function<void(const BinaryKey&, AutoFDOBinaryInfo&)>; using ETMBinaryCallback = std::function<void(const BinaryKey&, ETMBinary&)>; - -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_; -}; +using LBRDataCallback = std::function<void(LBRData&)>; static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) { ElfStatus status; @@ -122,90 +102,123 @@ static uint64_t GetFirstLoadSegmentVaddr(Dso* dso) { return 0; } -// Read perf.data, and generate AutoFDOBinaryInfo or ETMBinary. -// To avoid resetting data, it only processes one input file per instance. -class ETMPerfDataReader { +// Base class for reading perf.data and generating AutoFDO or branch list data. +class PerfDataReader { public: - ETMPerfDataReader(const std::string& filename, bool exclude_perf, ETMDumpOption etm_dump_option, - const RegEx* binary_name_regex) - : filename_(filename), + 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) + : 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 ETMBinaryCallback& callback) { etm_binary_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())) { - return false; - } - if (!record_file_reader_->ReadDataSection([this](auto r) { return ProcessRecord(r.get()); })) { + + if (!reader_->LoadBuildIdAndFileFeatures(thread_tree_)) { return false; } - if (etm_decoder_ && !etm_decoder_->FinishData()) { + if (!reader_->ReadDataSection([this](auto r) { return ProcessRecord(*r); })) { return false; } - if (autofdo_callback_) { - ProcessAutoFDOBinaryInfo(); - } else if (etm_binary_callback_) { - ProcessETMBinary(); + return PostProcess(); + } + + protected: + virtual bool ProcessRecord(Record& r) = 0; + virtual bool PostProcess() = 0; + + 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<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(etm_binary_callback_); - std::string s; - if (!record_file_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); + 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; } @@ -217,28 +230,28 @@ class ETMPerfDataReader { etm_decoder_->RegisterCallback( [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; } @@ -246,6 +259,44 @@ class ETMPerfDataReader { 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; @@ -294,24 +345,82 @@ class ETMPerfDataReader { } } - const std::string filename_; - bool exclude_perf_; ETMDumpOption etm_dump_option_; - BinaryFilter binary_filter_; - AutoFDOBinaryCallback autofdo_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. + // Store etm branch list data. std::unordered_map<Dso*, ETMBinary> etm_binary_map_; }; +class LBRPerfDataReader : public PerfDataReader { + public: + 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; + 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; + } + } + return true; + } + + bool PostProcess() override { 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; + } + + 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: @@ -660,34 +769,63 @@ 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_) { - ETMPerfDataReader 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() { ETMBranchListMerger branch_list_merger; - auto callback = [&](const BinaryKey& key, ETMBinary& binary) { + auto etm_callback = [&](const BinaryKey& key, ETMBinary& binary) { branch_list_merger.AddETMBinary(key, binary); }; - for (const auto& input_filename : input_filenames_) { - ETMPerfDataReader 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; } 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..758f89b5 100644 --- a/simpleperf/cmd_inject_test.cpp +++ b/simpleperf/cmd_inject_test.cpp @@ -224,3 +224,10 @@ 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 perf_data = GetTestData("perf_lbr.data"); + TemporaryFile tmpfile; + close(tmpfile.release()); + ASSERT_TRUE(RunInjectCmd({"-i", perf_data, "-o", tmpfile.path})); +} 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/perf_lbr.data b/simpleperf/testdata/perf_lbr.data Binary files differnew file mode 100644 index 00000000..81fafe62 --- /dev/null +++ b/simpleperf/testdata/perf_lbr.data |