/* * Copyright (C) 2016 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include "dso.h" #include "event_attr.h" #include "event_type.h" #include "record_file.h" #include "thread_tree.h" #include "tracing.h" #include "utils.h" class ReportLib; extern "C" { #define EXPORT __attribute__((visibility("default"))) struct Sample { uint64_t ip; uint32_t pid; uint32_t tid; const char* thread_comm; uint64_t time; uint32_t in_kernel; uint32_t cpu; uint64_t period; }; struct TracingFieldFormat { const char* name; uint32_t offset; uint32_t elem_size; uint32_t elem_count; uint32_t is_signed; }; struct TracingDataFormat { uint32_t size; uint32_t field_count; TracingFieldFormat* fields; }; struct Event { const char* name; TracingDataFormat tracing_data_format; }; struct Mapping { uint64_t start; uint64_t end; uint64_t pgoff; }; struct SymbolEntry { const char* dso_name; uint64_t vaddr_in_file; const char* symbol_name; uint64_t symbol_addr; uint64_t symbol_len; Mapping* mapping; }; struct CallChainEntry { uint64_t ip; SymbolEntry symbol; }; struct CallChain { uint32_t nr; CallChainEntry* entries; }; struct FeatureSection { const char* data; uint32_t data_size; }; // Create a new instance, // pass the instance to the other functions below. ReportLib* CreateReportLib() EXPORT; void DestroyReportLib(ReportLib* report_lib) EXPORT; // Set log severity, different levels are: // verbose, debug, info, warning, error, fatal. bool SetLogSeverity(ReportLib* report_lib, const char* log_level) EXPORT; bool SetSymfs(ReportLib* report_lib, const char* symfs_dir) EXPORT; 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; void MergeJavaMethods(ReportLib* report_lib, bool merge) EXPORT; Sample* GetNextSample(ReportLib* report_lib) EXPORT; Event* GetEventOfCurrentSample(ReportLib* report_lib) EXPORT; SymbolEntry* GetSymbolOfCurrentSample(ReportLib* report_lib) EXPORT; CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) EXPORT; const char* GetTracingDataOfCurrentSample(ReportLib* report_lib) EXPORT; const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT; FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) EXPORT; } struct EventInfo { perf_event_attr attr; std::string name; struct TracingInfo { TracingDataFormat data_format; std::vector field_names; std::vector fields; } tracing_info; }; class ReportLib { public: ReportLib() : log_severity_( new android::base::ScopedLogSeverity(android::base::INFO)), record_filename_("perf.data"), current_thread_(nullptr), trace_offcpu_(false), show_art_frames_(false) { } bool SetLogSeverity(const char* log_level); bool SetSymfs(const char* symfs_dir) { return Dso::SetSymFsDir(symfs_dir); } bool SetRecordFile(const char* record_file) { record_filename_ = record_file; return true; } bool SetKallsymsFile(const char* kallsyms_file); void ShowIpForUnknownSymbol() { thread_tree_.ShowIpForUnknownSymbol(); } void ShowArtFrames(bool show) { show_art_frames_ = show; } void MergeJavaMethods(bool merge) { merge_java_methods_ = merge; } Sample* GetNextSample(); Event* GetEventOfCurrentSample() { return ¤t_event_; } SymbolEntry* GetSymbolOfCurrentSample() { return current_symbol_; } CallChain* GetCallChainOfCurrentSample() { return ¤t_callchain_; } const char* GetTracingDataOfCurrentSample() { return current_tracing_data_; } const char* GetBuildIdForPath(const char* path); FeatureSection* GetFeatureSection(const char* feature_name); private: void SetCurrentSample(); const EventInfo* FindEventOfCurrentSample(); void CreateEvents(); bool OpenRecordFileIfNecessary(); Mapping* AddMapping(const MapEntry& map); std::unique_ptr log_severity_; std::string record_filename_; std::unique_ptr record_file_reader_; ThreadTree thread_tree_; std::unique_ptr current_record_; const ThreadEntry* current_thread_; Sample current_sample_; Event current_event_; SymbolEntry* current_symbol_; CallChain current_callchain_; const char* current_tracing_data_; std::vector> current_mappings_; std::vector callchain_entries_; std::string build_id_string_; std::vector events_; bool trace_offcpu_; std::unordered_map> next_sample_cache_; FeatureSection feature_section_; std::vector feature_section_data_; bool show_art_frames_; bool merge_java_methods_ = true; // Map from a java method name to it's dex file, start_addr and len. std::unordered_map> java_methods_; std::unique_ptr tracing_; }; bool ReportLib::SetLogSeverity(const char* log_level) { android::base::LogSeverity severity; if (!GetLogSeverity(log_level, &severity)) { LOG(ERROR) << "Unknown log severity: " << log_level; return false; } log_severity_ = nullptr; log_severity_.reset(new android::base::ScopedLogSeverity(severity)); return true; } bool ReportLib::SetKallsymsFile(const char* kallsyms_file) { std::string kallsyms; if (!android::base::ReadFileToString(kallsyms_file, &kallsyms)) { LOG(WARNING) << "Failed to read in kallsyms file from " << kallsyms_file; return false; } Dso::SetKallsyms(std::move(kallsyms)); return true; } bool ReportLib::OpenRecordFileIfNecessary() { if (record_file_reader_ == nullptr) { record_file_reader_ = RecordFileReader::CreateInstance(record_filename_); if (record_file_reader_ == nullptr) { return false; } record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_); auto& meta_info = record_file_reader_->GetMetaInfoFeature(); if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) { trace_offcpu_ = it->second == "true"; } if (merge_java_methods_) { for (Dso* dso : thread_tree_.GetAllDsos()) { if (dso->type() == DSO_DEX_FILE) { for (auto& symbol : dso->GetSymbols()) { java_methods_[symbol.Name()] = std::make_tuple(dso, symbol.addr, symbol.len); } } } } } return true; } Sample* ReportLib::GetNextSample() { if (!OpenRecordFileIfNecessary()) { return nullptr; } while (true) { std::unique_ptr record; if (!record_file_reader_->ReadRecord(record)) { return nullptr; } if (record == nullptr) { return nullptr; } thread_tree_.Update(*record); if (record->type() == PERF_RECORD_SAMPLE) { if (trace_offcpu_) { SampleRecord* r = static_cast(record.release()); auto it = next_sample_cache_.find(r->tid_data.tid); if (it == next_sample_cache_.end()) { next_sample_cache_[r->tid_data.tid].reset(r); continue; } else { record.reset(it->second.release()); it->second.reset(r); } } current_record_.reset(static_cast(record.release())); break; } else if (record->type() == PERF_RECORD_TRACING_DATA || record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) { const auto& r = *static_cast(record.get()); tracing_.reset(new Tracing(std::vector(r.data, r.data + r.data_size))); } } SetCurrentSample(); return ¤t_sample_; } void ReportLib::SetCurrentSample() { current_mappings_.clear(); callchain_entries_.clear(); SampleRecord& r = *current_record_; current_sample_.ip = r.ip_data.ip; current_sample_.pid = r.tid_data.pid; current_sample_.tid = r.tid_data.tid; current_thread_ = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); current_sample_.thread_comm = current_thread_->comm; current_sample_.time = r.time_data.time; current_sample_.in_kernel = r.InKernel(); current_sample_.cpu = r.cpu_data.cpu; if (trace_offcpu_) { uint64_t next_time = std::max(next_sample_cache_[r.tid_data.tid]->time_data.time, r.time_data.time + 1); current_sample_.period = next_time - r.time_data.time; } else { current_sample_.period = r.period_data.period; } size_t kernel_ip_count; std::vector ips = r.GetCallChain(&kernel_ip_count); std::vector> ip_maps; bool near_java_method = false; auto is_map_for_interpreter = [](const MapEntry* map) { return android::base::EndsWith(map->dso->Path(), "/libart.so") || android::base::EndsWith(map->dso->Path(), "/libartd.so"); }; for (size_t i = 0; i < ips.size(); ++i) { const MapEntry* map = thread_tree_.FindMap(current_thread_, ips[i], i < kernel_ip_count); if (!show_art_frames_) { // Remove interpreter frames both before and after the Java frame. if (map->dso->IsForJavaMethod()) { near_java_method = true; while (!ip_maps.empty() && is_map_for_interpreter(ip_maps.back().second)) { ip_maps.pop_back(); } } else if (is_map_for_interpreter(map)){ if (near_java_method) { continue; } } else { near_java_method = false; } } ip_maps.push_back(std::make_pair(ips[i], map)); } for (auto& pair : ip_maps) { uint64_t ip = pair.first; const MapEntry* map = pair.second; uint64_t vaddr_in_file; const Symbol* symbol = thread_tree_.FindSymbol(map, ip, &vaddr_in_file); CallChainEntry entry; entry.ip = ip; entry.symbol.dso_name = map->dso->Path().c_str(); entry.symbol.vaddr_in_file = vaddr_in_file; entry.symbol.symbol_name = symbol->DemangledName(); entry.symbol.symbol_addr = symbol->addr; entry.symbol.symbol_len = symbol->len; entry.symbol.mapping = AddMapping(*map); if (merge_java_methods_ && map->dso->type() == DSO_ELF_FILE && map->dso->IsForJavaMethod()) { // This is a jitted java method, merge it with the interpreted java method having the same // name if possible. Otherwise, merge it with other jitted java methods having the same name // by assigning a common dso_name. if (auto it = java_methods_.find(entry.symbol.symbol_name); it != java_methods_.end()) { entry.symbol.dso_name = std::get<0>(it->second)->Path().c_str(); entry.symbol.symbol_addr = std::get<1>(it->second); entry.symbol.symbol_len = std::get<2>(it->second); // Not enough info to map an offset in a jitted method to an offset in a dex file. So just // use the symbol_addr. entry.symbol.vaddr_in_file = entry.symbol.symbol_addr; } else { entry.symbol.dso_name = "[JIT cache]"; } } callchain_entries_.push_back(entry); } current_sample_.ip = callchain_entries_[0].ip; 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; 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; } } const EventInfo* ReportLib::FindEventOfCurrentSample() { if (events_.empty()) { CreateEvents(); } size_t attr_index; if (trace_offcpu_) { // For trace-offcpu, we don't want to show event sched:sched_switch. attr_index = 0; } else { attr_index = record_file_reader_->GetAttrIndexOfRecord(current_record_.get()); } return &events_[attr_index]; } void ReportLib::CreateEvents() { std::vector attrs = record_file_reader_->AttrSection(); events_.resize(attrs.size()); for (size_t i = 0; i < attrs.size(); ++i) { events_[i].attr = *attrs[i].attr; events_[i].name = GetEventNameByAttr(events_[i].attr); EventInfo::TracingInfo& tracing_info = events_[i].tracing_info; if (events_[i].attr.type == PERF_TYPE_TRACEPOINT && tracing_) { TracingFormat format = tracing_->GetTracingFormatHavingId(events_[i].attr.config); tracing_info.field_names.resize(format.fields.size()); tracing_info.fields.resize(format.fields.size()); for (size_t i = 0; i < format.fields.size(); ++i) { tracing_info.field_names[i] = format.fields[i].name; TracingFieldFormat& field = tracing_info.fields[i]; field.name = tracing_info.field_names[i].c_str(); field.offset = format.fields[i].offset; field.elem_size = format.fields[i].elem_size; field.elem_count = format.fields[i].elem_count; field.is_signed = format.fields[i].is_signed; } if (tracing_info.fields.empty()) { tracing_info.data_format.size = 0; } else { TracingFieldFormat& field = tracing_info.fields.back(); tracing_info.data_format.size = field.offset + field.elem_size * field.elem_count; } tracing_info.data_format.field_count = tracing_info.fields.size(); tracing_info.data_format.fields = &tracing_info.fields[0]; } else { tracing_info.data_format.size = 0; tracing_info.data_format.field_count = 0; tracing_info.data_format.fields = nullptr; } } } Mapping* ReportLib::AddMapping(const MapEntry& map) { current_mappings_.emplace_back(std::unique_ptr(new Mapping)); Mapping* mapping = current_mappings_.back().get(); mapping->start = map.start_addr; mapping->end = map.start_addr + map.len; mapping->pgoff = map.pgoff; return mapping; } const char* ReportLib::GetBuildIdForPath(const char* path) { if (!OpenRecordFileIfNecessary()) { build_id_string_.clear(); return build_id_string_.c_str(); } BuildId build_id = Dso::FindExpectedBuildIdForPath(path); if (build_id.IsEmpty()) { build_id_string_.clear(); } else { build_id_string_ = build_id.ToString(); } return build_id_string_.c_str(); } FeatureSection* ReportLib::GetFeatureSection(const char* feature_name) { if (!OpenRecordFileIfNecessary()) { return nullptr; } int feature = PerfFileFormat::GetFeatureId(feature_name); if (feature == -1 || !record_file_reader_->ReadFeatureSection(feature, &feature_section_data_)) { return nullptr; } feature_section_.data = feature_section_data_.data(); feature_section_.data_size = feature_section_data_.size(); return &feature_section_; } // Exported methods working with a client created instance ReportLib* CreateReportLib() { return new ReportLib(); } void DestroyReportLib(ReportLib* report_lib) { delete report_lib; } bool SetLogSeverity(ReportLib* report_lib, const char* log_level) { return report_lib->SetLogSeverity(log_level); } bool SetSymfs(ReportLib* report_lib, const char* symfs_dir) { return report_lib->SetSymfs(symfs_dir); } bool SetRecordFile(ReportLib* report_lib, const char* record_file) { return report_lib->SetRecordFile(record_file); } void ShowIpForUnknownSymbol(ReportLib* report_lib) { return report_lib->ShowIpForUnknownSymbol(); } void ShowArtFrames(ReportLib* report_lib, bool show) { return report_lib->ShowArtFrames(show); } void MergeJavaMethods(ReportLib* report_lib, bool merge) { return report_lib->MergeJavaMethods(merge); } bool SetKallsymsFile(ReportLib* report_lib, const char* kallsyms_file) { return report_lib->SetKallsymsFile(kallsyms_file); } Sample* GetNextSample(ReportLib* report_lib) { return report_lib->GetNextSample(); } Event* GetEventOfCurrentSample(ReportLib* report_lib) { return report_lib->GetEventOfCurrentSample(); } SymbolEntry* GetSymbolOfCurrentSample(ReportLib* report_lib) { return report_lib->GetSymbolOfCurrentSample(); } CallChain* GetCallChainOfCurrentSample(ReportLib* report_lib) { return report_lib->GetCallChainOfCurrentSample(); } const char* GetTracingDataOfCurrentSample(ReportLib* report_lib) { return report_lib->GetTracingDataOfCurrentSample(); } const char* GetBuildIdForPath(ReportLib* report_lib, const char* path) { return report_lib->GetBuildIdForPath(path); } FeatureSection* GetFeatureSection(ReportLib* report_lib, const char* feature_name) { return report_lib->GetFeatureSection(feature_name); }