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::__anon4982fd840111::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::__anon4982fd840111::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::__anon4982fd840111::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::__anon4982fd840111::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 "--vmlinux <file> Parse kernel symbols from <file>.\n"
453 "\n"
454 "Sample filter options:\n"
455 "--comms comm1,comm2,... Report only for threads with selected names.\n"
456 "--cpu cpu_item1,cpu_item2,... Report samples on the selected cpus. cpu_item can be cpu\n"
457 " number like 1, or cpu range like 0-3.\n"
458 "--dsos dso1,dso2,... Report only for selected dsos.\n"
459 "--pids pid1,pid2,... Same as '--include-pid'.\n"
460 "--symbols symbol1;symbol2;... Report only for selected symbols.\n"
461 "--tids tid1,tid2,... Same as '--include-tid'.\n"
462 RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING
463 // clang-format on
464 ),
465 record_filename_("perf.data"),
466 record_file_arch_(GetTargetArch()),
467 use_branch_address_(false),
468 accumulate_callchain_(false),
469 print_callgraph_(false),
470 callgraph_show_callee_(false),
471 callgraph_max_stack_(UINT32_MAX),
472 percent_limit_(0),
473 raw_period_(false),
474 brief_callgraph_(true),
475 trace_offcpu_(false),
476 sched_switch_attr_id_(0u),
477 record_filter_(thread_tree_) {}
478
479 bool Run(const std::vector<std::string>& args);
480
481 private:
482 bool ParseOptions(const std::vector<std::string>& args);
483 bool BuildSampleComparatorAndDisplayer();
484 bool ReadMetaInfoFromRecordFile();
485 bool ReadEventAttrFromRecordFile();
486 bool ReadFeaturesFromRecordFile();
487 bool ReadSampleTreeFromRecordFile();
488 bool ProcessRecord(std::unique_ptr<Record> record);
489 void ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record, size_t attr_id);
490 bool ProcessTracingData(const std::vector<char>& data);
491 bool PrintReport();
492 void PrintReportContext(FILE* fp);
493
494 std::string record_filename_;
495 ArchType record_file_arch_;
496 std::unique_ptr<RecordFileReader> record_file_reader_;
497 std::vector<perf_event_attr> event_attrs_;
498 std::vector<std::string> attr_names_;
499 ThreadTree thread_tree_;
500 // Create a SampleTreeBuilder and SampleTree for each event_attr.
501 std::vector<SampleTree> sample_tree_;
502 SampleTreeBuilderOptions sample_tree_builder_options_;
503 std::vector<std::unique_ptr<ReportCmdSampleTreeBuilder>> sample_tree_builder_;
504
505 std::unique_ptr<ReportCmdSampleTreeSorter> sample_tree_sorter_;
506 std::unique_ptr<ReportCmdSampleTreeDisplayer> sample_tree_displayer_;
507 bool use_branch_address_;
508 std::string record_cmdline_;
509 bool accumulate_callchain_;
510 bool print_callgraph_;
511 bool callgraph_show_callee_;
512 uint32_t callgraph_max_stack_;
513 double percent_limit_;
514 bool raw_period_;
515 bool brief_callgraph_;
516 bool trace_offcpu_;
517 size_t sched_switch_attr_id_;
518 bool report_csv_ = false;
519 std::string csv_separator_ = ",";
520 bool print_sample_count_ = false;
521 bool print_event_count_ = false;
522 std::vector<std::string> sort_keys_;
523 std::string report_filename_;
524 RecordFilter record_filter_;
525 };
526
Run(const std::vector<std::string> & args)527 bool ReportCommand::Run(const std::vector<std::string>& args) {
528 // 1. Parse options.
529 if (!ParseOptions(args)) {
530 return false;
531 }
532
533 // 2. Read record file and build SampleTree.
534 record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
535 if (record_file_reader_ == nullptr) {
536 return false;
537 }
538 if (!ReadMetaInfoFromRecordFile()) {
539 return false;
540 }
541 if (!ReadEventAttrFromRecordFile()) {
542 return false;
543 }
544 if (!BuildSampleComparatorAndDisplayer()) {
545 return false;
546 }
547 // Read features first to prepare build ids used when building SampleTree.
548 if (!ReadFeaturesFromRecordFile()) {
549 return false;
550 }
551 ScopedCurrentArch scoped_arch(record_file_arch_);
552 if (!ReadSampleTreeFromRecordFile()) {
553 return false;
554 }
555
556 // 3. Show collected information.
557 if (!PrintReport()) {
558 return false;
559 }
560
561 return true;
562 }
563
ParseOptions(const std::vector<std::string> & args)564 bool ReportCommand::ParseOptions(const std::vector<std::string>& args) {
565 OptionFormatMap option_formats = {
566 {"-b", {OptionValueType::NONE, OptionType::SINGLE}},
567 {"--children", {OptionValueType::NONE, OptionType::SINGLE}},
568 {"--comms", {OptionValueType::STRING, OptionType::MULTIPLE}},
569 {"--cpu", {OptionValueType::STRING, OptionType::MULTIPLE}},
570 {"--csv", {OptionValueType::NONE, OptionType::SINGLE}},
571 {"--csv-separator", {OptionValueType::STRING, OptionType::SINGLE}},
572 {"--dsos", {OptionValueType::STRING, OptionType::MULTIPLE}},
573 {"--full-callgraph", {OptionValueType::NONE, OptionType::SINGLE}},
574 {"-g", {OptionValueType::OPT_STRING, OptionType::SINGLE}},
575 {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
576 {"--kallsyms", {OptionValueType::STRING, OptionType::SINGLE}},
577 {"--max-stack", {OptionValueType::UINT, OptionType::SINGLE}},
578 {"-n", {OptionValueType::NONE, OptionType::SINGLE}},
579 {"--no-demangle", {OptionValueType::NONE, OptionType::SINGLE}},
580 {"--no-show-ip", {OptionValueType::NONE, OptionType::SINGLE}},
581 {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
582 {"--percent-limit", {OptionValueType::DOUBLE, OptionType::SINGLE}},
583 {"--pids", {OptionValueType::STRING, OptionType::MULTIPLE}},
584 {"--print-event-count", {OptionValueType::NONE, OptionType::SINGLE}},
585 {"--tids", {OptionValueType::STRING, OptionType::MULTIPLE}},
586 {"--raw-period", {OptionValueType::NONE, OptionType::SINGLE}},
587 {"--sort", {OptionValueType::STRING, OptionType::SINGLE}},
588 {"--symbols", {OptionValueType::STRING, OptionType::MULTIPLE}},
589 {"--symfs", {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("--vmlinux"); value) {
698 Dso::SetVmlinux(*value->str_value);
699 }
700 CHECK(options.values.empty());
701 return true;
702 }
703
BuildSampleComparatorAndDisplayer()704 bool ReportCommand::BuildSampleComparatorAndDisplayer() {
705 SampleDisplayer<SampleEntry, SampleTree> displayer;
706 displayer.SetReportFormat(report_csv_, csv_separator_);
707 SampleComparator<SampleEntry> comparator;
708
709 if (accumulate_callchain_) {
710 if (raw_period_) {
711 displayer.AddDisplayFunction("Children", DisplayAccumulatedPeriod<SampleEntry>);
712 displayer.AddDisplayFunction("Self", DisplaySelfPeriod<SampleEntry>);
713 } else {
714 displayer.AddDisplayFunction("Children", DisplayAccumulatedOverhead<SampleEntry, SampleTree>);
715 displayer.AddDisplayFunction("Self", DisplaySelfOverhead<SampleEntry, SampleTree>);
716 }
717 } else {
718 if (raw_period_) {
719 displayer.AddDisplayFunction("Overhead", DisplaySelfPeriod<SampleEntry>);
720 } else {
721 displayer.AddDisplayFunction("Overhead", DisplaySelfOverhead<SampleEntry, SampleTree>);
722 }
723 }
724 if (print_sample_count_) {
725 displayer.AddDisplayFunction("Sample", DisplaySampleCount<SampleEntry>);
726 }
727 if (print_event_count_) {
728 if (event_attrs_.size() == attr_names_.size()) {
729 // Without additional counters, counts field isn't available. So print period field instead.
730 if (accumulate_callchain_) {
731 displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod<SampleEntry>);
732 displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod<SampleEntry>);
733 } else {
734 displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod<SampleEntry>);
735 }
736 } else {
737 // With additional counters, print counts field.
738 for (size_t i = 0; i < attr_names_.size(); i++) {
739 auto self_event_count_fn = [i](const SampleEntry* s) {
740 return i < s->counts.size() ? std::to_string(s->counts[i]) : "0";
741 };
742 auto acc_event_count_fn = [i](const SampleEntry* s) {
743 return i < s->acc_counts.size() ? std::to_string(s->acc_counts[i]) : "0";
744 };
745 if (accumulate_callchain_) {
746 displayer.AddDisplayFunction("AccEventCount_" + attr_names_[i], acc_event_count_fn);
747 displayer.AddDisplayFunction("SelfEventCount_" + attr_names_[i], self_event_count_fn);
748 } else {
749 displayer.AddDisplayFunction("EventCount_" + attr_names_[i], self_event_count_fn);
750 }
751 }
752 }
753 }
754
755 for (auto& key : sort_keys_) {
756 if (!use_branch_address_ && branch_sort_keys.find(key) != branch_sort_keys.end()) {
757 LOG(ERROR) << "sort key '" << key << "' can only be used with -b option.";
758 return false;
759 }
760 if (key == "pid") {
761 comparator.AddCompareFunction(ComparePid);
762 displayer.AddDisplayFunction("Pid", DisplayPid<SampleEntry>);
763 } else if (key == "tid") {
764 comparator.AddCompareFunction(CompareTid);
765 displayer.AddDisplayFunction("Tid", DisplayTid<SampleEntry>);
766 } else if (key == "comm") {
767 comparator.AddCompareFunction(CompareComm);
768 displayer.AddDisplayFunction("Command", DisplayComm<SampleEntry>);
769 } else if (key == "dso") {
770 comparator.AddCompareFunction(CompareDso);
771 displayer.AddDisplayFunction("Shared Object", DisplayDso<SampleEntry>);
772 } else if (key == "symbol") {
773 comparator.AddCompareFunction(CompareSymbol);
774 displayer.AddDisplayFunction("Symbol", DisplaySymbol<SampleEntry>);
775 } else if (key == "vaddr_in_file") {
776 comparator.AddCompareFunction(CompareVaddrInFile);
777 displayer.AddDisplayFunction("VaddrInFile", DisplayVaddrInFile<SampleEntry>);
778 } else if (key == "dso_from") {
779 comparator.AddCompareFunction(CompareDsoFrom);
780 displayer.AddDisplayFunction("Source Shared Object", DisplayDsoFrom<SampleEntry>);
781 } else if (key == "dso_to") {
782 comparator.AddCompareFunction(CompareDso);
783 displayer.AddDisplayFunction("Target Shared Object", DisplayDso<SampleEntry>);
784 } else if (key == "symbol_from") {
785 comparator.AddCompareFunction(CompareSymbolFrom);
786 displayer.AddDisplayFunction("Source Symbol", DisplaySymbolFrom<SampleEntry>);
787 } else if (key == "symbol_to") {
788 comparator.AddCompareFunction(CompareSymbol);
789 displayer.AddDisplayFunction("Target Symbol", DisplaySymbol<SampleEntry>);
790 } else {
791 LOG(ERROR) << "Unknown sort key: " << key;
792 return false;
793 }
794 }
795
796 // Reporting with --csv will add event count and event name columns. But if --print-event-count is
797 // used, there is no need to duplicate printing event counts.
798 if (report_csv_ && !print_event_count_) {
799 if (accumulate_callchain_) {
800 displayer.AddDisplayFunction("AccEventCount", DisplayAccumulatedPeriod<SampleEntry>);
801 displayer.AddDisplayFunction("SelfEventCount", DisplaySelfPeriod<SampleEntry>);
802 } else {
803 displayer.AddDisplayFunction("EventCount", DisplaySelfPeriod<SampleEntry>);
804 }
805 displayer.AddDisplayFunction("EventName", DisplayEventName);
806 }
807
808 if (print_callgraph_) {
809 bool has_symbol_key = false;
810 bool has_vaddr_in_file_key = false;
811 for (const auto& key : sort_keys_) {
812 if (key == "symbol") {
813 has_symbol_key = true;
814 } else if (key == "vaddr_in_file") {
815 has_vaddr_in_file_key = true;
816 }
817 }
818 if (has_symbol_key) {
819 if (has_vaddr_in_file_key) {
820 displayer.AddExclusiveDisplayFunction(ReportCmdCallgraphDisplayerWithVaddrInFile());
821 } else {
822 displayer.AddExclusiveDisplayFunction(
823 ReportCmdCallgraphDisplayer(callgraph_max_stack_, percent_limit_, brief_callgraph_));
824 }
825 }
826 }
827
828 if (percent_limit_ != 0.0) {
829 displayer.SetFilterFunction([this](const SampleEntry* sample, const SampleTree* sample_tree) {
830 uint64_t total_period = sample->period + sample->accumulated_period;
831 return total_period >= sample_tree->total_period * percent_limit_ / 100.0;
832 });
833 }
834
835 sample_tree_builder_options_.comparator = comparator;
836 sample_tree_builder_options_.thread_tree = &thread_tree_;
837
838 SampleComparator<SampleEntry> sort_comparator;
839 sort_comparator.AddCompareFunction(CompareTotalPeriod);
840 if (print_callgraph_) {
841 sort_comparator.AddCompareFunction(CompareCallGraphDuplicated);
842 }
843 sort_comparator.AddCompareFunction(ComparePeriod);
844 sort_comparator.AddComparator(comparator);
845 sample_tree_sorter_.reset(new ReportCmdSampleTreeSorter(sort_comparator));
846 sample_tree_displayer_.reset(new ReportCmdSampleTreeDisplayer(displayer));
847 return true;
848 }
849
ReadMetaInfoFromRecordFile()850 bool ReportCommand::ReadMetaInfoFromRecordFile() {
851 auto& meta_info = record_file_reader_->GetMetaInfoFeature();
852 if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
853 trace_offcpu_ = it->second == "true";
854 }
855 return record_filter_.CheckClock(record_file_reader_->GetClockId());
856 }
857
ReadEventAttrFromRecordFile()858 bool ReportCommand::ReadEventAttrFromRecordFile() {
859 for (const EventAttrWithId& attr_with_id : record_file_reader_->AttrSection()) {
860 const perf_event_attr& attr = attr_with_id.attr;
861 attr_names_.emplace_back(GetEventNameByAttr(attr));
862
863 // There are no samples for events added by --add-counter. So skip them.
864 if ((attr.read_format & PERF_FORMAT_GROUP) && (attr.freq == 0) &&
865 (attr.sample_period == INFINITE_SAMPLE_PERIOD)) {
866 continue;
867 }
868 event_attrs_.emplace_back(attr);
869 }
870 if (use_branch_address_) {
871 bool has_branch_stack = true;
872 for (const auto& attr : event_attrs_) {
873 if ((attr.sample_type & PERF_SAMPLE_BRANCH_STACK) == 0) {
874 has_branch_stack = false;
875 break;
876 }
877 }
878 if (!has_branch_stack) {
879 LOG(ERROR) << record_filename_ << " is not recorded with branch stack sampling option.";
880 return false;
881 }
882 }
883 if (trace_offcpu_) {
884 size_t i;
885 for (i = 0; i < event_attrs_.size(); ++i) {
886 if (attr_names_[i] == "sched:sched_switch") {
887 break;
888 }
889 }
890 CHECK_NE(i, event_attrs_.size());
891 sched_switch_attr_id_ = i;
892 }
893 return true;
894 }
895
ReadFeaturesFromRecordFile()896 bool ReportCommand::ReadFeaturesFromRecordFile() {
897 if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
898 return false;
899 }
900
901 std::string arch = record_file_reader_->ReadFeatureString(PerfFileFormat::FEAT_ARCH);
902 if (!arch.empty()) {
903 record_file_arch_ = GetArchType(arch);
904 if (record_file_arch_ == ARCH_UNSUPPORTED) {
905 return false;
906 }
907 }
908
909 std::vector<std::string> cmdline = record_file_reader_->ReadCmdlineFeature();
910 if (!cmdline.empty()) {
911 record_cmdline_ = android::base::Join(cmdline, ' ');
912 }
913 if (record_file_reader_->HasFeature(PerfFileFormat::FEAT_TRACING_DATA)) {
914 std::vector<char> tracing_data;
915 if (!record_file_reader_->ReadFeatureSection(PerfFileFormat::FEAT_TRACING_DATA,
916 &tracing_data)) {
917 return false;
918 }
919 if (!ProcessTracingData(tracing_data)) {
920 return false;
921 }
922 }
923 return true;
924 }
925
ReadSampleTreeFromRecordFile()926 bool ReportCommand::ReadSampleTreeFromRecordFile() {
927 sample_tree_builder_options_.use_branch_address = use_branch_address_;
928 sample_tree_builder_options_.accumulate_callchain = accumulate_callchain_;
929 sample_tree_builder_options_.build_callchain = print_callgraph_;
930 sample_tree_builder_options_.use_caller_as_callchain_root = !callgraph_show_callee_;
931 sample_tree_builder_options_.trace_offcpu = trace_offcpu_;
932
933 for (size_t i = 0; i < event_attrs_.size(); ++i) {
934 sample_tree_builder_.push_back(
935 sample_tree_builder_options_.CreateSampleTreeBuilder(*record_file_reader_));
936 sample_tree_builder_.back()->SetEventName(attr_names_[i]);
937 OfflineUnwinder* unwinder = sample_tree_builder_.back()->GetUnwinder();
938 if (unwinder != nullptr) {
939 unwinder->LoadMetaInfo(record_file_reader_->GetMetaInfoFeature());
940 }
941 }
942
943 if (!record_file_reader_->ReadDataSection(
944 [this](std::unique_ptr<Record> record) { return ProcessRecord(std::move(record)); })) {
945 return false;
946 }
947 for (size_t i = 0; i < sample_tree_builder_.size(); ++i) {
948 sample_tree_.push_back(sample_tree_builder_[i]->GetSampleTree());
949 sample_tree_sorter_->Sort(sample_tree_.back().samples, print_callgraph_);
950 }
951 return true;
952 }
953
ProcessRecord(std::unique_ptr<Record> record)954 bool ReportCommand::ProcessRecord(std::unique_ptr<Record> record) {
955 thread_tree_.Update(*record);
956 if (record->type() == PERF_RECORD_SAMPLE) {
957 if (!record_filter_.Check(static_cast<SampleRecord*>(record.get()))) {
958 return true;
959 }
960 size_t attr_id = record_file_reader_->GetAttrIndexOfRecord(record.get());
961 if (!trace_offcpu_) {
962 sample_tree_builder_[attr_id]->ReportCmdProcessSampleRecord(
963 *static_cast<SampleRecord*>(record.get()));
964 } else {
965 ProcessSampleRecordInTraceOffCpuMode(std::move(record), attr_id);
966 }
967 } else if (record->type() == PERF_RECORD_TRACING_DATA ||
968 record->type() == SIMPLE_PERF_RECORD_TRACING_DATA) {
969 const auto& r = *static_cast<TracingDataRecord*>(record.get());
970 if (!ProcessTracingData(std::vector<char>(r.data, r.data + r.data_size))) {
971 return false;
972 }
973 }
974 return true;
975 }
976
ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record,size_t attr_id)977 void ReportCommand::ProcessSampleRecordInTraceOffCpuMode(std::unique_ptr<Record> record,
978 size_t attr_id) {
979 std::shared_ptr<SampleRecord> r(static_cast<SampleRecord*>(record.release()));
980 if (attr_id == sched_switch_attr_id_) {
981 // If this sample belongs to sched_switch event, we should broadcast the offcpu info
982 // to other event types.
983 for (size_t i = 0; i < event_attrs_.size(); ++i) {
984 if (i == sched_switch_attr_id_) {
985 continue;
986 }
987 sample_tree_builder_[i]->ReportCmdProcessSampleRecord(r);
988 }
989 } else {
990 sample_tree_builder_[attr_id]->ReportCmdProcessSampleRecord(r);
991 }
992 }
993
ProcessTracingData(const std::vector<char> & data)994 bool ReportCommand::ProcessTracingData(const std::vector<char>& data) {
995 auto tracing = Tracing::Create(data);
996 if (!tracing) {
997 return false;
998 }
999 for (size_t i = 0; i < event_attrs_.size(); i++) {
1000 if (event_attrs_[i].type == PERF_TYPE_TRACEPOINT) {
1001 uint64_t trace_event_id = event_attrs_[i].config;
1002 attr_names_[i] = tracing->GetTracingEventNameHavingId(trace_event_id);
1003 }
1004 }
1005 return true;
1006 }
1007
PrintReport()1008 bool ReportCommand::PrintReport() {
1009 std::unique_ptr<FILE, decltype(&fclose)> file_handler(nullptr, fclose);
1010 FILE* report_fp = stdout;
1011 if (!report_filename_.empty()) {
1012 report_fp = fopen(report_filename_.c_str(), "w");
1013 if (report_fp == nullptr) {
1014 PLOG(ERROR) << "failed to open file " << report_filename_;
1015 return false;
1016 }
1017 file_handler.reset(report_fp);
1018 }
1019 PrintReportContext(report_fp);
1020 for (size_t i = 0; i < event_attrs_.size(); ++i) {
1021 if (trace_offcpu_ && i == sched_switch_attr_id_) {
1022 continue;
1023 }
1024 if (i != 0) {
1025 fprintf(report_fp, "\n");
1026 }
1027 SampleTree& sample_tree = sample_tree_[i];
1028 fprintf(report_fp, "Event: %s (type %u, config %llu)\n", attr_names_[i].c_str(),
1029 event_attrs_[i].type, event_attrs_[i].config);
1030 fprintf(report_fp, "Samples: %" PRIu64 "\n", sample_tree.total_samples);
1031 if (sample_tree.total_error_callchains != 0) {
1032 fprintf(report_fp, "Error Callchains: %" PRIu64 ", %f%%\n",
1033 sample_tree.total_error_callchains,
1034 sample_tree.total_error_callchains * 100.0 / sample_tree.total_samples);
1035 }
1036 const char* period_prefix = trace_offcpu_ ? "Time in ns" : "Event count";
1037 fprintf(report_fp, "%s: %" PRIu64 "\n\n", period_prefix, sample_tree.total_period);
1038 sample_tree_displayer_->DisplaySamples(report_fp, sample_tree.samples, &sample_tree);
1039 }
1040 fflush(report_fp);
1041 if (ferror(report_fp) != 0) {
1042 PLOG(ERROR) << "print report failed";
1043 return false;
1044 }
1045 return true;
1046 }
1047
PrintReportContext(FILE * report_fp)1048 void ReportCommand::PrintReportContext(FILE* report_fp) {
1049 if (!record_cmdline_.empty()) {
1050 fprintf(report_fp, "Cmdline: %s\n", record_cmdline_.c_str());
1051 }
1052 fprintf(report_fp, "Arch: %s\n", GetArchString(record_file_arch_).c_str());
1053 }
1054
1055 } // namespace
1056
RegisterReportCommand()1057 void RegisterReportCommand() {
1058 RegisterCommand("report", [] { return std::unique_ptr<Command>(new ReportCommand()); });
1059 }
1060
1061 } // namespace simpleperf
1062