#include <unordered_set>
#include <vector>
+#include <android-base/file.h>
#include <android-base/logging.h>
#include <android-base/parsedouble.h>
#include <android-base/parseint.h>
// The data member 'callchain' can only move, not copy.
SampleEntry(SampleEntry&&) = default;
SampleEntry(SampleEntry&) = delete;
+
+ uint64_t GetPeriod() const {
+ return period;
+ }
};
struct SampleTree {
std::vector<SampleEntry*> samples;
uint64_t total_samples;
uint64_t total_period;
+ uint64_t total_error_callchains;
};
BUILD_COMPARE_VALUE_FUNCTION(CompareVaddrInFile, vaddr_in_file);
: SampleTreeBuilder(sample_comparator),
thread_tree_(thread_tree),
total_samples_(0),
- total_period_(0) {}
+ total_period_(0),
+ total_error_callchains_(0) {}
void SetFilters(const std::unordered_set<int>& pid_filter,
const std::unordered_set<int>& tid_filter,
symbol_filter_ = symbol_filter;
}
- SampleTree GetSampleTree() const {
+ SampleTree GetSampleTree() {
+ AddCallChainDuplicateInfo();
SampleTree sample_tree;
sample_tree.samples = GetSamples();
sample_tree.total_samples = total_samples_;
sample_tree.total_period = total_period_;
+ sample_tree.total_error_callchains = total_error_callchains_;
return sample_tree;
}
const uint64_t& acc_info) override {
const ThreadEntry* thread = sample->thread;
const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
+ if (thread_tree_->IsUnknownDso(map->dso)) {
+ // The unwinders can give wrong ip addresses, which can't map to a valid dso. Skip them.
+ total_error_callchains_++;
+ return nullptr;
+ }
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));
+ callchain_sample->thread_comm = sample->thread_comm;
return InsertCallChainSample(std::move(callchain_sample), callchain);
}
uint64_t total_samples_;
uint64_t total_period_;
+ uint64_t total_error_callchains_;
+};
+
+struct SampleTreeBuilderOptions {
+ SampleComparator<SampleEntry> comparator;
+ ThreadTree* thread_tree;
+ 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;
+ bool use_branch_address;
+ bool accumulate_callchain;
+ bool build_callchain;
+ bool use_caller_as_callchain_root;
+ bool strict_unwind_arch_check;
+
+ std::unique_ptr<ReportCmdSampleTreeBuilder> CreateSampleTreeBuilder() {
+ std::unique_ptr<ReportCmdSampleTreeBuilder> builder(
+ new ReportCmdSampleTreeBuilder(comparator, thread_tree));
+ builder->SetFilters(pid_filter, tid_filter, comm_filter, dso_filter, symbol_filter);
+ builder->SetBranchSampleOption(use_branch_address);
+ builder->SetCallChainSampleOptions(accumulate_callchain, build_callchain,
+ use_caller_as_callchain_root, strict_unwind_arch_check);
+ return builder;
+ }
};
using ReportCmdSampleTreeSorter = SampleTreeSorter<SampleEntry>;
"report", "report sampling information in perf.data",
// clang-format off
"Usage: simpleperf report [options]\n"
+"The default options are: -i perf.data --sort comm,pid,tid,dso,symbol.\n"
"-b Use the branch-to addresses in sampled take branches instead of the\n"
" instruction addresses. Only valid for perf.data recorded with -b/-j\n"
" option.\n"
"--children Print the overhead accumulated by appearing in the callchain.\n"
"--comms comm1,comm2,... Report only for selected comms.\n"
"--dsos dso1,dso2,... Report only for selected dsos.\n"
+"--full-callgraph Print full call graph. Used with -g option. By default,\n"
+" brief call graph is printed.\n"
"-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 caller mode.\n"
"-i <file> Specify path of record file, default is perf.data.\n"
+"--kallsyms <file> Set the file to read kernel symbols.\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"
"-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"
+"--raw-period Report period count instead of period percentage.\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"
print_callgraph_(false),
callgraph_show_callee_(false),
callgraph_max_stack_(UINT32_MAX),
- callgraph_percent_limit_(0) {}
+ callgraph_percent_limit_(0),
+ raw_period_(false),
+ brief_callgraph_(true) {}
bool Run(const std::vector<std::string>& args);
std::unique_ptr<RecordFileReader> record_file_reader_;
std::vector<EventAttrWithName> event_attrs_;
ThreadTree thread_tree_;
- SampleTree sample_tree_;
- std::unique_ptr<ReportCmdSampleTreeBuilder> sample_tree_builder_;
+ // Create a SampleTreeBuilder and SampleTree for each event_attr.
+ std::vector<SampleTree> sample_tree_;
+ SampleTreeBuilderOptions sample_tree_builder_options_;
+ std::vector<std::unique_ptr<ReportCmdSampleTreeBuilder>> sample_tree_builder_;
+
std::unique_ptr<ReportCmdSampleTreeSorter> sample_tree_sorter_;
std::unique_ptr<ReportCmdSampleTreeDisplayer> sample_tree_displayer_;
bool use_branch_address_;
bool callgraph_show_callee_;
uint32_t callgraph_max_stack_;
double callgraph_percent_limit_;
+ bool raw_period_;
+ bool brief_callgraph_;
std::string report_filename_;
};
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;
for (size_t i = 0; i < args.size(); ++i) {
if (args[i] == "-b") {
accumulate_callchain_ = true;
} else if (args[i] == "--comms" || args[i] == "--dsos") {
std::unordered_set<std::string>& filter =
- (args[i] == "--comms" ? comm_filter : dso_filter);
+ (args[i] == "--comms" ? sample_tree_builder_options_.comm_filter
+ : sample_tree_builder_options_.dso_filter);
if (!NextArgumentOrError(args, &i)) {
return false;
}
std::vector<std::string> strs = android::base::Split(args[i], ",");
filter.insert(strs.begin(), strs.end());
-
+ } else if (args[i] == "--full-callgraph") {
+ brief_callgraph_ = false;
} else if (args[i] == "-g") {
print_callgraph_ = true;
accumulate_callchain_ = true;
}
record_filename_ = args[i];
+ } else if (args[i] == "--kallsyms") {
+ if (!NextArgumentOrError(args, &i)) {
+ return false;
+ }
+ std::string kallsyms;
+ if (!android::base::ReadFileToString(args[i], &kallsyms)) {
+ LOG(ERROR) << "Can't read kernel symbols from " << args[i];
+ return false;
+ }
+ Dso::SetKallsyms(kallsyms);
} else if (args[i] == "--max-stack") {
if (!NextArgumentOrError(args, &i)) {
return false;
} 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);
+ (option == "--pids" ? sample_tree_builder_options_.pid_filter
+ : sample_tree_builder_options_.tid_filter);
if (!NextArgumentOrError(args, &i)) {
return false;
}
}
filter.insert(id);
}
-
+ } else if (args[i] == "--raw-period") {
+ raw_period_ = true;
} else if (args[i] == "--sort") {
if (!NextArgumentOrError(args, &i)) {
return false;
return false;
}
std::vector<std::string> strs = android::base::Split(args[i], ";");
- symbol_filter.insert(strs.begin(), strs.end());
+ sample_tree_builder_options_.symbol_filter.insert(strs.begin(), strs.end());
} else if (args[i] == "--symfs") {
if (!NextArgumentOrError(args, &i)) {
return false;
SampleComparator<SampleEntry> comparator;
if (accumulate_callchain_) {
- displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead);
- displayer.AddDisplayFunction("Self", DisplaySelfOverhead);
+ if (raw_period_) {
+ displayer.AddDisplayFunction("Children", DisplayAccumulatedPeriod);
+ displayer.AddDisplayFunction("Self", DisplaySelfPeriod);
+ } else {
+ displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead);
+ displayer.AddDisplayFunction("Self", DisplaySelfOverhead);
+ }
} else {
- displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead);
+ if (raw_period_) {
+ displayer.AddDisplayFunction("Overhead", DisplaySelfPeriod);
+ } else {
+ displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead);
+ }
}
if (print_sample_count) {
displayer.AddDisplayFunction("Sample", DisplaySampleCount);
ReportCmdCallgraphDisplayerWithVaddrInFile());
} else {
displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayer(
- callgraph_max_stack_, callgraph_percent_limit_));
+ callgraph_max_stack_, callgraph_percent_limit_, brief_callgraph_));
}
}
}
- sample_tree_builder_.reset(
- new ReportCmdSampleTreeBuilder(comparator, &thread_tree_));
- sample_tree_builder_->SetFilters(pid_filter, tid_filter, comm_filter,
- dso_filter, symbol_filter);
+ sample_tree_builder_options_.comparator = comparator;
+ sample_tree_builder_options_.thread_tree = &thread_tree_;
SampleComparator<SampleEntry> sort_comparator;
sort_comparator.AddCompareFunction(CompareTotalPeriod);
+ if (print_callgraph_) {
+ sort_comparator.AddCompareFunction(CompareCallGraphDuplicated);
+ }
+ sort_comparator.AddCompareFunction(ComparePeriod);
sort_comparator.AddComparator(comparator);
sample_tree_sorter_.reset(new ReportCmdSampleTreeSorter(sort_comparator));
sample_tree_displayer_.reset(new ReportCmdSampleTreeDisplayer(displayer));
}
bool ReportCommand::ReadFeaturesFromRecordFile() {
- std::vector<BuildIdRecord> records =
- record_file_reader_->ReadBuildIdFeature();
- std::vector<std::pair<std::string, BuildId>> build_ids;
- for (auto& r : records) {
- build_ids.push_back(std::make_pair(r.filename, r.build_id));
- }
- Dso::SetBuildIds(build_ids);
+ record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_);
std::string arch =
record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
}
bool ReportCommand::ReadSampleTreeFromRecordFile() {
- sample_tree_builder_->SetBranchSampleOption(use_branch_address_);
+ sample_tree_builder_options_.use_branch_address = 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.
- bool strict_unwind_arch_check = !system_wide_collection_;
- sample_tree_builder_->SetCallChainSampleOptions(
- accumulate_callchain_, print_callgraph_, !callgraph_show_callee_,
- strict_unwind_arch_check);
+ sample_tree_builder_options_.strict_unwind_arch_check = !system_wide_collection_;
+ sample_tree_builder_options_.accumulate_callchain = accumulate_callchain_;
+ sample_tree_builder_options_.build_callchain = print_callgraph_;
+ sample_tree_builder_options_.use_caller_as_callchain_root = !callgraph_show_callee_;
+
+ for (size_t i = 0; i < event_attrs_.size(); ++i) {
+ sample_tree_builder_.push_back(sample_tree_builder_options_.CreateSampleTreeBuilder());
+ }
+
if (!record_file_reader_->ReadDataSection(
[this](std::unique_ptr<Record> record) {
return ProcessRecord(std::move(record));
})) {
return false;
}
- sample_tree_ = sample_tree_builder_->GetSampleTree();
- sample_tree_sorter_->Sort(sample_tree_.samples, print_callgraph_);
+ for (size_t i = 0; i < sample_tree_builder_.size(); ++i) {
+ sample_tree_.push_back(sample_tree_builder_[i]->GetSampleTree());
+ sample_tree_sorter_->Sort(sample_tree_.back().samples, print_callgraph_);
+ }
return true;
}
bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
thread_tree_.Update(*record);
if (record->type() == PERF_RECORD_SAMPLE) {
- sample_tree_builder_->ProcessSampleRecord(
+ size_t attr_id = record_file_reader_->GetAttrIndexOfRecord(record.get());
+ sample_tree_builder_[attr_id]->ProcessSampleRecord(
*static_cast<const SampleRecord*>(record.get()));
} else if (record->type() == PERF_RECORD_TRACING_DATA) {
const auto& r = *static_cast<TracingDataRecord*>(record.get());
file_handler.reset(report_fp);
}
PrintReportContext(report_fp);
- sample_tree_displayer_->DisplaySamples(report_fp, sample_tree_.samples,
- &sample_tree_);
+ for (size_t i = 0; i < event_attrs_.size(); ++i) {
+ if (i != 0) {
+ fprintf(report_fp, "\n");
+ }
+ EventAttrWithName& attr = event_attrs_[i];
+ SampleTree& sample_tree = sample_tree_[i];
+ fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
+ attr.attr.type, attr.attr.config);
+ fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree.total_samples);
+ if (sample_tree.total_error_callchains != 0) {
+ fprintf(report_fp, "Error Callchains: %" PRIu64 ", %f%%\n",
+ sample_tree.total_error_callchains,
+ sample_tree.total_error_callchains * 100.0 / sample_tree.total_samples);
+ }
+ fprintf(report_fp, "Event count: %" PRIu64 "\n\n", sample_tree.total_period);
+ sample_tree_displayer_->DisplaySamples(report_fp, sample_tree.samples, &sample_tree);
+ }
fflush(report_fp);
if (ferror(report_fp) != 0) {
PLOG(ERROR) << "print report failed";
fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str());
}
fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
- for (const auto& attr : event_attrs_) {
- fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr.name.c_str(),
- attr.attr.type, attr.attr.config);
- }
- fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree_.total_samples);
- fprintf(report_fp, "Event count: %" PRIu64 "\n\n", sample_tree_.total_period);
}
} // namespace