• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2015 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <inttypes.h>
18 #include <algorithm>
19 #include <functional>
20 #include <map>
21 #include <set>
22 #include <string>
23 #include <unordered_map>
24 #include <unordered_set>
25 #include <vector>
26 
27 #include <android-base/file.h>
28 #include <android-base/logging.h>
29 #include <android-base/parseint.h>
30 #include <android-base/stringprintf.h>
31 #include <android-base/strings.h>
32 
33 #include "RecordFilter.h"
34 #include "command.h"
35 #include "event_attr.h"
36 #include "event_type.h"
37 #include "perf_regs.h"
38 #include "record.h"
39 #include "record_file.h"
40 #include "sample_tree.h"
41 #include "thread_tree.h"
42 #include "tracing.h"
43 #include "utils.h"
44 
45 namespace simpleperf {
46 namespace {
47 
48 using android::base::Split;
49 
50 static std::set<std::string> branch_sort_keys = {
51     "dso_from",
52     "dso_to",
53     "symbol_from",
54     "symbol_to",
55 };
56 struct BranchFromEntry {
57   const MapEntry* map;
58   const Symbol* symbol;
59   uint64_t vaddr_in_file;
60   uint64_t flags;
61 
BranchFromEntrysimpleperf::__anon602564b30111::BranchFromEntry62   BranchFromEntry() : map(nullptr), symbol(nullptr), vaddr_in_file(0), flags(0) {}
63 };
64 
65 struct SampleEntry {
66   uint64_t time;
67   uint64_t period;
68   // accumuated when appearing in other sample's callchain
69   uint64_t accumulated_period;
70   uint64_t sample_count;
71   int cpu;
72   pid_t pid;
73   pid_t tid;
74   const char* thread_comm;
75   const MapEntry* map;
76   const Symbol* symbol;
77   uint64_t vaddr_in_file;
78   BranchFromEntry branch_from;
79   // a callchain tree representing all callchains in the sample
80   CallChainRoot<SampleEntry> callchain;
81   // event counts for the sample
82   std::vector<uint64_t> counts;
83   // accumulated event counts for the sample
84   std::vector<uint64_t> acc_counts;
85 
SampleEntrysimpleperf::__anon602564b30111::SampleEntry86   SampleEntry(uint64_t time, uint64_t period, uint64_t accumulated_period, uint64_t sample_count,
87               int cpu, const ThreadEntry* thread, const MapEntry* map, const Symbol* symbol,
88               uint64_t vaddr_in_file, const std::vector<uint64_t>& counts,
89               const std::vector<uint64_t>& acc_counts)
90       : time(time),
91         period(period),
92         accumulated_period(accumulated_period),
93         sample_count(sample_count),
94         cpu(cpu),
95         pid(thread->pid),
96         tid(thread->tid),
97         thread_comm(thread->comm),
98         map(map),
99         symbol(symbol),
100         vaddr_in_file(vaddr_in_file),
101         counts(counts),
102         acc_counts(acc_counts) {}
103 
104   // The data member 'callchain' can only move, not copy.
105   SampleEntry(SampleEntry&&) = default;
106   SampleEntry(SampleEntry&) = delete;
107 
GetPeriodsimpleperf::__anon602564b30111::SampleEntry108   uint64_t GetPeriod() const { return period; }
109 };
110 
111 struct SampleTree {
112   std::vector<SampleEntry*> samples;
113   uint64_t total_samples;
114   uint64_t total_period;
115   uint64_t total_error_callchains;
116   std::string event_name;
117 };
118 
119 BUILD_COMPARE_VALUE_FUNCTION(CompareVaddrInFile, vaddr_in_file);
120 BUILD_DISPLAY_HEX64_FUNCTION(DisplayVaddrInFile, vaddr_in_file);
121 
DisplayEventName(const SampleEntry *,const SampleTree * info)122 static std::string DisplayEventName(const SampleEntry*, const SampleTree* info) {
123   return info->event_name;
124 }
125 
126 struct AccInfo {
127   uint64_t period = 0;
128   std::vector<uint64_t> counts;
129 };
130 
131 class ReportCmdSampleTreeBuilder : public SampleTreeBuilder<SampleEntry, AccInfo> {
132  public:
ReportCmdSampleTreeBuilder(const SampleComparator<SampleEntry> & sample_comparator,ThreadTree * thread_tree,const std::unordered_map<uint64_t,size_t> & event_id_to_attr_index)133   ReportCmdSampleTreeBuilder(const SampleComparator<SampleEntry>& sample_comparator,
134                              ThreadTree* thread_tree,
135                              const std::unordered_map<uint64_t, size_t>& event_id_to_attr_index)
136       : SampleTreeBuilder(sample_comparator),
137         thread_tree_(thread_tree),
138         event_id_to_attr_index_(event_id_to_attr_index),
139         total_samples_(0),
140         total_period_(0),
141         total_error_callchains_(0) {}
142 
SetFilters(const std::unordered_set<int> & cpu_filter,const std::unordered_set<std::string> & comm_filter,const std::unordered_set<std::string> & dso_filter,const std::unordered_set<std::string> & symbol_filter)143   void SetFilters(const std::unordered_set<int>& cpu_filter,
144                   const std::unordered_set<std::string>& comm_filter,
145                   const std::unordered_set<std::string>& dso_filter,
146                   const std::unordered_set<std::string>& symbol_filter) {
147     cpu_filter_ = cpu_filter;
148     comm_filter_ = comm_filter;
149     dso_filter_ = dso_filter;
150     symbol_filter_ = symbol_filter;
151   }
152 
SetEventName(const std::string & event_name)153   void SetEventName(const std::string& event_name) { event_name_ = event_name; }
154 
GetSampleTree()155   SampleTree GetSampleTree() {
156     AddCallChainDuplicateInfo();
157     SampleTree sample_tree;
158     sample_tree.samples = GetSamples();
159     sample_tree.total_samples = total_samples_;
160     sample_tree.total_period = total_period_;
161     sample_tree.total_error_callchains = total_error_callchains_;
162     sample_tree.event_name = event_name_;
163     return sample_tree;
164   }
165 
ReportCmdProcessSampleRecord(std::shared_ptr<SampleRecord> & r)166   virtual void ReportCmdProcessSampleRecord(std::shared_ptr<SampleRecord>& r) {
167     return ProcessSampleRecord(*r);
168   }
169 
ReportCmdProcessSampleRecord(const SampleRecord & r)170   virtual void ReportCmdProcessSampleRecord(const SampleRecord& r) {
171     return ProcessSampleRecord(r);
172   }
173 
174  protected:
175   virtual uint64_t GetPeriod(const SampleRecord& r) = 0;
176 
CreateSample(const SampleRecord & r,bool in_kernel,AccInfo * acc_info)177   SampleEntry* CreateSample(const SampleRecord& r, bool in_kernel, AccInfo* acc_info) override {
178     const ThreadEntry* thread = thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
179     const MapEntry* map = thread_tree_->FindMap(thread, r.ip_data.ip, in_kernel);
180     uint64_t vaddr_in_file;
181     const Symbol* symbol = thread_tree_->FindSymbol(map, r.ip_data.ip, &vaddr_in_file);
182     uint64_t period = GetPeriod(r);
183     acc_info->period = period;
184     std::vector<uint64_t> counts = GetCountsForSample(r);
185     acc_info->counts = counts;
186     std::unique_ptr<SampleEntry> sample(new SampleEntry(r.time_data.time, period, 0, 1, r.Cpu(),
187                                                         thread, map, symbol, vaddr_in_file, counts,
188                                                         counts));
189     return InsertSample(std::move(sample));
190   }
191 
CreateBranchSample(const SampleRecord & r,const BranchStackItemType & item)192   SampleEntry* CreateBranchSample(const SampleRecord& r, const BranchStackItemType& item) override {
193     const ThreadEntry* thread = thread_tree_->FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
194     const MapEntry* from_map = thread_tree_->FindMap(thread, item.from);
195     uint64_t from_vaddr_in_file;
196     const Symbol* from_symbol = thread_tree_->FindSymbol(from_map, item.from, &from_vaddr_in_file);
197     const MapEntry* to_map = thread_tree_->FindMap(thread, item.to);
198     uint64_t to_vaddr_in_file;
199     const Symbol* to_symbol = thread_tree_->FindSymbol(to_map, item.to, &to_vaddr_in_file);
200     std::unique_ptr<SampleEntry> sample(new SampleEntry(r.time_data.time, r.period_data.period, 0,
201                                                         1, r.Cpu(), thread, to_map, to_symbol,
202                                                         to_vaddr_in_file, {}, {}));
203     sample->branch_from.map = from_map;
204     sample->branch_from.symbol = from_symbol;
205     sample->branch_from.vaddr_in_file = from_vaddr_in_file;
206     sample->branch_from.flags = item.flags;
207     return InsertSample(std::move(sample));
208   }
209 
CreateCallChainSample(const ThreadEntry * thread,const SampleEntry * sample,uint64_t ip,bool in_kernel,const std::vector<SampleEntry * > & callchain,const AccInfo & acc_info)210   SampleEntry* CreateCallChainSample(const ThreadEntry* thread, const SampleEntry* sample,
211                                      uint64_t ip, bool in_kernel,
212                                      const std::vector<SampleEntry*>& callchain,
213                                      const AccInfo& acc_info) override {
214     const MapEntry* map = thread_tree_->FindMap(thread, ip, in_kernel);
215     if (thread_tree_->IsUnknownDso(map->dso)) {
216       // The unwinders can give wrong ip addresses, which can't map to a valid dso. Skip them.
217       total_error_callchains_++;
218       return nullptr;
219     }
220     uint64_t vaddr_in_file;
221     const Symbol* symbol = thread_tree_->FindSymbol(map, ip, &vaddr_in_file);
222     std::unique_ptr<SampleEntry> callchain_sample(
223         new SampleEntry(sample->time, 0, acc_info.period, 0, sample->cpu, thread, map, symbol,
224                         vaddr_in_file, {}, acc_info.counts));
225     callchain_sample->thread_comm = sample->thread_comm;
226     return InsertCallChainSample(std::move(callchain_sample), callchain);
227   }
228 
GetThreadOfSample(SampleEntry * sample)229   const ThreadEntry* GetThreadOfSample(SampleEntry* sample) override {
230     return thread_tree_->FindThreadOrNew(sample->pid, sample->tid);
231   }
232 
GetPeriodForCallChain(const AccInfo & acc_info)233   uint64_t GetPeriodForCallChain(const AccInfo& acc_info) override { return acc_info.period; }
234 
FilterSample(const SampleEntry * sample)235   bool FilterSample(const SampleEntry* sample) override {
236     if (!cpu_filter_.empty() && cpu_filter_.count(sample->cpu) == 0) {
237       return false;
238     }
239     if (!comm_filter_.empty() && comm_filter_.count(sample->thread_comm) == 0) {
240       return false;
241     }
242     if (!dso_filter_.empty() && dso_filter_.count(sample->map->dso->GetReportPath().data()) == 0) {
243       return false;
244     }
245     if (!symbol_filter_.empty() && symbol_filter_.count(sample->symbol->DemangledName()) == 0) {
246       return false;
247     }
248     return true;
249   }
250 
UpdateSummary(const SampleEntry * sample)251   void UpdateSummary(const SampleEntry* sample) override {
252     total_samples_ += sample->sample_count;
253     total_period_ += sample->period;
254   }
255 
MergeSample(SampleEntry * sample1,SampleEntry * sample2)256   void MergeSample(SampleEntry* sample1, SampleEntry* sample2) override {
257     sample1->period += sample2->period;
258     sample1->accumulated_period += sample2->accumulated_period;
259     sample1->sample_count += sample2->sample_count;
260     if (sample1->counts.size() < sample2->counts.size()) {
261       sample1->counts.resize(sample2->counts.size(), 0);
262     }
263     for (size_t i = 0; i < sample2->counts.size(); i++) {
264       sample1->counts[i] += sample2->counts[i];
265     }
266     if (sample1->acc_counts.size() < sample2->acc_counts.size()) {
267       sample1->acc_counts.resize(sample2->acc_counts.size(), 0);
268     }
269     for (size_t i = 0; i < sample2->acc_counts.size(); i++) {
270       sample1->acc_counts[i] += sample2->acc_counts[i];
271     }
272   }
273 
274  private:
GetCountsForSample(const SampleRecord & r)275   std::vector<uint64_t> GetCountsForSample(const SampleRecord& r) {
276     CHECK_EQ(r.read_data.counts.size(), r.read_data.ids.size());
277     std::vector<uint64_t> res(r.read_data.counts.size(), 0);
278     for (size_t i = 0; i < r.read_data.counts.size(); i++) {
279       uint64_t event_id = r.read_data.ids[i];
280       uint64_t count = r.read_data.counts[i];
281       uint64_t& last_count = event_id_count_map_[event_id];
282       uint64_t added_count = count - last_count;
283       last_count = count;
284       auto it = event_id_to_attr_index_.find(event_id);
285       CHECK(it != event_id_to_attr_index_.end());
286       CHECK_LT(it->second, res.size());
287       // Count for the current sample is the added event count after generating the previous sample.
288       res[it->second] = added_count;
289     }
290     return res;
291   }
292 
293   ThreadTree* thread_tree_;
294   const std::unordered_map<uint64_t, size_t>& event_id_to_attr_index_;
295 
296   std::unordered_set<int> cpu_filter_;
297   std::unordered_set<std::string> comm_filter_;
298   std::unordered_set<std::string> dso_filter_;
299   std::unordered_set<std::string> symbol_filter_;
300 
301   uint64_t total_samples_;
302   uint64_t total_period_;
303   uint64_t total_error_callchains_;
304 
305   std::string event_name_;
306   // Map from event_id to its last event count.
307   std::unordered_map<uint64_t, uint64_t> event_id_count_map_;
308 };
309 
310 // Build sample tree based on event count in each sample.
311 class EventCountSampleTreeBuilder : public ReportCmdSampleTreeBuilder {
312  public:
EventCountSampleTreeBuilder(const SampleComparator<SampleEntry> & sample_comparator,ThreadTree * thread_tree,const std::unordered_map<uint64_t,size_t> & event_id_to_attr_index)313   EventCountSampleTreeBuilder(const SampleComparator<SampleEntry>& sample_comparator,
314                               ThreadTree* thread_tree,
315                               const std::unordered_map<uint64_t, size_t>& event_id_to_attr_index)
316       : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree, event_id_to_attr_index) {}
317 
318  protected:
GetPeriod(const SampleRecord & r)319   uint64_t GetPeriod(const SampleRecord& r) override { return r.period_data.period; }
320 };
321 
322 // Build sample tree based on the time difference between current sample and next sample.
323 class TimestampSampleTreeBuilder : public ReportCmdSampleTreeBuilder {
324  public:
TimestampSampleTreeBuilder(const SampleComparator<SampleEntry> & sample_comparator,ThreadTree * thread_tree,const std::unordered_map<uint64_t,size_t> & event_id_to_attr_index)325   TimestampSampleTreeBuilder(const SampleComparator<SampleEntry>& sample_comparator,
326                              ThreadTree* thread_tree,
327                              const std::unordered_map<uint64_t, size_t>& event_id_to_attr_index)
328       : ReportCmdSampleTreeBuilder(sample_comparator, thread_tree, event_id_to_attr_index) {}
329 
ReportCmdProcessSampleRecord(std::shared_ptr<SampleRecord> & r)330   void ReportCmdProcessSampleRecord(std::shared_ptr<SampleRecord>& r) override {
331     pid_t tid = static_cast<pid_t>(r->tid_data.tid);
332     auto it = next_sample_cache_.find(tid);
333     if (it == next_sample_cache_.end()) {
334       next_sample_cache_[tid] = r;
335     } else {
336       std::shared_ptr<SampleRecord> cur = it->second;
337       it->second = r;
338       ProcessSampleRecord(*cur);
339     }
340   }
341 
342  protected:
GetPeriod(const SampleRecord & r)343   uint64_t GetPeriod(const SampleRecord& r) override {
344     auto it = next_sample_cache_.find(r.tid_data.tid);
345     CHECK(it != next_sample_cache_.end());
346     // Normally the samples are sorted by time, but check here for safety.
347     if (it->second->time_data.time > r.time_data.time) {
348       return it->second->time_data.time - r.time_data.time;
349     }
350     return 1u;
351   }
352 
353  private:
354   std::unordered_map<pid_t, std::shared_ptr<SampleRecord>> next_sample_cache_;
355 };
356 
357 struct SampleTreeBuilderOptions {
358   SampleComparator<SampleEntry> comparator;
359   ThreadTree* thread_tree;
360   std::unordered_set<std::string> comm_filter;
361   std::unordered_set<std::string> dso_filter;
362   std::unordered_set<std::string> symbol_filter;
363   std::unordered_set<int> cpu_filter;
364   bool use_branch_address;
365   bool accumulate_callchain;
366   bool build_callchain;
367   bool use_caller_as_callchain_root;
368   bool trace_offcpu;
369 
CreateSampleTreeBuildersimpleperf::__anon602564b30111::SampleTreeBuilderOptions370   std::unique_ptr<ReportCmdSampleTreeBuilder> CreateSampleTreeBuilder(
371       const RecordFileReader& reader) {
372     std::unique_ptr<ReportCmdSampleTreeBuilder> builder;
373     if (trace_offcpu) {
374       builder.reset(new TimestampSampleTreeBuilder(comparator, thread_tree, reader.EventIdMap()));
375     } else {
376       builder.reset(new EventCountSampleTreeBuilder(comparator, thread_tree, reader.EventIdMap()));
377     }
378     builder->SetFilters(cpu_filter, comm_filter, dso_filter, symbol_filter);
379     builder->SetBranchSampleOption(use_branch_address);
380     builder->SetCallChainSampleOptions(accumulate_callchain, build_callchain,
381                                        use_caller_as_callchain_root);
382     return builder;
383   }
384 };
385 
386 using ReportCmdSampleTreeSorter = SampleTreeSorter<SampleEntry>;
387 using ReportCmdSampleTreeDisplayer = SampleTreeDisplayer<SampleEntry, SampleTree>;
388 
389 using ReportCmdCallgraphDisplayer = CallgraphDisplayer<SampleEntry, CallChainNode<SampleEntry>>;
390 
391 class ReportCmdCallgraphDisplayerWithVaddrInFile : public ReportCmdCallgraphDisplayer {
392  protected:
PrintSampleName(const SampleEntry * sample)393   std::string PrintSampleName(const SampleEntry* sample) override {
394     return android::base::StringPrintf("%s [+0x%" PRIx64 "]", sample->symbol->DemangledName(),
395                                        sample->vaddr_in_file);
396   }
397 };
398 
399 class ReportCommand : public Command {
400  public:
ReportCommand()401   ReportCommand()
402       : Command(
403             "report", "report sampling information in perf.data",
404             // clang-format off
405 "Usage: simpleperf report [options]\n"
406 "The default options are: -i perf.data --sort comm,pid,tid,dso,symbol.\n"
407 "-b    Use the branch-to addresses in sampled take branches instead of the\n"
408 "      instruction addresses. Only valid for perf.data recorded with -b/-j\n"
409 "      option.\n"
410 "--children    Print the overhead accumulated by appearing in the callchain.\n"
411 "              In the report, Children column shows overhead for a symbol and functions called\n"
412 "              by the symbol, while Self column shows overhead for the symbol itself.\n"
413 "--csv                     Report in csv format.\n"
414 "--csv-separator <sep>     Set separator for csv columns. Default is ','.\n"
415 "--full-callgraph  Print full call graph. Used with -g option. By default,\n"
416 "                  brief call graph is printed.\n"
417 "-g [callee|caller]    Print call graph. If callee mode is used, the graph\n"
418 "                      shows how functions are called from others. Otherwise,\n"
419 "                      the graph shows how functions call others.\n"
420 "                      Default is caller mode.\n"
421 "-i <file>  Specify path of record file, default is perf.data.\n"
422 "--kallsyms <file>     Set the file to read kernel symbols.\n"
423 "--max-stack <frames>  Set max stack frames shown when printing call graph.\n"
424 "-n         Print the sample count for each item.\n"
425 "--no-demangle         Don't demangle symbol names.\n"
426 "--no-show-ip          Don't show vaddr in file for unknown symbols.\n"
427 "-o report_file_name   Set report file name, default is stdout.\n"
428 "--percent-limit <percent>  Set min percentage in report entries and call graphs.\n"
429 "--print-event-count   Print event counts for each item. Additional events can be added by\n"
430 "                      --add-counter in record cmd.\n"
431 "--raw-period          Report period count instead of period percentage.\n"
432 "--sort key1,key2,...  Select keys used to group samples into report entries. Samples having\n"
433 "                      the same key values are aggregated into one report entry. Each report\n"
434 "                      entry is printed in one row, having columns to show key values.\n"
435 "                      Possible keys include:\n"
436 "                        pid             -- process id\n"
437 "                        tid             -- thread id\n"
438 "                        comm            -- thread name (can be changed during\n"
439 "                                           the lifetime of a thread)\n"
440 "                        dso             -- shared library\n"
441 "                        symbol          -- function name in the shared library\n"
442 "                        vaddr_in_file   -- virtual address in the shared\n"
443 "                                           library\n"
444 "                      Keys can only be used with -b option:\n"
445 "                        dso_from        -- shared library branched from\n"
446 "                        dso_to          -- shared library branched to\n"
447 "                        symbol_from     -- name of function branched from\n"
448 "                        symbol_to       -- name of function branched to\n"
449 "                      The default sort keys are:\n"
450 "                        comm,pid,tid,dso,symbol\n"
451 "--symfs <dir>         Look for files with symbols relative to this directory.\n"
452 "--symdir <dir>        Look for files with symbols in a directory recursively.\n"
453 "--vmlinux <file>      Parse kernel symbols from <file>.\n"
454 "\n"
455 "Sample filter options:\n"
456 "--comms comm1,comm2,...          Report only for threads with selected names.\n"
457 "--dsos dso1,dso2,...             Report only for selected dsos.\n"
458 "--pids pid1,pid2,...             Same as '--include-pid'.\n"
459 "--symbols symbol1;symbol2;...    Report only for selected symbols.\n"
460 "--tids tid1,tid2,...             Same as '--include-tid'.\n"
461 RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING
462             // clang-format on
463             ),
464         record_filename_("perf.data"),
465         record_file_arch_(GetTargetArch()),
466         use_branch_address_(false),
467         accumulate_callchain_(false),
468         print_callgraph_(false),
469         callgraph_show_callee_(false),
470         callgraph_max_stack_(UINT32_MAX),
471         percent_limit_(0),
472         raw_period_(false),
473         brief_callgraph_(true),
474         trace_offcpu_(false),
475         sched_switch_attr_id_(0u),
476         record_filter_(thread_tree_) {}
477 
478   bool Run(const std::vector<std::string>& args);
479 
480  private:
481   bool ParseOptions(const std::vector<std::string>& args);
482   bool BuildSampleComparatorAndDisplayer();
483   bool ReadMetaInfoFromRecordFile();
484   bool ReadEventAttrFromRecordFile();
485   bool ReadFeaturesFromRecordFile();
486   bool ReadSampleTreeFromRecordFile();
487   bool ProcessRecord(std::unique_ptr<Record> record);
488   void ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record, size_t attr_id);
489   bool ProcessTracingData(const std::vector<char>& data);
490   bool PrintReport();
491   void PrintReportContext(FILE* fp);
492 
493   std::string record_filename_;
494   ArchType record_file_arch_;
495   std::unique_ptr<RecordFileReader> record_file_reader_;
496   std::vector<perf_event_attr> event_attrs_;
497   std::vector<std::string> attr_names_;
498   ThreadTree thread_tree_;
499   // Create a SampleTreeBuilder and SampleTree for each event_attr.
500   std::vector<SampleTree> sample_tree_;
501   SampleTreeBuilderOptions sample_tree_builder_options_;
502   std::vector<std::unique_ptr<ReportCmdSampleTreeBuilder>> sample_tree_builder_;
503 
504   std::unique_ptr<ReportCmdSampleTreeSorter> sample_tree_sorter_;
505   std::unique_ptr<ReportCmdSampleTreeDisplayer> sample_tree_displayer_;
506   bool use_branch_address_;
507   std::string record_cmdline_;
508   bool accumulate_callchain_;
509   bool print_callgraph_;
510   bool callgraph_show_callee_;
511   uint32_t callgraph_max_stack_;
512   double percent_limit_;
513   bool raw_period_;
514   bool brief_callgraph_;
515   bool trace_offcpu_;
516   size_t sched_switch_attr_id_;
517   bool report_csv_ = false;
518   std::string csv_separator_ = ",";
519   bool print_sample_count_ = false;
520   bool print_event_count_ = false;
521   std::vector<std::string> sort_keys_;
522   std::string report_filename_;
523   RecordFilter record_filter_;
524 };
525 
Run(const std::vector<std::string> & args)526 bool ReportCommand::Run(const std::vector<std::string>& args) {
527   // 1. Parse options.
528   if (!ParseOptions(args)) {
529     return false;
530   }
531 
532   // 2. Read record file and build SampleTree.
533   record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
534   if (record_file_reader_ == nullptr) {
535     return false;
536   }
537   if (!ReadMetaInfoFromRecordFile()) {
538     return false;
539   }
540   if (!ReadEventAttrFromRecordFile()) {
541     return false;
542   }
543   if (!BuildSampleComparatorAndDisplayer()) {
544     return false;
545   }
546   // Read features first to prepare build ids used when building SampleTree.
547   if (!ReadFeaturesFromRecordFile()) {
548     return false;
549   }
550   ScopedCurrentArch scoped_arch(record_file_arch_);
551   if (!ReadSampleTreeFromRecordFile()) {
552     return false;
553   }
554 
555   // 3. Show collected information.
556   if (!PrintReport()) {
557     return false;
558   }
559 
560   return true;
561 }
562 
ParseOptions(const std::vector<std::string> & args)563 bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
564   OptionFormatMap option_formats = {
565       {"-b", {OptionValueType::NONE, OptionType::SINGLE}},
566       {"--children", {OptionValueType::NONE, OptionType::SINGLE}},
567       {"--comms", {OptionValueType::STRING, OptionType::MULTIPLE}},
568       {"--cpu", {OptionValueType::STRING, OptionType::MULTIPLE}},
569       {"--csv", {OptionValueType::NONE, OptionType::SINGLE}},
570       {"--csv-separator", {OptionValueType::STRING, OptionType::SINGLE}},
571       {"--dsos", {OptionValueType::STRING, OptionType::MULTIPLE}},
572       {"--full-callgraph", {OptionValueType::NONE, OptionType::SINGLE}},
573       {"-g", {OptionValueType::OPT_STRING, OptionType::SINGLE}},
574       {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
575       {"--kallsyms", {OptionValueType::STRING, OptionType::SINGLE}},
576       {"--max-stack", {OptionValueType::UINT, OptionType::SINGLE}},
577       {"-n", {OptionValueType::NONE, OptionType::SINGLE}},
578       {"--no-demangle", {OptionValueType::NONE, OptionType::SINGLE}},
579       {"--no-show-ip", {OptionValueType::NONE, OptionType::SINGLE}},
580       {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
581       {"--percent-limit", {OptionValueType::DOUBLE, OptionType::SINGLE}},
582       {"--pids", {OptionValueType::STRING, OptionType::MULTIPLE}},
583       {"--print-event-count", {OptionValueType::NONE, OptionType::SINGLE}},
584       {"--tids", {OptionValueType::STRING, OptionType::MULTIPLE}},
585       {"--raw-period", {OptionValueType::NONE, OptionType::SINGLE}},
586       {"--sort", {OptionValueType::STRING, OptionType::SINGLE}},
587       {"--symbols", {OptionValueType::STRING, OptionType::MULTIPLE}},
588       {"--symfs", {OptionValueType::STRING, OptionType::SINGLE}},
589       {"--symdir", {OptionValueType::STRING, OptionType::SINGLE}},
590       {"--vmlinux", {OptionValueType::STRING, OptionType::SINGLE}},
591   };
592   OptionFormatMap record_filter_options = GetRecordFilterOptionFormats(false);
593   option_formats.insert(record_filter_options.begin(), record_filter_options.end());
594 
595   OptionValueMap options;
596   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
597   if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
598     return false;
599   }
600 
601   // Process options.
602   use_branch_address_ = options.PullBoolValue("-b");
603   accumulate_callchain_ = options.PullBoolValue("--children");
604   for (const OptionValue& value : options.PullValues("--comms")) {
605     std::vector<std::string> strs = Split(*value.str_value, ",");
606     sample_tree_builder_options_.comm_filter.insert(strs.begin(), strs.end());
607   }
608   if (!record_filter_.ParseOptions(options)) {
609     return false;
610   }
611   for (const OptionValue& value : options.PullValues("--cpu")) {
612     if (auto cpus = GetCpusFromString(*value.str_value); cpus) {
613       sample_tree_builder_options_.cpu_filter.insert(cpus->begin(), cpus->end());
614     } else {
615       return false;
616     }
617   }
618   report_csv_ = options.PullBoolValue("--csv");
619   options.PullStringValue("--csv-separator", &csv_separator_);
620   for (const OptionValue& value : options.PullValues("--dsos")) {
621     std::vector<std::string> strs = Split(*value.str_value, ",");
622     sample_tree_builder_options_.dso_filter.insert(strs.begin(), strs.end());
623   }
624   brief_callgraph_ = !options.PullBoolValue("--full-callgraph");
625 
626   if (auto value = options.PullValue("-g"); value) {
627     print_callgraph_ = true;
628     accumulate_callchain_ = true;
629     if (value->str_value != nullptr) {
630       if (*value->str_value == "callee") {
631         callgraph_show_callee_ = true;
632       } else if (*value->str_value == "caller") {
633         callgraph_show_callee_ = false;
634       } else {
635         LOG(ERROR) << "Unknown argument with -g option: " << *value->str_value;
636         return false;
637       }
638     }
639   }
640   options.PullStringValue("-i", &record_filename_);
641   if (auto value = options.PullValue("--kallsyms"); value) {
642     std::string kallsyms;
643     if (!android::base::ReadFileToString(*value->str_value, &kallsyms)) {
644       LOG(ERROR) << "Can't read kernel symbols from " << *value->str_value;
645       return false;
646     }
647     Dso::SetKallsyms(kallsyms);
648   }
649   if (!options.PullUintValue("--max-stack", &callgraph_max_stack_)) {
650     return false;
651   }
652   print_sample_count_ = options.PullBoolValue("-n");
653 
654   Dso::SetDemangle(!options.PullBoolValue("--no-demangle"));
655 
656   if (!options.PullBoolValue("--no-show-ip")) {
657     thread_tree_.ShowIpForUnknownSymbol();
658   }
659 
660   options.PullStringValue("-o", &report_filename_);
661   if (!options.PullDoubleValue("--percent-limit", &percent_limit_, 0)) {
662     return false;
663   }
664 
665   if (auto strs = options.PullStringValues("--pids"); !strs.empty()) {
666     if (auto pids = GetPidsFromStrings(strs, false, false); pids) {
667       record_filter_.AddPids(pids.value(), false);
668     } else {
669       return false;
670     }
671   }
672   print_event_count_ = options.PullBoolValue("--print-event-count");
673   for (const OptionValue& value : options.PullValues("--tids")) {
674     if (auto tids = GetTidsFromString(*value.str_value, false); tids) {
675       record_filter_.AddTids(tids.value(), false);
676     } else {
677       return false;
678     }
679   }
680   raw_period_ = options.PullBoolValue("--raw-period");
681 
682   sort_keys_ = {"comm", "pid", "tid", "dso", "symbol"};
683   if (auto value = options.PullValue("--sort"); value) {
684     sort_keys_ = Split(*value->str_value, ",");
685   }
686 
687   for (const OptionValue& value : options.PullValues("--symbols")) {
688     std::vector<std::string> symbols = Split(*value.str_value, ";");
689     sample_tree_builder_options_.symbol_filter.insert(symbols.begin(), symbols.end());
690   }
691 
692   if (auto value = options.PullValue("--symfs"); value) {
693     if (!Dso::SetSymFsDir(*value->str_value)) {
694       return false;
695     }
696   }
697   if (auto value = options.PullValue("--symdir"); value) {
698     if (!Dso::AddSymbolDir(*value->str_value)) {
699       return false;
700     }
701   }
702   if (auto value = options.PullValue("--vmlinux"); value) {
703     Dso::SetVmlinux(*value->str_value);
704   }
705   CHECK(options.values.empty());
706   return true;
707 }
708 
BuildSampleComparatorAndDisplayer()709 bool ReportCommand::BuildSampleComparatorAndDisplayer() {
710   SampleDisplayer<SampleEntry, SampleTree> displayer;
711   displayer.SetReportFormat(report_csv_, csv_separator_);
712   SampleComparator<SampleEntry> comparator;
713 
714   if (accumulate_callchain_) {
715     if (raw_period_) {
716       displayer.AddDisplayFunction("Children", DisplayAccumulatedPeriod<SampleEntry>);
717       displayer.AddDisplayFunction("Self", DisplaySelfPeriod<SampleEntry>);
718     } else {
719       displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead<SampleEntry, SampleTree>);
720       displayer.AddDisplayFunction("Self", DisplaySelfOverhead<SampleEntry, SampleTree>);
721     }
722   } else {
723     if (raw_period_) {
724       displayer.AddDisplayFunction("Overhead", DisplaySelfPeriod<SampleEntry>);
725     } else {
726       displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead<SampleEntry, SampleTree>);
727     }
728   }
729   if (print_sample_count_) {
730     displayer.AddDisplayFunction("Sample", DisplaySampleCount<SampleEntry>);
731   }
732   if (print_event_count_) {
733     if (event_attrs_.size() == attr_names_.size()) {
734       // Without additional counters, counts field isn't available. So print period field instead.
735       if (accumulate_callchain_) {
736         displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod<SampleEntry>);
737         displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod<SampleEntry>);
738       } else {
739         displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod<SampleEntry>);
740       }
741     } else {
742       // With additional counters, print counts field.
743       for (size_t i = 0; i < attr_names_.size(); i++) {
744         auto self_event_count_fn = [i](const SampleEntry* s) {
745           return i < s->counts.size() ? std::to_string(s->counts[i]) : "0";
746         };
747         auto acc_event_count_fn = [i](const SampleEntry* s) {
748           return i < s->acc_counts.size() ? std::to_string(s->acc_counts[i]) : "0";
749         };
750         if (accumulate_callchain_) {
751           displayer.AddDisplayFunction("AccEventCount_" + attr_names_[i], acc_event_count_fn);
752           displayer.AddDisplayFunction("SelfEventCount_" + attr_names_[i], self_event_count_fn);
753         } else {
754           displayer.AddDisplayFunction("EventCount_" + attr_names_[i], self_event_count_fn);
755         }
756       }
757     }
758   }
759 
760   for (auto& key : sort_keys_) {
761     if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) {
762       LOG(ERROR) << "sort key '" << key << "' can only be used with -b option.";
763       return false;
764     }
765     if (key == "pid") {
766       comparator.AddCompareFunction(ComparePid);
767       displayer.AddDisplayFunction("Pid", DisplayPid<SampleEntry>);
768     } else if (key == "tid") {
769       comparator.AddCompareFunction(CompareTid);
770       displayer.AddDisplayFunction("Tid", DisplayTid<SampleEntry>);
771     } else if (key == "comm") {
772       comparator.AddCompareFunction(CompareComm);
773       displayer.AddDisplayFunction("Command", DisplayComm<SampleEntry>);
774     } else if (key == "dso") {
775       comparator.AddCompareFunction(CompareDso);
776       displayer.AddDisplayFunction("Shared Object", DisplayDso<SampleEntry>);
777     } else if (key == "symbol") {
778       comparator.AddCompareFunction(CompareSymbol);
779       displayer.AddDisplayFunction("Symbol", DisplaySymbol<SampleEntry>);
780     } else if (key == "vaddr_in_file") {
781       comparator.AddCompareFunction(CompareVaddrInFile);
782       displayer.AddDisplayFunction("VaddrInFile", DisplayVaddrInFile<SampleEntry>);
783     } else if (key == "dso_from") {
784       comparator.AddCompareFunction(CompareDsoFrom);
785       displayer.AddDisplayFunction("Source Shared Object", DisplayDsoFrom<SampleEntry>);
786     } else if (key == "dso_to") {
787       comparator.AddCompareFunction(CompareDso);
788       displayer.AddDisplayFunction("Target Shared Object", DisplayDso<SampleEntry>);
789     } else if (key == "symbol_from") {
790       comparator.AddCompareFunction(CompareSymbolFrom);
791       displayer.AddDisplayFunction("Source Symbol", DisplaySymbolFrom<SampleEntry>);
792     } else if (key == "symbol_to") {
793       comparator.AddCompareFunction(CompareSymbol);
794       displayer.AddDisplayFunction("Target Symbol", DisplaySymbol<SampleEntry>);
795     } else {
796       LOG(ERROR) << "Unknown sort key: " << key;
797       return false;
798     }
799   }
800 
801   // Reporting with --csv will add event count and event name columns. But if --print-event-count is
802   // used, there is no need to duplicate printing event counts.
803   if (report_csv_ && !print_event_count_) {
804     if (accumulate_callchain_) {
805       displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod<SampleEntry>);
806       displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod<SampleEntry>);
807     } else {
808       displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod<SampleEntry>);
809     }
810     displayer.AddDisplayFunction("EventName", DisplayEventName);
811   }
812 
813   if (print_callgraph_) {
814     bool has_symbol_key = false;
815     bool has_vaddr_in_file_key = false;
816     for (const auto& key : sort_keys_) {
817       if (key == "symbol") {
818         has_symbol_key = true;
819       } else if (key == "vaddr_in_file") {
820         has_vaddr_in_file_key = true;
821       }
822     }
823     if (has_symbol_key) {
824       if (has_vaddr_in_file_key) {
825         displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayerWithVaddrInFile());
826       } else {
827         displayer.AddExclusiveDisplayFunction(
828             ReportCmdCallgraphDisplayer(callgraph_max_stack_, percent_limit_, brief_callgraph_));
829       }
830     }
831   }
832 
833   if (percent_limit_ != 0.0) {
834     displayer.SetFilterFunction([this](const SampleEntry* sample, const SampleTree* sample_tree) {
835       uint64_t total_period = sample->period + sample->accumulated_period;
836       return total_period >= sample_tree->total_period * percent_limit_ / 100.0;
837     });
838   }
839 
840   sample_tree_builder_options_.comparator = comparator;
841   sample_tree_builder_options_.thread_tree = &thread_tree_;
842 
843   SampleComparator<SampleEntry> sort_comparator;
844   sort_comparator.AddCompareFunction(CompareTotalPeriod);
845   if (print_callgraph_) {
846     sort_comparator.AddCompareFunction(CompareCallGraphDuplicated);
847   }
848   sort_comparator.AddCompareFunction(ComparePeriod);
849   sort_comparator.AddComparator(comparator);
850   sample_tree_sorter_.reset(new ReportCmdSampleTreeSorter(sort_comparator));
851   sample_tree_displayer_.reset(new ReportCmdSampleTreeDisplayer(displayer));
852   return true;
853 }
854 
ReadMetaInfoFromRecordFile()855 bool ReportCommand::ReadMetaInfoFromRecordFile() {
856   auto& meta_info = record_file_reader_->GetMetaInfoFeature();
857   if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
858     trace_offcpu_ = it->second == "true";
859   }
860   return record_filter_.CheckClock(record_file_reader_->GetClockId());
861 }
862 
ReadEventAttrFromRecordFile()863 bool ReportCommand::ReadEventAttrFromRecordFile() {
864   for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
865     const perf_event_attr& attr = attr_with_id.attr;
866     attr_names_.emplace_back(GetEventNameByAttr(attr));
867 
868     // There are no samples for events added by --add-counter. So skip them.
869     if ((attr.read_format & PERF_FORMAT_GROUP) && (attr.freq == 0) &&
870         (attr.sample_period == INFINITE_SAMPLE_PERIOD)) {
871       continue;
872     }
873     event_attrs_.emplace_back(attr);
874   }
875   if (use_branch_address_) {
876     bool has_branch_stack = true;
877     for (const auto& attr : event_attrs_) {
878       if ((attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
879         has_branch_stack = false;
880         break;
881       }
882     }
883     if (!has_branch_stack) {
884       LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option.";
885       return false;
886     }
887   }
888   if (trace_offcpu_) {
889     size_t i;
890     for (i = 0; i < event_attrs_.size(); ++i) {
891       if (attr_names_[i] == "sched:sched_switch") {
892         break;
893       }
894     }
895     CHECK_NE(i, event_attrs_.size());
896     sched_switch_attr_id_ = i;
897   }
898   return true;
899 }
900 
ReadFeaturesFromRecordFile()901 bool ReportCommand::ReadFeaturesFromRecordFile() {
902   if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
903     return false;
904   }
905 
906   std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
907   if (!arch.empty()) {
908     record_file_arch_ = GetArchType(arch);
909     if (record_file_arch_ == ARCH_UNSUPPORTED) {
910       return false;
911     }
912   }
913 
914   std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
915   if (!cmdline.empty()) {
916     record_cmdline_ = android::base::Join(cmdline, ' ');
917   }
918   if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
919     std::vector<char> tracing_data;
920     if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_TRACING_DATA,
921                                                  &tracing_data)) {
922       return false;
923     }
924     if (!ProcessTracingData(tracing_data)) {
925       return false;
926     }
927   }
928   return true;
929 }
930 
ReadSampleTreeFromRecordFile()931 bool ReportCommand::ReadSampleTreeFromRecordFile() {
932   sample_tree_builder_options_.use_branch_address = use_branch_address_;
933   sample_tree_builder_options_.accumulate_callchain = accumulate_callchain_;
934   sample_tree_builder_options_.build_callchain = print_callgraph_;
935   sample_tree_builder_options_.use_caller_as_callchain_root = !callgraph_show_callee_;
936   sample_tree_builder_options_.trace_offcpu = trace_offcpu_;
937 
938   for (size_t i = 0; i < event_attrs_.size(); ++i) {
939     sample_tree_builder_.push_back(
940         sample_tree_builder_options_.CreateSampleTreeBuilder(*record_file_reader_));
941     sample_tree_builder_.back()->SetEventName(attr_names_[i]);
942     OfflineUnwinder* unwinder = sample_tree_builder_.back()->GetUnwinder();
943     if (unwinder != nullptr) {
944       unwinder->LoadMetaInfo(record_file_reader_->GetMetaInfoFeature());
945     }
946   }
947 
948   if (!record_file_reader_->ReadDataSection(
949           [this](std::unique_ptr<Record> record) { return ProcessRecord(std::move(record)); })) {
950     return false;
951   }
952   for (size_t i = 0; i < sample_tree_builder_.size(); ++i) {
953     sample_tree_.push_back(sample_tree_builder_[i]->GetSampleTree());
954     sample_tree_sorter_->Sort(sample_tree_.back().samples, print_callgraph_);
955   }
956   return true;
957 }
958 
ProcessRecord(std::unique_ptr<Record> record)959 bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
960   thread_tree_.Update(*record);
961   if (record->type() == PERF_RECORD_SAMPLE) {
962     if (!record_filter_.Check(static_cast<SampleRecord&>(*record))) {
963       return true;
964     }
965     size_t attr_id = record_file_reader_->GetAttrIndexOfRecord(record.get());
966     if (!trace_offcpu_) {
967       sample_tree_builder_[attr_id]->ReportCmdProcessSampleRecord(
968           *static_cast<SampleRecord*>(record.get()));
969     } else {
970       ProcessSampleRecordInTraceOffCpuMode(std::move(record), attr_id);
971     }
972   } else if (record->type() == PERF_RECORD_TRACING_DATA ||
973              record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) {
974     const auto& r = *static_cast<TracingDataRecord*>(record.get());
975     if (!ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size))) {
976       return false;
977     }
978   }
979   return true;
980 }
981 
ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record,size_t attr_id)982 void ReportCommand::ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record,
983                                                          size_t attr_id) {
984   std::shared_ptr<SampleRecord> r(static_cast<SampleRecord*>(record.release()));
985   if (attr_id == sched_switch_attr_id_) {
986     // If this sample belongs to sched_switch event, we should broadcast the offcpu info
987     // to other event types.
988     for (size_t i = 0; i < event_attrs_.size(); ++i) {
989       if (i == sched_switch_attr_id_) {
990         continue;
991       }
992       sample_tree_builder_[i]->ReportCmdProcessSampleRecord(r);
993     }
994   } else {
995     sample_tree_builder_[attr_id]->ReportCmdProcessSampleRecord(r);
996   }
997 }
998 
ProcessTracingData(const std::vector<char> & data)999 bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
1000   auto tracing = Tracing::Create(data);
1001   if (!tracing) {
1002     return false;
1003   }
1004   for (size_t i = 0; i < event_attrs_.size(); i++) {
1005     if (event_attrs_[i].type == PERF_TYPE_TRACEPOINT) {
1006       uint64_t trace_event_id = event_attrs_[i].config;
1007       attr_names_[i] = tracing->GetTracingEventNameHavingId(trace_event_id);
1008     }
1009   }
1010   return true;
1011 }
1012 
PrintReport()1013 bool ReportCommand::PrintReport() {
1014   std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose);
1015   FILE* report_fp = stdout;
1016   if (!report_filename_.empty()) {
1017     report_fp = fopen(report_filename_.c_str(), "w");
1018     if (report_fp == nullptr) {
1019       PLOG(ERROR) << "failed to open file " << report_filename_;
1020       return false;
1021     }
1022     file_handler.reset(report_fp);
1023   }
1024   PrintReportContext(report_fp);
1025   for (size_t i = 0; i < event_attrs_.size(); ++i) {
1026     if (trace_offcpu_ && i == sched_switch_attr_id_) {
1027       continue;
1028     }
1029     if (i != 0) {
1030       fprintf(report_fp, "\n");
1031     }
1032     SampleTree& sample_tree = sample_tree_[i];
1033     fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr_names_[i].c_str(),
1034             event_attrs_[i].type, event_attrs_[i].config);
1035     fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree.total_samples);
1036     if (sample_tree.total_error_callchains != 0) {
1037       fprintf(report_fp, "Error Callchains: %" PRIu64 ", %f%%\n",
1038               sample_tree.total_error_callchains,
1039               sample_tree.total_error_callchains * 100.0 / sample_tree.total_samples);
1040     }
1041     const char* period_prefix = trace_offcpu_ ? "Time in ns" : "Event count";
1042     fprintf(report_fp, "%s: %" PRIu64 "\n\n", period_prefix, sample_tree.total_period);
1043     sample_tree_displayer_->DisplaySamples(report_fp, sample_tree.samples, &sample_tree);
1044   }
1045   fflush(report_fp);
1046   if (ferror(report_fp) != 0) {
1047     PLOG(ERROR) << "print report failed";
1048     return false;
1049   }
1050   return true;
1051 }
1052 
PrintReportContext(FILE * report_fp)1053 void ReportCommand::PrintReportContext(FILE* report_fp) {
1054   if (!record_cmdline_.empty()) {
1055     fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str());
1056   }
1057   fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
1058 }
1059 
1060 }  // namespace
1061 
RegisterReportCommand()1062 void RegisterReportCommand() {
1063   RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
1064 }
1065 
1066 }  // namespace simpleperf
1067