You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
392 lines
11 KiB
392 lines
11 KiB
/* |
|
* 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 <memory> |
|
#include <utility> |
|
|
|
#include <android-base/logging.h> |
|
#include <android-base/file.h> |
|
|
|
#include "dso.h" |
|
#include "event_attr.h" |
|
#include "record_file.h" |
|
#include "thread_tree.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 Event { |
|
const char* name; |
|
}; |
|
|
|
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; |
|
Mapping* mapping; |
|
}; |
|
|
|
struct CallChainEntry { |
|
uint64_t ip; |
|
SymbolEntry symbol; |
|
}; |
|
|
|
struct CallChain { |
|
uint32_t nr; |
|
CallChainEntry* entries; |
|
}; |
|
|
|
// 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; |
|
|
|
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* GetBuildIdForPath(ReportLib* report_lib, const char* path) EXPORT; |
|
} |
|
|
|
struct EventAttrWithName { |
|
perf_event_attr attr; |
|
std::string name; |
|
}; |
|
|
|
enum { |
|
UPDATE_FLAG_OF_SAMPLE = 1 << 0, |
|
UPDATE_FLAG_OF_EVENT = 1 << 1, |
|
UPDATE_FLAG_OF_SYMBOL = 1 << 2, |
|
UPDATE_FLAG_OF_CALLCHAIN = 1 << 3, |
|
}; |
|
|
|
class ReportLib { |
|
public: |
|
ReportLib() |
|
: log_severity_( |
|
new android::base::ScopedLogSeverity(android::base::INFO)), |
|
record_filename_("perf.data"), |
|
current_thread_(nullptr), |
|
update_flag_(0) |
|
{} |
|
|
|
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(); } |
|
|
|
Sample* GetNextSample(); |
|
Event* GetEventOfCurrentSample(); |
|
SymbolEntry* GetSymbolOfCurrentSample(); |
|
CallChain* GetCallChainOfCurrentSample(); |
|
|
|
const char* GetBuildIdForPath(const char* path); |
|
|
|
private: |
|
Sample* GetCurrentSample(); |
|
bool OpenRecordFileIfNecessary(); |
|
Mapping* AddMapping(const MapEntry& map); |
|
|
|
std::unique_ptr<android::base::ScopedLogSeverity> log_severity_; |
|
std::string record_filename_; |
|
std::unique_ptr<RecordFileReader> record_file_reader_; |
|
ThreadTree thread_tree_; |
|
std::unique_ptr<SampleRecord> current_record_; |
|
const ThreadEntry* current_thread_; |
|
Sample current_sample_; |
|
Event current_event_; |
|
SymbolEntry current_symbol_; |
|
CallChain current_callchain_; |
|
std::vector<std::unique_ptr<Mapping>> current_mappings_; |
|
std::vector<CallChainEntry> callchain_entries_; |
|
std::string build_id_string_; |
|
int update_flag_; |
|
std::vector<EventAttrWithName> event_attrs_; |
|
}; |
|
|
|
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_); |
|
} |
|
return true; |
|
} |
|
|
|
Sample* ReportLib::GetNextSample() { |
|
if (!OpenRecordFileIfNecessary()) { |
|
return nullptr; |
|
} |
|
while (true) { |
|
std::unique_ptr<Record> record; |
|
if (!record_file_reader_->ReadRecord(record)) { |
|
return nullptr; |
|
} |
|
if (record == nullptr) { |
|
return nullptr; |
|
} |
|
thread_tree_.Update(*record); |
|
if (record->type() == PERF_RECORD_SAMPLE) { |
|
current_record_.reset(static_cast<SampleRecord*>(record.release())); |
|
break; |
|
} |
|
} |
|
update_flag_ = 0; |
|
current_mappings_.clear(); |
|
return GetCurrentSample(); |
|
} |
|
|
|
Sample* ReportLib::GetCurrentSample() { |
|
if (!(update_flag_ & UPDATE_FLAG_OF_SAMPLE)) { |
|
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; |
|
current_sample_.period = r.period_data.period; |
|
update_flag_ |= UPDATE_FLAG_OF_SAMPLE; |
|
} |
|
return ¤t_sample_; |
|
} |
|
|
|
Event* ReportLib::GetEventOfCurrentSample() { |
|
if (!(update_flag_ & UPDATE_FLAG_OF_EVENT)) { |
|
if (event_attrs_.empty()) { |
|
std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection(); |
|
for (const auto& attr_with_id : attrs) { |
|
EventAttrWithName attr; |
|
attr.attr = *attr_with_id.attr; |
|
attr.name = GetEventNameByAttr(attr.attr); |
|
event_attrs_.push_back(attr); |
|
} |
|
} |
|
size_t attr_index = |
|
record_file_reader_->GetAttrIndexOfRecord(current_record_.get()); |
|
current_event_.name = event_attrs_[attr_index].name.c_str(); |
|
update_flag_ |= UPDATE_FLAG_OF_EVENT; |
|
} |
|
return ¤t_event_; |
|
} |
|
|
|
SymbolEntry* ReportLib::GetSymbolOfCurrentSample() { |
|
if (!(update_flag_ & UPDATE_FLAG_OF_SYMBOL)) { |
|
SampleRecord& r = *current_record_; |
|
const MapEntry* map = |
|
thread_tree_.FindMap(current_thread_, r.ip_data.ip, r.InKernel()); |
|
uint64_t vaddr_in_file; |
|
const Symbol* symbol = |
|
thread_tree_.FindSymbol(map, r.ip_data.ip, &vaddr_in_file); |
|
current_symbol_.dso_name = map->dso->Path().c_str(); |
|
current_symbol_.vaddr_in_file = vaddr_in_file; |
|
current_symbol_.symbol_name = symbol->DemangledName(); |
|
current_symbol_.symbol_addr = symbol->addr; |
|
current_symbol_.mapping = AddMapping(*map); |
|
update_flag_ |= UPDATE_FLAG_OF_SYMBOL; |
|
} |
|
return ¤t_symbol_; |
|
} |
|
|
|
CallChain* ReportLib::GetCallChainOfCurrentSample() { |
|
if (!(update_flag_ & UPDATE_FLAG_OF_CALLCHAIN)) { |
|
SampleRecord& r = *current_record_; |
|
callchain_entries_.clear(); |
|
|
|
if (r.sample_type & PERF_SAMPLE_CALLCHAIN) { |
|
bool first_ip = true; |
|
bool in_kernel = r.InKernel(); |
|
for (uint64_t i = 0; i < r.callchain_data.ip_nr; ++i) { |
|
uint64_t ip = r.callchain_data.ips[i]; |
|
if (ip >= PERF_CONTEXT_MAX) { |
|
switch (ip) { |
|
case PERF_CONTEXT_KERNEL: |
|
in_kernel = true; |
|
break; |
|
case PERF_CONTEXT_USER: |
|
in_kernel = false; |
|
break; |
|
default: |
|
LOG(DEBUG) << "Unexpected perf_context in callchain: " << std::hex |
|
<< ip; |
|
} |
|
} else { |
|
if (first_ip) { |
|
first_ip = false; |
|
// Remove duplication with sample ip. |
|
if (ip == r.ip_data.ip) { |
|
continue; |
|
} |
|
} |
|
const MapEntry* map = |
|
thread_tree_.FindMap(current_thread_, ip, in_kernel); |
|
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.mapping = AddMapping(*map); |
|
callchain_entries_.push_back(entry); |
|
} |
|
} |
|
} |
|
current_callchain_.nr = callchain_entries_.size(); |
|
current_callchain_.entries = callchain_entries_.data(); |
|
update_flag_ |= UPDATE_FLAG_OF_CALLCHAIN; |
|
} |
|
return ¤t_callchain_; |
|
} |
|
|
|
Mapping* ReportLib::AddMapping(const MapEntry& map) { |
|
current_mappings_.emplace_back(std::unique_ptr<Mapping>(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(); |
|
} |
|
|
|
// 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(); |
|
} |
|
|
|
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* GetBuildIdForPath(ReportLib* report_lib, const char* path) { |
|
return report_lib->GetBuildIdForPath(path); |
|
}
|
|
|