392 lines
11 KiB
C++
392 lines
11 KiB
C++
/*
|
|
* 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);
|
|
}
|