diff options
author | Yabin Cui <yabinc@google.com> | 2024-02-28 19:56:49 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-02-28 19:56:49 +0000 |
commit | e48ae55565ce54973ab488496ea78e4ad38363ee (patch) | |
tree | af57c9c6c362c36188131564222717ee3f29839e /simpleperf | |
parent | e3ff5614104ce2717da117580bad1d743c542add (diff) | |
parent | cd5d5348b183751cd736b20cac92a4de613bf3a2 (diff) | |
download | extras-e48ae55565ce54973ab488496ea78e4ad38363ee.tar.gz |
Merge changes I2b3cf4b5,I6a8a5b40 into main
* changes:
simpleperf: support removing methods based on method name regex
simpleperf: refactor CallChainReportBuilder
Diffstat (limited to 'simpleperf')
-rw-r--r-- | simpleperf/RecordFilter.cpp | 9 | ||||
-rw-r--r-- | simpleperf/RegEx.cpp | 9 | ||||
-rw-r--r-- | simpleperf/RegEx.h | 3 | ||||
-rw-r--r-- | simpleperf/report_lib_interface.cpp | 56 | ||||
-rw-r--r-- | simpleperf/report_utils.cpp | 290 | ||||
-rw-r--r-- | simpleperf/report_utils.h | 32 | ||||
-rw-r--r-- | simpleperf/report_utils_test.cpp | 32 |
7 files changed, 294 insertions, 137 deletions
diff --git a/simpleperf/RecordFilter.cpp b/simpleperf/RecordFilter.cpp index 1c8f44e9..d6d25154 100644 --- a/simpleperf/RecordFilter.cpp +++ b/simpleperf/RecordFilter.cpp @@ -79,15 +79,6 @@ class TidFilter : public RecordFilterCondition { std::set<pid_t> exclude_tids_; }; -static bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs) { - for (auto& reg : regs) { - if (reg->Search(s)) { - return true; - } - } - return false; -} - class ProcessNameFilter : public RecordFilterCondition { public: ProcessNameFilter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {} diff --git a/simpleperf/RegEx.cpp b/simpleperf/RegEx.cpp index 97bb45bb..6de8440a 100644 --- a/simpleperf/RegEx.cpp +++ b/simpleperf/RegEx.cpp @@ -77,4 +77,13 @@ std::unique_ptr<RegEx> RegEx::Create(std::string_view pattern) { } } +bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs) { + for (auto& reg : regs) { + if (reg->Search(s)) { + return true; + } + } + return false; +} + } // namespace simpleperf diff --git a/simpleperf/RegEx.h b/simpleperf/RegEx.h index c1e4f511..6e75afff 100644 --- a/simpleperf/RegEx.h +++ b/simpleperf/RegEx.h @@ -20,6 +20,7 @@ #include <optional> #include <string> #include <string_view> +#include <vector> namespace simpleperf { @@ -51,4 +52,6 @@ class RegEx { std::string pattern_; }; +bool SearchInRegs(std::string_view s, const std::vector<std::unique_ptr<RegEx>>& regs); + } // namespace simpleperf diff --git a/simpleperf/report_lib_interface.cpp b/simpleperf/report_lib_interface.cpp index 09a39f0a..e59dd3a3 100644 --- a/simpleperf/report_lib_interface.cpp +++ b/simpleperf/report_lib_interface.cpp @@ -193,6 +193,9 @@ class ReportLib { bool remove_art_frame = !show; callchain_report_builder_.SetRemoveArtFrame(remove_art_frame); } + bool RemoveMethod(const char* method_name_regex) { + return callchain_report_builder_.RemoveMethod(method_name_regex); + } void MergeJavaMethods(bool merge) { callchain_report_builder_.SetConvertJITFrame(merge); } bool AddProguardMappingFile(const char* mapping_file) { return callchain_report_builder_.AddProguardMappingFile(mapping_file); @@ -212,11 +215,12 @@ class ReportLib { FeatureSection* GetFeatureSection(const char* feature_name); private: + std::unique_ptr<SampleRecord> GetNextSampleRecord(); void ProcessSampleRecord(std::unique_ptr<Record> r); void ProcessSwitchRecord(std::unique_ptr<Record> r); void AddSampleRecordToQueue(SampleRecord* r); - void SetCurrentSample(const SampleRecord& r); - const EventInfo* FindEventOfCurrentSample(); + bool SetCurrentSample(std::unique_ptr<SampleRecord> sample_record); + const EventInfo& FindEvent(const SampleRecord& r); void CreateEvents(); bool OpenRecordFileIfNecessary(); @@ -357,9 +361,20 @@ Sample* ReportLib::GetNextSample() { if (!OpenRecordFileIfNecessary()) { return nullptr; } - if (!sample_record_queue_.empty()) { - sample_record_queue_.pop(); + + while (true) { + std::unique_ptr<SampleRecord> r = GetNextSampleRecord(); + if (!r) { + break; + } + if (SetCurrentSample(std::move(r))) { + return ¤t_sample_; + } } + return nullptr; +} + +std::unique_ptr<SampleRecord> ReportLib::GetNextSampleRecord() { while (sample_record_queue_.empty()) { std::unique_ptr<Record> record; if (!record_file_reader_->ReadRecord(record) || record == nullptr) { @@ -380,8 +395,9 @@ Sample* ReportLib::GetNextSample() { } } } - SetCurrentSample(*sample_record_queue_.front()); - return ¤t_sample_; + std::unique_ptr<SampleRecord> result = std::move(sample_record_queue_.front()); + sample_record_queue_.pop(); + return result; } void ReportLib::ProcessSampleRecord(std::unique_ptr<Record> r) { @@ -453,7 +469,8 @@ void ReportLib::AddSampleRecordToQueue(SampleRecord* r) { } } -void ReportLib::SetCurrentSample(const SampleRecord& r) { +bool ReportLib::SetCurrentSample(std::unique_ptr<SampleRecord> sample_record) { + const SampleRecord& r = *sample_record; current_mappings_.clear(); callchain_entries_.clear(); current_sample_.ip = r.ip_data.ip; @@ -471,6 +488,10 @@ void ReportLib::SetCurrentSample(const SampleRecord& r) { std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count); std::vector<CallChainReportEntry> report_entries = callchain_report_builder_.Build(current_thread_, ips, kernel_ip_count); + if (report_entries.empty()) { + // Skip samples with callchain fully removed by RemoveMethod(). + return false; + } for (const auto& report_entry : report_entries) { callchain_entries_.resize(callchain_entries_.size() + 1); @@ -491,29 +512,29 @@ void ReportLib::SetCurrentSample(const SampleRecord& r) { current_symbol_ = &(callchain_entries_[0].symbol); current_callchain_.nr = callchain_entries_.size() - 1; current_callchain_.entries = &callchain_entries_[1]; - const EventInfo* event = FindEventOfCurrentSample(); - current_event_.name = event->name.c_str(); - current_event_.tracing_data_format = event->tracing_info.data_format; + const EventInfo& event = FindEvent(r); + current_event_.name = event.name.c_str(); + current_event_.tracing_data_format = event.tracing_info.data_format; if (current_event_.tracing_data_format.size > 0u && (r.sample_type & PERF_SAMPLE_RAW)) { CHECK_GE(r.raw_data.size, current_event_.tracing_data_format.size); current_tracing_data_ = r.raw_data.data; } else { current_tracing_data_ = nullptr; } + return true; } -const EventInfo* ReportLib::FindEventOfCurrentSample() { +const EventInfo& ReportLib::FindEvent(const SampleRecord& r) { if (events_.empty()) { CreateEvents(); } if (trace_offcpu_.mode == TraceOffCpuMode::MIXED_ON_OFF_CPU) { // To mix on-cpu and off-cpu samples, pretend they are from the same event type. // Otherwise, some report scripts may split them. - return &events_[0]; + return events_[0]; } - SampleRecord* r = sample_record_queue_.front().get(); - size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(r); - return &events_[attr_index]; + size_t attr_index = record_file_reader_->GetAttrIndexOfRecord(&r); + return events_[attr_index]; } void ReportLib::CreateEvents() { @@ -611,6 +632,7 @@ bool SetRecordFile(ReportLib* report_lib, const char* record_file) EXPORT; bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) EXPORT; void ShowIpForUnknownSymbol(ReportLib* report_lib) EXPORT; void ShowArtFrames(ReportLib* report_lib, bool show) EXPORT; +bool RemoveMethod(ReportLib* report_lib, const char* method_name_regex) EXPORT; void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT; bool AddProguardMappingFile(ReportLib* report_lib, const char* mapping_file) EXPORT; const char* GetSupportedTraceOffCpuModes(ReportLib* report_lib) EXPORT; @@ -658,6 +680,10 @@ void ShowArtFrames(ReportLib* report_lib, bool show) { return report_lib->ShowArtFrames(show); } +bool RemoveMethod(ReportLib* report_lib, const char* method_name_regex) { + return report_lib->RemoveMethod(method_name_regex); +} + void MergeJavaMethods(ReportLib* report_lib, bool merge) { return report_lib->MergeJavaMethods(merge); } diff --git a/simpleperf/report_utils.cpp b/simpleperf/report_utils.cpp index ecbc6aa8..5c3ce6f9 100644 --- a/simpleperf/report_utils.cpp +++ b/simpleperf/report_utils.cpp @@ -23,6 +23,7 @@ #include <android-base/strings.h> #include "JITDebugReader.h" +#include "RegEx.h" #include "utils.h" namespace simpleperf { @@ -189,6 +190,160 @@ static bool IsArtEntry(const CallChainReportEntry& entry, bool* is_jni_trampolin return false; }; +CallChainReportModifier::~CallChainReportModifier() {} + +// Remove art frames. +class ArtFrameRemover : public CallChainReportModifier { + public: + void Modify(std::vector<CallChainReportEntry>& callchain) override { + auto it = + std::remove_if(callchain.begin(), callchain.end(), [](const CallChainReportEntry& entry) { + return entry.execution_type == CallChainExecutionType::ART_METHOD; + }); + callchain.erase(it, callchain.end()); + } +}; + +// Convert JIT methods to their corresponding interpreted Java methods. +class JITFrameConverter : public CallChainReportModifier { + public: + JITFrameConverter(const ThreadTree& thread_tree) : thread_tree_(thread_tree) {} + + void Modify(std::vector<CallChainReportEntry>& callchain) override { + CollectJavaMethods(); + for (size_t i = 0; i < callchain.size();) { + auto& entry = callchain[i]; + if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) { + // This is a JIT java method, merge it with the interpreted java method having the same + // name if possible. Otherwise, merge it with other JIT java methods having the same name + // by assigning a common dso_name. + if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName())); + it != java_method_map_.end()) { + entry.dso = it->second.dso; + entry.symbol = it->second.symbol; + // Not enough info to map an offset in a JIT method to an offset in a dex file. So just + // use the symbol_addr. + entry.vaddr_in_file = entry.symbol->addr; + + // ART may call from an interpreted Java method into its corresponding JIT method. To + // avoid showing the method calling itself, remove the JIT frame. + if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso && + callchain[i + 1].symbol == entry.symbol) { + callchain.erase(callchain.begin() + i); + continue; + } + + } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) { + // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name. + entry.dso_name = "[JIT cache]"; + } + } + i++; + } + } + + private: + struct JavaMethod { + Dso* dso; + const Symbol* symbol; + JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {} + }; + + void CollectJavaMethods() { + if (!java_method_initialized_) { + java_method_initialized_ = true; + for (Dso* dso : thread_tree_.GetAllDsos()) { + if (dso->type() == DSO_DEX_FILE) { + dso->LoadSymbols(); + for (auto& symbol : dso->GetSymbols()) { + java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol)); + } + } + } + } + } + + const ThreadTree& thread_tree_; + bool java_method_initialized_ = false; + std::unordered_map<std::string, JavaMethod> java_method_map_; +}; + +// Use proguard mapping.txt to de-obfuscate minified symbols. +class JavaMethodDeobfuscater : public CallChainReportModifier { + public: + JavaMethodDeobfuscater(bool remove_r8_synthesized_frame) + : remove_r8_synthesized_frame_(remove_r8_synthesized_frame) {} + + bool AddProguardMappingFile(std::string_view mapping_file) { + return retrace_.AddProguardMappingFile(mapping_file); + } + + void Modify(std::vector<CallChainReportEntry>& callchain) override { + for (size_t i = 0; i < callchain.size();) { + auto& entry = callchain[i]; + if (!IsJavaEntry(entry)) { + i++; + continue; + } + std::string_view name = entry.symbol->FunctionName(); + std::string original_name; + bool synthesized; + if (retrace_.DeObfuscateJavaMethods(name, &original_name, &synthesized)) { + if (synthesized && remove_r8_synthesized_frame_) { + callchain.erase(callchain.begin() + i); + continue; + } + entry.symbol->SetDemangledName(original_name); + } + i++; + } + } + + private: + bool IsJavaEntry(const CallChainReportEntry& entry) { + static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"}; + if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD || + entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) { + return true; + } + if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) { + const std::string& path = entry.dso->Path(); + for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) { + if (android::base::EndsWith(path, suffix)) { + return true; + } + } + } + return false; + } + + const bool remove_r8_synthesized_frame_; + ProguardMappingRetrace retrace_; +}; + +// Use regex to filter method names. +class MethodNameFilter : public CallChainReportModifier { + public: + bool RemoveMethod(std::string_view method_name_regex) { + if (auto regex = RegEx::Create(method_name_regex); regex != nullptr) { + exclude_names_.emplace_back(std::move(regex)); + return true; + } + return false; + } + + void Modify(std::vector<CallChainReportEntry>& callchain) override { + auto it = std::remove_if(callchain.begin(), callchain.end(), + [this](const CallChainReportEntry& entry) { + return SearchInRegs(entry.symbol->DemangledName(), exclude_names_); + }); + callchain.erase(it, callchain.end()); + } + + private: + std::vector<std::unique_ptr<RegEx>> exclude_names_; +}; + CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree) : thread_tree_(thread_tree) { const char* env_name = "REMOVE_R8_SYNTHESIZED_FRAME"; @@ -202,13 +357,39 @@ CallChainReportBuilder::CallChainReportBuilder(ThreadTree& thread_tree) remove_r8_synthesized_frame_ = true; } } + SetRemoveArtFrame(true); + SetConvertJITFrame(true); +} + +void CallChainReportBuilder::SetRemoveArtFrame(bool enable) { + if (enable) { + art_frame_remover_.reset(new ArtFrameRemover); + } else { + art_frame_remover_.reset(nullptr); + } +} + +void CallChainReportBuilder::SetConvertJITFrame(bool enable) { + if (enable) { + jit_frame_converter_.reset(new JITFrameConverter(thread_tree_)); + } else { + jit_frame_converter_.reset(nullptr); + } } bool CallChainReportBuilder::AddProguardMappingFile(std::string_view mapping_file) { - if (!retrace_) { - retrace_.reset(new ProguardMappingRetrace); + if (!java_method_deobfuscater_) { + java_method_deobfuscater_.reset(new JavaMethodDeobfuscater(remove_r8_synthesized_frame_)); + } + return static_cast<JavaMethodDeobfuscater&>(*java_method_deobfuscater_) + .AddProguardMappingFile(mapping_file); +} + +bool CallChainReportBuilder::RemoveMethod(std::string_view method_name_regex) { + if (!method_name_filter_) { + method_name_filter_.reset(new MethodNameFilter); } - return retrace_->AddProguardMappingFile(mapping_file); + return static_cast<MethodNameFilter&>(*method_name_filter_).RemoveMethod(method_name_regex); } std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntry* thread, @@ -239,17 +420,17 @@ std::vector<CallChainReportEntry> CallChainReportBuilder::Build(const ThreadEntr entry.execution_type = execution_type; } MarkArtFrame(result); - if (remove_art_frame_) { - auto it = std::remove_if(result.begin(), result.end(), [](const CallChainReportEntry& entry) { - return entry.execution_type == CallChainExecutionType::ART_METHOD; - }); - result.erase(it, result.end()); + if (art_frame_remover_) { + art_frame_remover_->Modify(result); + } + if (jit_frame_converter_) { + jit_frame_converter_->Modify(result); } - if (convert_jit_frame_) { - ConvertJITFrame(result); + if (java_method_deobfuscater_) { + java_method_deobfuscater_->Modify(result); } - if (retrace_) { - DeObfuscateJavaMethods(result); + if (method_name_filter_) { + method_name_filter_->Modify(result); } return result; } @@ -292,91 +473,6 @@ void CallChainReportBuilder::MarkArtFrame(std::vector<CallChainReportEntry>& cal } } -void CallChainReportBuilder::ConvertJITFrame(std::vector<CallChainReportEntry>& callchain) { - CollectJavaMethods(); - for (size_t i = 0; i < callchain.size();) { - auto& entry = callchain[i]; - if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD) { - // This is a JIT java method, merge it with the interpreted java method having the same - // name if possible. Otherwise, merge it with other JIT java methods having the same name - // by assigning a common dso_name. - if (auto it = java_method_map_.find(std::string(entry.symbol->FunctionName())); - it != java_method_map_.end()) { - entry.dso = it->second.dso; - entry.symbol = it->second.symbol; - // Not enough info to map an offset in a JIT method to an offset in a dex file. So just - // use the symbol_addr. - entry.vaddr_in_file = entry.symbol->addr; - - // ART may call from an interpreted Java method into its corresponding JIT method. To - // avoid showing the method calling itself, remove the JIT frame. - if (i + 1 < callchain.size() && callchain[i + 1].dso == entry.dso && - callchain[i + 1].symbol == entry.symbol) { - callchain.erase(callchain.begin() + i); - continue; - } - - } else if (!JITDebugReader::IsPathInJITSymFile(entry.dso->Path())) { - // Old JITSymFiles use names like "TemporaryFile-XXXXXX". So give them a better name. - entry.dso_name = "[JIT cache]"; - } - } - i++; - } -} - -void CallChainReportBuilder::CollectJavaMethods() { - if (!java_method_initialized_) { - java_method_initialized_ = true; - for (Dso* dso : thread_tree_.GetAllDsos()) { - if (dso->type() == DSO_DEX_FILE) { - dso->LoadSymbols(); - for (auto& symbol : dso->GetSymbols()) { - java_method_map_.emplace(symbol.Name(), JavaMethod(dso, &symbol)); - } - } - } - } -} - -static bool IsJavaEntry(const CallChainReportEntry& entry) { - static const char* COMPILED_JAVA_FILE_SUFFIXES[] = {".odex", ".oat", ".dex"}; - if (entry.execution_type == CallChainExecutionType::JIT_JVM_METHOD || - entry.execution_type == CallChainExecutionType::INTERPRETED_JVM_METHOD) { - return true; - } - if (entry.execution_type == CallChainExecutionType::NATIVE_METHOD) { - const std::string& path = entry.dso->Path(); - for (const char* suffix : COMPILED_JAVA_FILE_SUFFIXES) { - if (android::base::EndsWith(path, suffix)) { - return true; - } - } - } - return false; -} - -void CallChainReportBuilder::DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain) { - for (size_t i = 0; i < callchain.size();) { - auto& entry = callchain[i]; - if (!IsJavaEntry(entry)) { - i++; - continue; - } - std::string_view name = entry.symbol->FunctionName(); - std::string original_name; - bool synthesized; - if (retrace_->DeObfuscateJavaMethods(name, &original_name, &synthesized)) { - if (synthesized && remove_r8_synthesized_frame_) { - callchain.erase(callchain.begin() + i); - continue; - } - entry.symbol->SetDemangledName(original_name); - } - i++; - } -} - bool ThreadReportBuilder::AggregateThreads(const std::vector<std::string>& thread_name_regex) { size_t i = thread_regs_.size(); thread_regs_.resize(i + thread_name_regex.size()); diff --git a/simpleperf/report_utils.h b/simpleperf/report_utils.h index fa42bba6..631e5fad 100644 --- a/simpleperf/report_utils.h +++ b/simpleperf/report_utils.h @@ -91,39 +91,39 @@ struct CallChainReportEntry { CallChainExecutionType execution_type = CallChainExecutionType::NATIVE_METHOD; }; +// a base class for modifying callchain reports +class CallChainReportModifier { + public: + virtual ~CallChainReportModifier(); + + virtual void Modify(std::vector<CallChainReportEntry>& callchain) = 0; +}; + class CallChainReportBuilder { public: CallChainReportBuilder(ThreadTree& thread_tree); // If true, remove interpreter frames both before and after a Java frame. // Default is true. - void SetRemoveArtFrame(bool enable) { remove_art_frame_ = enable; } + void SetRemoveArtFrame(bool enable); // If true, convert a JIT method into its corresponding interpreted Java method. So they can be // merged in reports like flamegraph. Default is true. - void SetConvertJITFrame(bool enable) { convert_jit_frame_ = enable; } + void SetConvertJITFrame(bool enable); // Add proguard mapping.txt to de-obfuscate minified symbols. bool AddProguardMappingFile(std::string_view mapping_file); + // Remove methods with name containing the given regular expression. + bool RemoveMethod(std::string_view method_name_regex); std::vector<CallChainReportEntry> Build(const ThreadEntry* thread, const std::vector<uint64_t>& ips, size_t kernel_ip_count); private: - struct JavaMethod { - Dso* dso; - const Symbol* symbol; - JavaMethod(Dso* dso, const Symbol* symbol) : dso(dso), symbol(symbol) {} - }; - void MarkArtFrame(std::vector<CallChainReportEntry>& callchain); - void ConvertJITFrame(std::vector<CallChainReportEntry>& callchain); - void CollectJavaMethods(); - void DeObfuscateJavaMethods(std::vector<CallChainReportEntry>& callchain); ThreadTree& thread_tree_; - bool remove_art_frame_ = true; bool remove_r8_synthesized_frame_ = false; - bool convert_jit_frame_ = true; - bool java_method_initialized_ = false; - std::unordered_map<std::string, JavaMethod> java_method_map_; - std::unique_ptr<ProguardMappingRetrace> retrace_; + std::unique_ptr<CallChainReportModifier> art_frame_remover_; + std::unique_ptr<CallChainReportModifier> jit_frame_converter_; + std::unique_ptr<CallChainReportModifier> java_method_deobfuscater_; + std::unique_ptr<CallChainReportModifier> method_name_filter_; }; struct ThreadReport { diff --git a/simpleperf/report_utils_test.cpp b/simpleperf/report_utils_test.cpp index 55771218..ad4b9df3 100644 --- a/simpleperf/report_utils_test.cpp +++ b/simpleperf/report_utils_test.cpp @@ -540,6 +540,38 @@ TEST_F(CallChainReportBuilderTest, convert_jit_frame_for_jit_method_with_signatu ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD); } +TEST_F(CallChainReportBuilderTest, remove_method_name) { + // Test excluding method names. + CallChainReportBuilder builder(thread_tree); + builder.SetRemoveArtFrame(false); + builder.RemoveMethod("art_"); + std::vector<CallChainReportEntry> entries = builder.Build(thread, fake_ips, 0); + ASSERT_EQ(entries.size(), 2); + ASSERT_EQ(entries[0].ip, 0x2000); + ASSERT_STREQ(entries[0].symbol->Name(), "java_method1"); + ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path); + ASSERT_EQ(entries[0].vaddr_in_file, 0); + ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD); + ASSERT_EQ(entries[1].ip, 0x3000); + ASSERT_STREQ(entries[1].symbol->Name(), "java_method2"); + ASSERT_EQ(entries[1].dso->Path(), fake_dex_file_path); + ASSERT_EQ(entries[1].vaddr_in_file, 0x100); + ASSERT_EQ(entries[1].execution_type, CallChainExecutionType::JIT_JVM_METHOD); + + builder.RemoveMethod("java_method2"); + entries = builder.Build(thread, fake_ips, 0); + ASSERT_EQ(entries.size(), 1); + ASSERT_EQ(entries[0].ip, 0x2000); + ASSERT_STREQ(entries[0].symbol->Name(), "java_method1"); + ASSERT_EQ(entries[0].dso->Path(), fake_dex_file_path); + ASSERT_EQ(entries[0].vaddr_in_file, 0); + ASSERT_EQ(entries[0].execution_type, CallChainExecutionType::INTERPRETED_JVM_METHOD); + + builder.RemoveMethod("java_method1"); + entries = builder.Build(thread, fake_ips, 0); + ASSERT_EQ(entries.size(), 0); +} + class ThreadReportBuilderTest : public testing::Test { protected: virtual void SetUp() { |