#include <vector>
#include <android-base/logging.h>
+#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
#include <android-base/stringprintf.h>
#include <android-base/strings.h>
#include "command.h"
#include "dwarf_unwind.h"
-#include "environment.h"
#include "event_attr.h"
#include "event_type.h"
#include "perf_regs.h"
"dso_from", "dso_to", "symbol_from", "symbol_to",
};
struct BranchFromEntry {
- uint64_t ip;
const MapEntry* map;
const Symbol* symbol;
+ uint64_t vaddr_in_file;
uint64_t flags;
- BranchFromEntry() : ip(0), map(nullptr), symbol(nullptr), flags(0) {}
+ BranchFromEntry()
+ : map(nullptr), symbol(nullptr), vaddr_in_file(0), flags(0) {}
};
struct SampleEntry {
- uint64_t ip;
uint64_t time;
uint64_t period;
// accumuated when appearing in other sample's callchain
const char* thread_comm;
const MapEntry* map;
const Symbol* symbol;
+ uint64_t vaddr_in_file;
BranchFromEntry branch_from;
// a callchain tree representing all callchains in the sample
CallChainRoot<SampleEntry> callchain;
- SampleEntry(uint64_t ip, uint64_t time, uint64_t period,
- uint64_t accumulated_period, uint64_t sample_count,
- const ThreadEntry* thread, const MapEntry* map,
- const Symbol* symbol)
- : ip(ip),
- time(time),
+ SampleEntry(uint64_t time, uint64_t period, uint64_t accumulated_period,
+ uint64_t sample_count, const ThreadEntry* thread,
+ const MapEntry* map, const Symbol* symbol, uint64_t vaddr_in_file)
+ : time(time),
period(period),
accumulated_period(accumulated_period),
sample_count(sample_count),
thread(thread),
thread_comm(thread->comm),
map(map),
- symbol(symbol) {}
+ symbol(symbol),
+ vaddr_in_file(vaddr_in_file) {}
// The data member 'callchain' can only move, not copy.
SampleEntry(SampleEntry&&) = default;
uint64_t total_period;
};
+BUILD_COMPARE_VALUE_FUNCTION(CompareVaddrInFile, vaddr_in_file);
+BUILD_DISPLAY_HEX64_FUNCTION(DisplayVaddrInFile, vaddr_in_file);
+
class ReportCmdSampleTreeBuilder
: public SampleTreeBuilder<SampleEntry, uint64_t> {
public:
void SetFilters(const std::unordered_set<int>& pid_filter,
const std::unordered_set<int>& tid_filter,
const std::unordered_set<std::string>& comm_filter,
- const std::unordered_set<std::string>& dso_filter) {
+ const std::unordered_set<std::string>& dso_filter,
+ const std::unordered_set<std::string>& symbol_filter) {
pid_filter_ = pid_filter;
tid_filter_ = tid_filter;
comm_filter_ = comm_filter;
dso_filter_ = dso_filter;
+ symbol_filter_ = symbol_filter;
}
SampleTree GetSampleTree() const {
thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
const MapEntry* map =
thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel);
- const Symbol* symbol = thread_tree_->FindSymbol(map, r.ip_data.ip);
+ uint64_t vaddr_in_file;
+ const Symbol* symbol =
+ thread_tree_->FindSymbol(map, r.ip_data.ip, &vaddr_in_file);
*acc_info = r.period_data.period;
return InsertSample(std::unique_ptr<SampleEntry>(
- new SampleEntry(r.ip_data.ip, r.time_data.time, r.period_data.period, 0,
- 1, thread, map, symbol)));
+ new SampleEntry(r.time_data.time, r.period_data.period, 0, 1, thread,
+ map, symbol, vaddr_in_file)));
}
SampleEntry* CreateBranchSample(const SampleRecord& r,
const ThreadEntry* thread =
thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
const MapEntry* from_map = thread_tree_->FindMap(thread, item.from);
- const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, item.from);
+ uint64_t from_vaddr_in_file;
+ const Symbol* from_symbol =
+ thread_tree_->FindSymbol(from_map, item.from, &from_vaddr_in_file);
const MapEntry* to_map = thread_tree_->FindMap(thread, item.to);
- const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, item.to);
+ uint64_t to_vaddr_in_file;
+ const Symbol* to_symbol =
+ thread_tree_->FindSymbol(to_map, item.to, &to_vaddr_in_file);
std::unique_ptr<SampleEntry> sample(
- new SampleEntry(item.to, r.time_data.time, r.period_data.period, 0, 1,
- thread, to_map, to_symbol));
- sample->branch_from.ip = item.from;
+ new SampleEntry(r.time_data.time, r.period_data.period, 0, 1, thread,
+ to_map, to_symbol, to_vaddr_in_file));
sample->branch_from.map = from_map;
sample->branch_from.symbol = from_symbol;
+ sample->branch_from.vaddr_in_file = from_vaddr_in_file;
sample->branch_from.flags = item.flags;
return InsertSample(std::move(sample));
}
const uint64_t& acc_info) override {
const ThreadEntry* thread = sample->thread;
const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
- const Symbol* symbol = thread_tree_->FindSymbol(map, ip);
- std::unique_ptr<SampleEntry> callchain_sample(
- new SampleEntry(ip, sample->time, 0, acc_info, 0, thread, map, symbol));
+ uint64_t vaddr_in_file;
+ const Symbol* symbol = thread_tree_->FindSymbol(map, ip, &vaddr_in_file);
+ std::unique_ptr<SampleEntry> callchain_sample(new SampleEntry(
+ sample->time, 0, acc_info, 0, thread, map, symbol, vaddr_in_file));
return InsertCallChainSample(std::move(callchain_sample), callchain);
}
return sample->thread;
}
- void InsertCallChainForSample(SampleEntry* sample,
- const std::vector<SampleEntry*>& callchain,
- const uint64_t& acc_info) override {
- sample->callchain.AddCallChain(callchain, acc_info);
+ uint64_t GetPeriodForCallChain(const uint64_t& acc_info) override {
+ return acc_info;
}
bool FilterSample(const SampleEntry* sample) override {
dso_filter_.find(sample->map->dso->Path()) == dso_filter_.end()) {
return false;
}
+ if (!symbol_filter_.empty() &&
+ symbol_filter_.find(sample->symbol->DemangledName()) ==
+ symbol_filter_.end()) {
+ return false;
+ }
return true;
}
std::unordered_set<int> tid_filter_;
std::unordered_set<std::string> comm_filter_;
std::unordered_set<std::string> dso_filter_;
+ std::unordered_set<std::string> symbol_filter_;
uint64_t total_samples_;
uint64_t total_period_;
using ReportCmdSampleTreeDisplayer =
SampleTreeDisplayer<SampleEntry, SampleTree>;
+using ReportCmdCallgraphDisplayer =
+ CallgraphDisplayer<SampleEntry, CallChainNode<SampleEntry>>;
+
+class ReportCmdCallgraphDisplayerWithVaddrInFile
+ : public ReportCmdCallgraphDisplayer {
+ protected:
+ std::string PrintSampleName(const SampleEntry* sample) override {
+ return android::base::StringPrintf("%s [+0x%" PRIx64 "]",
+ sample->symbol->DemangledName(),
+ sample->vaddr_in_file);
+ }
+};
+
struct EventAttrWithName {
perf_event_attr attr;
std::string name;
- std::vector<uint64_t> event_ids;
};
class ReportCommand : public Command {
"-g [callee|caller] Print call graph. If callee mode is used, the graph\n"
" shows how functions are called from others. Otherwise,\n"
" the graph shows how functions call others.\n"
-" Default is callee mode.\n"
+" Default is caller mode.\n"
"-i <file> Specify path of record file, default is perf.data.\n"
+"--max-stack <frames> Set max stack frames shown when printing call graph.\n"
"-n Print the sample count for each item.\n"
"--no-demangle Don't demangle symbol names.\n"
+"--no-show-ip Don't show vaddr in file for unknown symbols.\n"
"-o report_file_name Set report file name, default is stdout.\n"
+"--percent-limit <percent> Set min percentage shown when printing call graph.\n"
"--pids pid1,pid2,... Report only for selected pids.\n"
-"--sort key1,key2,... Select the keys to sort and print the report.\n"
-" Possible keys include pid, tid, comm, dso, symbol,\n"
-" dso_from, dso_to, symbol_from, symbol_to.\n"
-" dso_from, dso_to, symbol_from, symbol_to can only be\n"
-" used with -b option.\n"
-" Default keys are \"comm,pid,tid,dso,symbol\"\n"
+"--sort key1,key2,... Select keys used to sort and print the report. The\n"
+" appearance order of keys decides the order of keys used\n"
+" to sort and print the report.\n"
+" Possible keys include:\n"
+" pid -- process id\n"
+" tid -- thread id\n"
+" comm -- thread name (can be changed during\n"
+" the lifetime of a thread)\n"
+" dso -- shared library\n"
+" symbol -- function name in the shared library\n"
+" vaddr_in_file -- virtual address in the shared\n"
+" library\n"
+" Keys can only be used with -b option:\n"
+" dso_from -- shared library branched from\n"
+" dso_to -- shared library branched to\n"
+" symbol_from -- name of function branched from\n"
+" symbol_to -- name of function branched to\n"
+" The default sort keys are:\n"
+" comm,pid,tid,dso,symbol\n"
+"--symbols symbol1;symbol2;... Report only for selected symbols.\n"
"--symfs <dir> Look for files with symbols relative to this directory.\n"
"--tids tid1,tid2,... Report only for selected tids.\n"
"--vmlinux <file> Parse kernel symbols from <file>.\n"
system_wide_collection_(false),
accumulate_callchain_(false),
print_callgraph_(false),
- callgraph_show_callee_(true) {}
+ callgraph_show_callee_(false),
+ callgraph_max_stack_(UINT32_MAX),
+ callgraph_percent_limit_(0) {}
bool Run(const std::vector<std::string>& args);
bool accumulate_callchain_;
bool print_callgraph_;
bool callgraph_show_callee_;
+ uint32_t callgraph_max_stack_;
+ double callgraph_percent_limit_;
std::string report_filename_;
};
bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
bool demangle = true;
+ bool show_ip_for_unknown_symbol = true;
std::string symfs_dir;
std::string vmlinux;
bool print_sample_count = false;
std::vector<std::string> sort_keys = {"comm", "pid", "tid", "dso", "symbol"};
std::unordered_set<std::string> comm_filter;
std::unordered_set<std::string> dso_filter;
+ std::unordered_set<std::string> symbol_filter;
std::unordered_set<int> pid_filter;
std::unordered_set<int> tid_filter;
}
record_filename_ = args[i];
+ } else if (args[i] == "--max-stack") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ if (!android::base::ParseUint(args[i].c_str(), &callgraph_max_stack_)) {
+ LOG(ERROR) << "invalid arg for --max-stack: " << args[i];
+ return false;
+ }
} else if (args[i] == "-n") {
print_sample_count = true;
} else if (args[i] == "--no-demangle") {
demangle = false;
+ } else if (args[i] == "--no-show-ip") {
+ show_ip_for_unknown_symbol = false;
} else if (args[i] == "-o") {
if (!NextArgumentOrError(args, &i)) {
return false;
}
report_filename_ = args[i];
-
+ } else if (args[i] == "--percent-limit") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ if (!android::base::ParseDouble(args[i].c_str(),
+ &callgraph_percent_limit_, 0.0)) {
+ LOG(ERROR) << "invalid arg for --percent-limit: " << args[i];
+ }
} else if (args[i] == "--pids" || args[i] == "--tids") {
+ const std::string& option = args[i];
+ std::unordered_set<int>& filter =
+ (option == "--pids" ? pid_filter : tid_filter);
if (!NextArgumentOrError(args, &i)) {
return false;
}
std::vector<std::string> strs = android::base::Split(args[i], ",");
- std::vector<int> ids;
for (const auto& s : strs) {
int id;
if (!android::base::ParseInt(s.c_str(), &id, 0)) {
- LOG(ERROR) << "invalid id in " << args[i] << " option: " << s;
+ LOG(ERROR) << "invalid id in " << option << " option: " << s;
return false;
}
- ids.push_back(id);
+ filter.insert(id);
}
- std::unordered_set<int>& filter =
- (args[i] == "--pids" ? pid_filter : tid_filter);
- filter.insert(ids.begin(), ids.end());
} else if (args[i] == "--sort") {
if (!NextArgumentOrError(args, &i)) {
return false;
}
sort_keys = android::base::Split(args[i], ",");
+ } else if (args[i] == "--symbols") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ std::vector<std::string> strs = android::base::Split(args[i], ";");
+ symbol_filter.insert(strs.begin(), strs.end());
} else if (args[i] == "--symfs") {
if (!NextArgumentOrError(args, &i)) {
return false;
Dso::SetVmlinux(vmlinux);
}
+ if (show_ip_for_unknown_symbol) {
+ thread_tree_.ShowIpForUnknownSymbol();
+ }
+
SampleDisplayer<SampleEntry, SampleTree> displayer;
SampleComparator<SampleEntry> comparator;
} else {
displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead);
}
- if (print_callgraph_) {
- displayer.AddExclusiveDisplayFunction(DisplayCallgraph);
- }
if (print_sample_count) {
displayer.AddDisplayFunction("Sample", DisplaySampleCount);
}
} else if (key == "symbol") {
comparator.AddCompareFunction(CompareSymbol);
displayer.AddDisplayFunction("Symbol", DisplaySymbol);
+ } else if (key == "vaddr_in_file") {
+ comparator.AddCompareFunction(CompareVaddrInFile);
+ displayer.AddDisplayFunction("VaddrInFile", DisplayVaddrInFile);
} else if (key == "dso_from") {
comparator.AddCompareFunction(CompareDsoFrom);
displayer.AddDisplayFunction("Source Shared Object", DisplayDsoFrom);
return false;
}
}
+ if (print_callgraph_) {
+ bool has_symbol_key = false;
+ bool has_vaddr_in_file_key = false;
+ for (const auto& key : sort_keys) {
+ if (key == "symbol") {
+ has_symbol_key = true;
+ } else if (key == "vaddr_in_file") {
+ has_vaddr_in_file_key = true;
+ }
+ }
+ if (has_symbol_key) {
+ if (has_vaddr_in_file_key) {
+ displayer.AddExclusiveDisplayFunction(
+ ReportCmdCallgraphDisplayerWithVaddrInFile());
+ } else {
+ displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayer(
+ callgraph_max_stack_, callgraph_percent_limit_));
+ }
+ }
+ }
sample_tree_builder_.reset(
new ReportCmdSampleTreeBuilder(comparator, &thread_tree_));
sample_tree_builder_->SetFilters(pid_filter, tid_filter, comm_filter,
- dso_filter);
+ dso_filter, symbol_filter);
SampleComparator<SampleEntry> sort_comparator;
sort_comparator.AddCompareFunction(CompareTotalPeriod);
}
bool ReportCommand::ReadEventAttrFromRecordFile() {
- std::vector<AttrWithId> attrs = record_file_reader_->AttrSection();
+ std::vector<EventAttrWithId> attrs = record_file_reader_->AttrSection();
for (const auto& attr_with_id : attrs) {
EventAttrWithName attr;
attr.attr = *attr_with_id.attr;
- attr.event_ids = attr_with_id.ids;
- const EventType* event_type =
- FindEventTypeByConfig(attr.attr.type, attr.attr.config);
- if (event_type != nullptr) {
- attr.name = event_type->name;
- }
+ attr.name = GetEventNameByAttr(attr.attr);
event_attrs_.push_back(attr);
}
if (use_branch_address_) {
}
bool ReportCommand::ReadSampleTreeFromRecordFile() {
- thread_tree_.AddThread(0, 0, "swapper");
sample_tree_builder_->SetBranchSampleOption(use_branch_address_);
// Normally do strict arch check when unwinding stack. But allow unwinding
// 32-bit processes on 64-bit devices for system wide profiling.
*static_cast<const SampleRecord*>(record.get()));
} else if (record->type() == PERF_RECORD_TRACING_DATA) {
const auto& r = *static_cast<TracingDataRecord*>(record.get());
- if (!ProcessTracingData(r.data)) {
+ if (!ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size))) {
return false;
}
}