/* * Copyright (C) 2015 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 #include #include #include #include #include #include #include #include #include "RecordFilter.h" #include "command.h" #include "event_attr.h" #include "event_type.h" #include "perf_regs.h" #include "record.h" #include "record_file.h" #include "sample_tree.h" #include "thread_tree.h" #include "tracing.h" #include "utils.h" namespace simpleperf { namespace { using android::base::Split; static std::set branch_sort_keys = { "dso_from", "dso_to", "symbol_from", "symbol_to", }; struct BranchFromEntry { const MapEntry* map; const Symbol* symbol; uint64_t vaddr_in_file; uint64_t flags; BranchFromEntry() : map(nullptr), symbol(nullptr), vaddr_in_file(0), flags(0) {} }; struct SampleEntry { uint64_t time; uint64_t period; // accumuated when appearing in other sample's callchain uint64_t accumulated_period; uint64_t sample_count; int cpu; pid_t pid; pid_t tid; 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 callchain; // event counts for the sample std::vector counts; // accumulated event counts for the sample std::vector acc_counts; SampleEntry(uint64_t time, uint64_t period, uint64_t accumulated_period, uint64_t sample_count, int cpu, const ThreadEntry* thread, const MapEntry* map, const Symbol* symbol, uint64_t vaddr_in_file, const std::vector& counts, const std::vector& acc_counts) : time(time), period(period), accumulated_period(accumulated_period), sample_count(sample_count), cpu(cpu), pid(thread->pid), tid(thread->tid), thread_comm(thread->comm), map(map), symbol(symbol), vaddr_in_file(vaddr_in_file), counts(counts), acc_counts(acc_counts) {} // 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 samples; uint64_t total_samples; uint64_t total_period; uint64_t total_error_callchains; std::string event_name; }; BUILD_COMPARE_VALUE_FUNCTION(CompareVaddrInFile, vaddr_in_file); BUILD_DISPLAY_HEX64_FUNCTION(DisplayVaddrInFile, vaddr_in_file); static std::string DisplayEventName(const SampleEntry*, const SampleTree* info) { return info->event_name; } struct AccInfo { uint64_t period = 0; std::vector counts; }; class ReportCmdSampleTreeBuilder : public SampleTreeBuilder { public: ReportCmdSampleTreeBuilder(const SampleComparator& sample_comparator, ThreadTree* thread_tree, const std::unordered_map& event_id_to_attr_index) : SampleTreeBuilder(sample_comparator), thread_tree_(thread_tree), event_id_to_attr_index_(event_id_to_attr_index), total_samples_(0), total_period_(0), total_error_callchains_(0) {} void SetFilters(const std::unordered_set& cpu_filter, const std::unordered_set& comm_filter, const std::unordered_set& dso_filter, const std::unordered_set& symbol_filter) { cpu_filter_ = cpu_filter; comm_filter_ = comm_filter; dso_filter_ = dso_filter; symbol_filter_ = symbol_filter; } void SetEventName(const std::string& event_name) { event_name_ = event_name; } 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_; sample_tree.event_name = event_name_; return sample_tree; } virtual void ReportCmdProcessSampleRecord(std::shared_ptr& r) { return ProcessSampleRecord(*r); } virtual void ReportCmdProcessSampleRecord(const SampleRecord& r) { return ProcessSampleRecord(r); } protected: virtual uint64_t GetPeriod(const SampleRecord& r) = 0; SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel, AccInfo* acc_info) override { const ThreadEntry* thread = thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); const MapEntry* map = thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel); uint64_t vaddr_in_file; const Symbol* symbol = thread_tree_->FindSymbol(map, r.ip_data.ip, &vaddr_in_file); uint64_t period = GetPeriod(r); acc_info->period = period; std::vector counts = GetCountsForSample(r); acc_info->counts = counts; std::unique_ptr sample(new SampleEntry(r.time_data.time, period, 0, 1, r.Cpu(), thread, map, symbol, vaddr_in_file, counts, counts)); return InsertSample(std::move(sample)); } SampleEntry* CreateBranchSample(const SampleRecord& r, const BranchStackItemType& item) override { const ThreadEntry* thread = thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); const MapEntry* from_map = thread_tree_->FindMap(thread, 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); uint64_t to_vaddr_in_file; const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, item.to, &to_vaddr_in_file); std::unique_ptr sample(new SampleEntry(r.time_data.time, r.period_data.period, 0, 1, r.Cpu(), 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)); } SampleEntry* CreateCallChainSample(const ThreadEntry* thread, const SampleEntry* sample, uint64_t ip, bool in_kernel, const std::vector& callchain, const AccInfo& acc_info) override { 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 callchain_sample( new SampleEntry(sample->time, 0, acc_info.period, 0, sample->cpu, thread, map, symbol, vaddr_in_file, {}, acc_info.counts)); callchain_sample->thread_comm = sample->thread_comm; return InsertCallChainSample(std::move(callchain_sample), callchain); } const ThreadEntry* GetThreadOfSample(SampleEntry* sample) override { return thread_tree_->FindThreadOrNew(sample->pid, sample->tid); } uint64_t GetPeriodForCallChain(const AccInfo& acc_info) override { return acc_info.period; } bool FilterSample(const SampleEntry* sample) override { if (!cpu_filter_.empty() && cpu_filter_.count(sample->cpu) == 0) { return false; } if (!comm_filter_.empty() && comm_filter_.count(sample->thread_comm) == 0) { return false; } if (!dso_filter_.empty() && dso_filter_.count(sample->map->dso->GetReportPath().data()) == 0) { return false; } if (!symbol_filter_.empty() && symbol_filter_.count(sample->symbol->DemangledName()) == 0) { return false; } return true; } void UpdateSummary(const SampleEntry* sample) override { total_samples_ += sample->sample_count; total_period_ += sample->period; } void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override { sample1->period += sample2->period; sample1->accumulated_period += sample2->accumulated_period; sample1->sample_count += sample2->sample_count; if (sample1->counts.size() < sample2->counts.size()) { sample1->counts.resize(sample2->counts.size(), 0); } for (size_t i = 0; i < sample2->counts.size(); i++) { sample1->counts[i] += sample2->counts[i]; } if (sample1->acc_counts.size() < sample2->acc_counts.size()) { sample1->acc_counts.resize(sample2->acc_counts.size(), 0); } for (size_t i = 0; i < sample2->acc_counts.size(); i++) { sample1->acc_counts[i] += sample2->acc_counts[i]; } } private: std::vector GetCountsForSample(const SampleRecord& r) { CHECK_EQ(r.read_data.counts.size(), r.read_data.ids.size()); std::vector res(r.read_data.counts.size(), 0); for (size_t i = 0; i < r.read_data.counts.size(); i++) { uint64_t event_id = r.read_data.ids[i]; uint64_t count = r.read_data.counts[i]; uint64_t& last_count = event_id_count_map_[event_id]; uint64_t added_count = count - last_count; last_count = count; auto it = event_id_to_attr_index_.find(event_id); CHECK(it != event_id_to_attr_index_.end()); CHECK_LT(it->second, res.size()); // Count for the current sample is the added event count after generating the previous sample. res[it->second] = added_count; } return res; } ThreadTree* thread_tree_; const std::unordered_map& event_id_to_attr_index_; std::unordered_set cpu_filter_; std::unordered_set comm_filter_; std::unordered_set dso_filter_; std::unordered_set symbol_filter_; uint64_t total_samples_; uint64_t total_period_; uint64_t total_error_callchains_; std::string event_name_; // Map from event_id to its last event count. std::unordered_map event_id_count_map_; }; // Build sample tree based on event count in each sample. class EventCountSampleTreeBuilder : public ReportCmdSampleTreeBuilder { public: EventCountSampleTreeBuilder(const SampleComparator& sample_comparator, ThreadTree* thread_tree, const std::unordered_map& event_id_to_attr_index) : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree, event_id_to_attr_index) {} protected: uint64_t GetPeriod(const SampleRecord& r) override { return r.period_data.period; } }; // Build sample tree based on the time difference between current sample and next sample. class TimestampSampleTreeBuilder : public ReportCmdSampleTreeBuilder { public: TimestampSampleTreeBuilder(const SampleComparator& sample_comparator, ThreadTree* thread_tree, const std::unordered_map& event_id_to_attr_index) : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree, event_id_to_attr_index) {} void ReportCmdProcessSampleRecord(std::shared_ptr& r) override { pid_t tid = static_cast(r->tid_data.tid); auto it = next_sample_cache_.find(tid); if (it == next_sample_cache_.end()) { next_sample_cache_[tid] = r; } else { std::shared_ptr cur = it->second; it->second = r; ProcessSampleRecord(*cur); } } protected: uint64_t GetPeriod(const SampleRecord& r) override { auto it = next_sample_cache_.find(r.tid_data.tid); CHECK(it != next_sample_cache_.end()); // Normally the samples are sorted by time, but check here for safety. if (it->second->time_data.time > r.time_data.time) { return it->second->time_data.time - r.time_data.time; } return 1u; } private: std::unordered_map> next_sample_cache_; }; struct SampleTreeBuilderOptions { SampleComparator comparator; ThreadTree* thread_tree; std::unordered_set comm_filter; std::unordered_set dso_filter; std::unordered_set symbol_filter; std::unordered_set cpu_filter; bool use_branch_address; bool accumulate_callchain; bool build_callchain; bool use_caller_as_callchain_root; bool trace_offcpu; std::unique_ptr CreateSampleTreeBuilder( const RecordFileReader& reader) { std::unique_ptr builder; if (trace_offcpu) { builder.reset(new TimestampSampleTreeBuilder(comparator, thread_tree, reader.EventIdMap())); } else { builder.reset(new EventCountSampleTreeBuilder(comparator, thread_tree, reader.EventIdMap())); } builder->SetFilters(cpu_filter, comm_filter, dso_filter, symbol_filter); builder->SetBranchSampleOption(use_branch_address); builder->SetCallChainSampleOptions(accumulate_callchain, build_callchain, use_caller_as_callchain_root); return builder; } }; using ReportCmdSampleTreeSorter = SampleTreeSorter; using ReportCmdSampleTreeDisplayer = SampleTreeDisplayer; using ReportCmdCallgraphDisplayer = CallgraphDisplayer>; 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); } }; class ReportCommand : public Command { public: ReportCommand() : Command( "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" " In the report, Children column shows overhead for a symbol and functions called\n" " by the symbol, while Self column shows overhead for the symbol itself.\n" "--csv Report in csv format.\n" "--csv-separator Set separator for csv columns. Default is ','.\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 Specify path of record file, default is perf.data.\n" "--kallsyms Set the file to read kernel symbols.\n" "--max-stack 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 Set min percentage in report entries and call graphs.\n" "--print-event-count Print event counts for each item. Additional events can be added by\n" " --add-counter in record cmd.\n" "--raw-period Report period count instead of period percentage.\n" "--sort key1,key2,... Select keys used to group samples into report entries. Samples having\n" " the same key values are aggregated into one report entry. Each report\n" " entry is printed in one row, having columns to show key values.\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" "--symfs Look for files with symbols relative to this directory.\n" "--symdir Look for files with symbols in a directory recursively.\n" "--vmlinux Parse kernel symbols from .\n" "\n" "Sample filter options:\n" "--comms comm1,comm2,... Report only for threads with selected names.\n" "--dsos dso1,dso2,... Report only for selected dsos.\n" "--pids pid1,pid2,... Same as '--include-pid'.\n" "--symbols symbol1;symbol2;... Report only for selected symbols.\n" "--tids tid1,tid2,... Same as '--include-tid'.\n" RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING // clang-format on ), record_filename_("perf.data"), record_file_arch_(GetTargetArch()), use_branch_address_(false), accumulate_callchain_(false), print_callgraph_(false), callgraph_show_callee_(false), callgraph_max_stack_(UINT32_MAX), percent_limit_(0), raw_period_(false), brief_callgraph_(true), trace_offcpu_(false), sched_switch_attr_id_(0u), record_filter_(thread_tree_) {} bool Run(const std::vector& args); private: bool ParseOptions(const std::vector& args); bool BuildSampleComparatorAndDisplayer(); bool ReadMetaInfoFromRecordFile(); bool ReadEventAttrFromRecordFile(); bool ReadFeaturesFromRecordFile(); bool ReadSampleTreeFromRecordFile(); bool ProcessRecord(std::unique_ptr record); void ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr record, size_t attr_id); bool ProcessTracingData(const std::vector& data); bool PrintReport(); void PrintReportContext(FILE* fp); std::string record_filename_; ArchType record_file_arch_; std::unique_ptr record_file_reader_; std::vector event_attrs_; std::vector attr_names_; ThreadTree thread_tree_; // Create a SampleTreeBuilder and SampleTree for each event_attr. std::vector sample_tree_; SampleTreeBuilderOptions sample_tree_builder_options_; std::vector> sample_tree_builder_; std::unique_ptr sample_tree_sorter_; std::unique_ptr sample_tree_displayer_; bool use_branch_address_; std::string record_cmdline_; bool accumulate_callchain_; bool print_callgraph_; bool callgraph_show_callee_; uint32_t callgraph_max_stack_; double percent_limit_; bool raw_period_; bool brief_callgraph_; bool trace_offcpu_; size_t sched_switch_attr_id_; bool report_csv_ = false; std::string csv_separator_ = ","; bool print_sample_count_ = false; bool print_event_count_ = false; std::vector sort_keys_; std::string report_filename_; RecordFilter record_filter_; }; bool ReportCommand::Run(const std::vector& args) { // 1. Parse options. if (!ParseOptions(args)) { return false; } // 2. Read record file and build SampleTree. record_file_reader_ = RecordFileReader::CreateInstance(record_filename_); if (record_file_reader_ == nullptr) { return false; } if (!ReadMetaInfoFromRecordFile()) { return false; } if (!ReadEventAttrFromRecordFile()) { return false; } if (!BuildSampleComparatorAndDisplayer()) { return false; } // Read features first to prepare build ids used when building SampleTree. if (!ReadFeaturesFromRecordFile()) { return false; } ScopedCurrentArch scoped_arch(record_file_arch_); if (!ReadSampleTreeFromRecordFile()) { return false; } // 3. Show collected information. if (!PrintReport()) { return false; } return true; } bool ReportCommand::ParseOptions(const std::vector& args) { OptionFormatMap option_formats = { {"-b", {OptionValueType::NONE, OptionType::SINGLE}}, {"--children", {OptionValueType::NONE, OptionType::SINGLE}}, {"--comms", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--cpu", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--csv", {OptionValueType::NONE, OptionType::SINGLE}}, {"--csv-separator", {OptionValueType::STRING, OptionType::SINGLE}}, {"--dsos", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--full-callgraph", {OptionValueType::NONE, OptionType::SINGLE}}, {"-g", {OptionValueType::OPT_STRING, OptionType::SINGLE}}, {"-i", {OptionValueType::STRING, OptionType::SINGLE}}, {"--kallsyms", {OptionValueType::STRING, OptionType::SINGLE}}, {"--max-stack", {OptionValueType::UINT, OptionType::SINGLE}}, {"-n", {OptionValueType::NONE, OptionType::SINGLE}}, {"--no-demangle", {OptionValueType::NONE, OptionType::SINGLE}}, {"--no-show-ip", {OptionValueType::NONE, OptionType::SINGLE}}, {"-o", {OptionValueType::STRING, OptionType::SINGLE}}, {"--percent-limit", {OptionValueType::DOUBLE, OptionType::SINGLE}}, {"--pids", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--print-event-count", {OptionValueType::NONE, OptionType::SINGLE}}, {"--tids", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--raw-period", {OptionValueType::NONE, OptionType::SINGLE}}, {"--sort", {OptionValueType::STRING, OptionType::SINGLE}}, {"--symbols", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--symfs", {OptionValueType::STRING, OptionType::SINGLE}}, {"--symdir", {OptionValueType::STRING, OptionType::SINGLE}}, {"--vmlinux", {OptionValueType::STRING, OptionType::SINGLE}}, }; OptionFormatMap record_filter_options = GetRecordFilterOptionFormats(false); option_formats.insert(record_filter_options.begin(), record_filter_options.end()); OptionValueMap options; std::vector> ordered_options; if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) { return false; } // Process options. use_branch_address_ = options.PullBoolValue("-b"); accumulate_callchain_ = options.PullBoolValue("--children"); for (const OptionValue& value : options.PullValues("--comms")) { std::vector strs = Split(*value.str_value, ","); sample_tree_builder_options_.comm_filter.insert(strs.begin(), strs.end()); } if (!record_filter_.ParseOptions(options)) { return false; } for (const OptionValue& value : options.PullValues("--cpu")) { if (auto cpus = GetCpusFromString(*value.str_value); cpus) { sample_tree_builder_options_.cpu_filter.insert(cpus->begin(), cpus->end()); } else { return false; } } report_csv_ = options.PullBoolValue("--csv"); options.PullStringValue("--csv-separator", &csv_separator_); for (const OptionValue& value : options.PullValues("--dsos")) { std::vector strs = Split(*value.str_value, ","); sample_tree_builder_options_.dso_filter.insert(strs.begin(), strs.end()); } brief_callgraph_ = !options.PullBoolValue("--full-callgraph"); if (auto value = options.PullValue("-g"); value) { print_callgraph_ = true; accumulate_callchain_ = true; if (value->str_value != nullptr) { if (*value->str_value == "callee") { callgraph_show_callee_ = true; } else if (*value->str_value == "caller") { callgraph_show_callee_ = false; } else { LOG(ERROR) << "Unknown argument with -g option: " << *value->str_value; return false; } } } options.PullStringValue("-i", &record_filename_); if (auto value = options.PullValue("--kallsyms"); value) { std::string kallsyms; if (!android::base::ReadFileToString(*value->str_value, &kallsyms)) { LOG(ERROR) << "Can't read kernel symbols from " << *value->str_value; return false; } Dso::SetKallsyms(kallsyms); } if (!options.PullUintValue("--max-stack", &callgraph_max_stack_)) { return false; } print_sample_count_ = options.PullBoolValue("-n"); Dso::SetDemangle(!options.PullBoolValue("--no-demangle")); if (!options.PullBoolValue("--no-show-ip")) { thread_tree_.ShowIpForUnknownSymbol(); } options.PullStringValue("-o", &report_filename_); if (!options.PullDoubleValue("--percent-limit", &percent_limit_, 0)) { return false; } if (auto strs = options.PullStringValues("--pids"); !strs.empty()) { if (auto pids = GetPidsFromStrings(strs, false, false); pids) { record_filter_.AddPids(pids.value(), false); } else { return false; } } print_event_count_ = options.PullBoolValue("--print-event-count"); for (const OptionValue& value : options.PullValues("--tids")) { if (auto tids = GetTidsFromString(*value.str_value, false); tids) { record_filter_.AddTids(tids.value(), false); } else { return false; } } raw_period_ = options.PullBoolValue("--raw-period"); sort_keys_ = {"comm", "pid", "tid", "dso", "symbol"}; if (auto value = options.PullValue("--sort"); value) { sort_keys_ = Split(*value->str_value, ","); } for (const OptionValue& value : options.PullValues("--symbols")) { std::vector symbols = Split(*value.str_value, ";"); sample_tree_builder_options_.symbol_filter.insert(symbols.begin(), symbols.end()); } if (auto value = options.PullValue("--symfs"); value) { if (!Dso::SetSymFsDir(*value->str_value)) { return false; } } if (auto value = options.PullValue("--symdir"); value) { if (!Dso::AddSymbolDir(*value->str_value)) { return false; } } if (auto value = options.PullValue("--vmlinux"); value) { Dso::SetVmlinux(*value->str_value); } CHECK(options.values.empty()); return true; } bool ReportCommand::BuildSampleComparatorAndDisplayer() { SampleDisplayer displayer; displayer.SetReportFormat(report_csv_, csv_separator_); SampleComparator comparator; if (accumulate_callchain_) { if (raw_period_) { displayer.AddDisplayFunction("Children", DisplayAccumulatedPeriod); displayer.AddDisplayFunction("Self", DisplaySelfPeriod); } else { displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead); displayer.AddDisplayFunction("Self", DisplaySelfOverhead); } } else { if (raw_period_) { displayer.AddDisplayFunction("Overhead", DisplaySelfPeriod); } else { displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead); } } if (print_sample_count_) { displayer.AddDisplayFunction("Sample", DisplaySampleCount); } if (print_event_count_) { if (event_attrs_.size() == attr_names_.size()) { // Without additional counters, counts field isn't available. So print period field instead. if (accumulate_callchain_) { displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod); displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod); } else { displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod); } } else { // With additional counters, print counts field. for (size_t i = 0; i < attr_names_.size(); i++) { auto self_event_count_fn = [i](const SampleEntry* s) { return i < s->counts.size() ? std::to_string(s->counts[i]) : "0"; }; auto acc_event_count_fn = [i](const SampleEntry* s) { return i < s->acc_counts.size() ? std::to_string(s->acc_counts[i]) : "0"; }; if (accumulate_callchain_) { displayer.AddDisplayFunction("AccEventCount_" + attr_names_[i], acc_event_count_fn); displayer.AddDisplayFunction("SelfEventCount_" + attr_names_[i], self_event_count_fn); } else { displayer.AddDisplayFunction("EventCount_" + attr_names_[i], self_event_count_fn); } } } } for (auto& key : sort_keys_) { if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) { LOG(ERROR) << "sort key '" << key << "' can only be used with -b option."; return false; } if (key == "pid") { comparator.AddCompareFunction(ComparePid); displayer.AddDisplayFunction("Pid", DisplayPid); } else if (key == "tid") { comparator.AddCompareFunction(CompareTid); displayer.AddDisplayFunction("Tid", DisplayTid); } else if (key == "comm") { comparator.AddCompareFunction(CompareComm); displayer.AddDisplayFunction("Command", DisplayComm); } else if (key == "dso") { comparator.AddCompareFunction(CompareDso); displayer.AddDisplayFunction("Shared Object", DisplayDso); } 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); } else if (key == "dso_to") { comparator.AddCompareFunction(CompareDso); displayer.AddDisplayFunction("Target Shared Object", DisplayDso); } else if (key == "symbol_from") { comparator.AddCompareFunction(CompareSymbolFrom); displayer.AddDisplayFunction("Source Symbol", DisplaySymbolFrom); } else if (key == "symbol_to") { comparator.AddCompareFunction(CompareSymbol); displayer.AddDisplayFunction("Target Symbol", DisplaySymbol); } else { LOG(ERROR) << "Unknown sort key: " << key; return false; } } // Reporting with --csv will add event count and event name columns. But if --print-event-count is // used, there is no need to duplicate printing event counts. if (report_csv_ && !print_event_count_) { if (accumulate_callchain_) { displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod); displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod); } else { displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod); } displayer.AddDisplayFunction("EventName", DisplayEventName); } 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_, percent_limit_, brief_callgraph_)); } } } if (percent_limit_ != 0.0) { displayer.SetFilterFunction([this](const SampleEntry* sample, const SampleTree* sample_tree) { uint64_t total_period = sample->period + sample->accumulated_period; return total_period >= sample_tree->total_period * percent_limit_ / 100.0; }); } sample_tree_builder_options_.comparator = comparator; sample_tree_builder_options_.thread_tree = &thread_tree_; SampleComparator 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)); return true; } bool ReportCommand::ReadMetaInfoFromRecordFile() { auto& meta_info = record_file_reader_->GetMetaInfoFeature(); if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) { trace_offcpu_ = it->second == "true"; } return record_filter_.CheckClock(record_file_reader_->GetClockId()); } bool ReportCommand::ReadEventAttrFromRecordFile() { for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) { const perf_event_attr& attr = attr_with_id.attr; attr_names_.emplace_back(GetEventNameByAttr(attr)); // There are no samples for events added by --add-counter. So skip them. if ((attr.read_format & PERF_FORMAT_GROUP) && (attr.freq == 0) && (attr.sample_period == INFINITE_SAMPLE_PERIOD)) { continue; } event_attrs_.emplace_back(attr); } if (use_branch_address_) { bool has_branch_stack = true; for (const auto& attr : event_attrs_) { if ((attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) { has_branch_stack = false; break; } } if (!has_branch_stack) { LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option."; return false; } } if (trace_offcpu_) { size_t i; for (i = 0; i < event_attrs_.size(); ++i) { if (attr_names_[i] == "sched:sched_switch") { break; } } CHECK_NE(i, event_attrs_.size()); sched_switch_attr_id_ = i; } return true; } bool ReportCommand::ReadFeaturesFromRecordFile() { if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) { return false; } std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH); if (!arch.empty()) { record_file_arch_ = GetArchType(arch); if (record_file_arch_ == ARCH_UNSUPPORTED) { return false; } } std::vector cmdline = record_file_reader_->ReadCmdlineFeature(); if (!cmdline.empty()) { record_cmdline_ = android::base::Join(cmdline, ' '); } if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) { std::vector tracing_data; if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_TRACING_DATA, &tracing_data)) { return false; } if (!ProcessTracingData(tracing_data)) { return false; } } return true; } bool ReportCommand::ReadSampleTreeFromRecordFile() { sample_tree_builder_options_.use_branch_address = use_branch_address_; 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_; sample_tree_builder_options_.trace_offcpu = trace_offcpu_; for (size_t i = 0; i < event_attrs_.size(); ++i) { sample_tree_builder_.push_back( sample_tree_builder_options_.CreateSampleTreeBuilder(*record_file_reader_)); sample_tree_builder_.back()->SetEventName(attr_names_[i]); OfflineUnwinder* unwinder = sample_tree_builder_.back()->GetUnwinder(); if (unwinder != nullptr) { unwinder->LoadMetaInfo(record_file_reader_->GetMetaInfoFeature()); } } if (!record_file_reader_->ReadDataSection( [this](std::unique_ptr record) { return ProcessRecord(std::move(record)); })) { return false; } 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) { thread_tree_.Update(*record); if (record->type() == PERF_RECORD_SAMPLE) { if (!record_filter_.Check(static_cast(*record))) { return true; } size_t attr_id = record_file_reader_->GetAttrIndexOfRecord(record.get()); if (!trace_offcpu_) { sample_tree_builder_[attr_id]->ReportCmdProcessSampleRecord( *static_cast(record.get())); } else { ProcessSampleRecordInTraceOffCpuMode(std::move(record), attr_id); } } else if (record->type() == PERF_RECORD_TRACING_DATA || record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) { const auto& r = *static_cast(record.get()); if (!ProcessTracingData(std::vector(r.data, r.data + r.data_size))) { return false; } } return true; } void ReportCommand::ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr record, size_t attr_id) { std::shared_ptr r(static_cast(record.release())); if (attr_id == sched_switch_attr_id_) { // If this sample belongs to sched_switch event, we should broadcast the offcpu info // to other event types. for (size_t i = 0; i < event_attrs_.size(); ++i) { if (i == sched_switch_attr_id_) { continue; } sample_tree_builder_[i]->ReportCmdProcessSampleRecord(r); } } else { sample_tree_builder_[attr_id]->ReportCmdProcessSampleRecord(r); } } bool ReportCommand::ProcessTracingData(const std::vector& data) { auto tracing = Tracing::Create(data); if (!tracing) { return false; } for (size_t i = 0; i < event_attrs_.size(); i++) { if (event_attrs_[i].type == PERF_TYPE_TRACEPOINT) { uint64_t trace_event_id = event_attrs_[i].config; attr_names_[i] = tracing->GetTracingEventNameHavingId(trace_event_id); } } return true; } bool ReportCommand::PrintReport() { std::unique_ptr file_handler(nullptr, fclose); FILE* report_fp = stdout; if (!report_filename_.empty()) { report_fp = fopen(report_filename_.c_str(), "w"); if (report_fp == nullptr) { PLOG(ERROR) << "failed to open file " << report_filename_; return false; } file_handler.reset(report_fp); } PrintReportContext(report_fp); for (size_t i = 0; i < event_attrs_.size(); ++i) { if (trace_offcpu_ && i == sched_switch_attr_id_) { continue; } if (i != 0) { fprintf(report_fp, "\n"); } SampleTree& sample_tree = sample_tree_[i]; fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr_names_[i].c_str(), event_attrs_[i].type, event_attrs_[i].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); } const char* period_prefix = trace_offcpu_ ? "Time in ns" : "Event count"; fprintf(report_fp, "%s: %" PRIu64 "\n\n", period_prefix, 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"; return false; } return true; } void ReportCommand::PrintReportContext(FILE* report_fp) { if (!record_cmdline_.empty()) { fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str()); } fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str()); } } // namespace void RegisterReportCommand() { RegisterCommand("report", [] { return std::unique_ptr(new ReportCommand()); }); } } // namespace simpleperf