xref: /aosp_15_r20/system/extras/simpleperf/cmd_report_sample.cpp (revision 288bf5226967eb3dac5cce6c939ccc2a7f2b4fe5)
1 /*
2  * Copyright (C) 2016 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 
19 #include <limits>
20 #include <memory>
21 
22 #include <android-base/strings.h>
23 
24 #include "system/extras/simpleperf/cmd_report_sample.pb.h"
25 
26 #include <google/protobuf/io/coded_stream.h>
27 #include <google/protobuf/io/zero_copy_stream_impl_lite.h>
28 
29 #include "OfflineUnwinder.h"
30 #include "RecordFilter.h"
31 #include "command.h"
32 #include "event_attr.h"
33 #include "event_type.h"
34 #include "record_file.h"
35 #include "report_utils.h"
36 #include "thread_tree.h"
37 #include "utils.h"
38 
39 namespace simpleperf {
40 namespace {
41 
42 namespace proto = simpleperf_report_proto;
43 
44 static const char PROT_FILE_MAGIC[] = "SIMPLEPERF";
45 static const uint16_t PROT_FILE_VERSION = 1u;
46 
47 class ProtobufFileWriter : public google::protobuf::io::CopyingOutputStream {
48  public:
ProtobufFileWriter(FILE * out_fp)49   explicit ProtobufFileWriter(FILE* out_fp) : out_fp_(out_fp) {}
50 
Write(const void * buffer,int size)51   bool Write(const void* buffer, int size) override {
52     return fwrite(buffer, size, 1, out_fp_) == 1;
53   }
54 
55  private:
56   FILE* out_fp_;
57 };
58 
59 class ProtobufFileReader : public google::protobuf::io::CopyingInputStream {
60  public:
ProtobufFileReader(FILE * in_fp)61   explicit ProtobufFileReader(FILE* in_fp) : in_fp_(in_fp) {}
62 
Read(void * buffer,int size)63   int Read(void* buffer, int size) override { return fread(buffer, 1, size, in_fp_); }
64 
65  private:
66   FILE* in_fp_;
67 };
68 
ToProtoExecutionType(CallChainExecutionType type)69 static proto::Sample_CallChainEntry_ExecutionType ToProtoExecutionType(
70     CallChainExecutionType type) {
71   switch (type) {
72     case CallChainExecutionType::NATIVE_METHOD:
73       return proto::Sample_CallChainEntry_ExecutionType_NATIVE_METHOD;
74     case CallChainExecutionType::INTERPRETED_JVM_METHOD:
75       return proto::Sample_CallChainEntry_ExecutionType_INTERPRETED_JVM_METHOD;
76     case CallChainExecutionType::JIT_JVM_METHOD:
77       return proto::Sample_CallChainEntry_ExecutionType_JIT_JVM_METHOD;
78     case CallChainExecutionType::ART_METHOD:
79       return proto::Sample_CallChainEntry_ExecutionType_ART_METHOD;
80   }
81   CHECK(false) << "unexpected execution type";
82   return proto::Sample_CallChainEntry_ExecutionType_NATIVE_METHOD;
83 }
84 
ProtoExecutionTypeToString(proto::Sample_CallChainEntry_ExecutionType type)85 static const char* ProtoExecutionTypeToString(proto::Sample_CallChainEntry_ExecutionType type) {
86   switch (type) {
87     case proto::Sample_CallChainEntry_ExecutionType_NATIVE_METHOD:
88       return "native_method";
89     case proto::Sample_CallChainEntry_ExecutionType_INTERPRETED_JVM_METHOD:
90       return "interpreted_jvm_method";
91     case proto::Sample_CallChainEntry_ExecutionType_JIT_JVM_METHOD:
92       return "jit_jvm_method";
93     case proto::Sample_CallChainEntry_ExecutionType_ART_METHOD:
94       return "art_method";
95   }
96   CHECK(false) << "unexpected execution type: " << type;
97   return "";
98 }
99 
ProtoUnwindingErrorCodeToString(proto::Sample_UnwindingResult_ErrorCode error_code)100 static const char* ProtoUnwindingErrorCodeToString(
101     proto::Sample_UnwindingResult_ErrorCode error_code) {
102   switch (error_code) {
103     case proto::Sample_UnwindingResult::ERROR_NONE:
104       return "ERROR_NONE";
105     case proto::Sample_UnwindingResult::ERROR_UNKNOWN:
106       return "ERROR_UNKNOWN";
107     case proto::Sample_UnwindingResult::ERROR_NOT_ENOUGH_STACK:
108       return "ERROR_NOT_ENOUGH_STACK";
109     case proto::Sample_UnwindingResult::ERROR_MEMORY_INVALID:
110       return "ERROR_MEMORY_INVALID";
111     case proto::Sample_UnwindingResult::ERROR_UNWIND_INFO:
112       return "ERROR_UNWIND_INFO";
113     case proto::Sample_UnwindingResult::ERROR_INVALID_MAP:
114       return "ERROR_INVALID_MAP";
115     case proto::Sample_UnwindingResult::ERROR_MAX_FRAME_EXCEEDED:
116       return "ERROR_MAX_FRAME_EXCEEDED";
117     case proto::Sample_UnwindingResult::ERROR_REPEATED_FRAME:
118       return "ERROR_REPEATED_FRAME";
119     case proto::Sample_UnwindingResult::ERROR_INVALID_ELF:
120       return "ERROR_INVALID_ELF";
121   }
122 }
123 
124 struct SampleEntry {
125   uint64_t time;
126   uint64_t period;
127   uint32_t event_type_id;
128   bool is_complete_callchain;
129   std::vector<CallChainReportEntry> callchain;
130   std::optional<UnwindingResult> unwinding_result;
131 };
132 
133 struct ThreadId {
134   uint32_t pid;
135   uint32_t tid;
136 
ThreadIdsimpleperf::__anon248605f60111::ThreadId137   ThreadId(uint32_t pid, uint32_t tid) : pid(pid), tid(tid) {}
138 
operator ==simpleperf::__anon248605f60111::ThreadId139   bool operator==(const ThreadId& other) const { return pid == other.pid && tid == other.tid; }
140 };
141 
142 struct ThreadIdHash {
operator ()simpleperf::__anon248605f60111::ThreadIdHash143   size_t operator()(const ThreadId& thread_id) const noexcept {
144     size_t seed = 0;
145     HashCombine(seed, thread_id.pid);
146     HashCombine(seed, thread_id.tid);
147     return seed;
148   }
149 };
150 
151 struct ThreadData {
152   std::string thread_name;
153   std::queue<SampleEntry> stack_gap_samples;
154 };
155 
156 class ReportSampleCommand : public Command {
157  public:
ReportSampleCommand()158   ReportSampleCommand()
159       : Command(
160             "report-sample", "report raw sample information in perf.data",
161             // clang-format off
162 "Usage: simpleperf report-sample [options]\n"
163 "--dump-protobuf-report <file>      Dump report file generated by\n"
164 "                                   `simpleperf report-sample --protobuf -o <file>`.\n"
165 "-i <file>                          Specify path of record file, default is perf.data.\n"
166 "-o report_file_name                Set report file name. When --protobuf is used, default is\n"
167 "                                   report_sample.trace. Otherwise, default writes to stdout.\n"
168 "--proguard-mapping-file <file>     Add proguard mapping file to de-obfuscate symbols.\n"
169 "--protobuf                         Use protobuf format in cmd_report_sample.proto to output\n"
170 "                                   samples.\n"
171 "--remove-gaps MAX_GAP_LENGTH       Ideally all callstacks are complete. But some may be broken\n"
172 "                                   for different reasons. To create a smooth view in Stack\n"
173 "                                   Chart, remove small gaps of broken callstacks. MAX_GAP_LENGTH\n"
174 "                                   is the max length of continuous broken-stack samples we want\n"
175 "                                   to remove. Default is 3.\n"
176 "--remove-unknown-kernel-symbols    Remove kernel callchains when kernel symbols are not\n"
177 "                                   available.\n"
178 "--show-art-frames                  Show frames of internal methods in the ART Java interpreter.\n"
179 "--show-callchain                   Show callchain with samples.\n"
180 "--show-execution-type              Show execution type of a method\n"
181 "--symdir <dir>                     Look for files with symbols in a directory recursively.\n"
182 "\n"
183 "Sample filter options:\n"
184 RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING
185             // clang-format on
186             ),
187         record_filename_("perf.data"),
188         show_callchain_(false),
189         use_protobuf_(false),
190         report_fp_(nullptr),
191         coded_os_(nullptr),
192         sample_count_(0),
193         lost_count_(0),
194         trace_offcpu_(false),
195         remove_unknown_kernel_symbols_(false),
196         kernel_symbols_available_(false),
197         callchain_report_builder_(thread_tree_),
198         record_filter_(thread_tree_) {}
199 
200   bool Run(const std::vector<std::string>& args) override;
201 
202  private:
203   bool ParseOptions(const std::vector<std::string>& args);
204   bool DumpProtobufReport(const std::string& filename);
205   bool OpenRecordFile();
206   bool PrintMetaInfo();
207   bool ProcessRecord(std::unique_ptr<Record> record);
208   void UpdateThreadName(uint32_t pid, uint32_t tid);
209   bool ProcessSampleRecord(const SampleRecord& r);
210   bool ProcessSample(const ThreadEntry& thread, SampleEntry& sample);
211   bool ReportSample(const ThreadId& thread_id, const SampleEntry& sample, size_t stack_gap_length);
212   bool FinishReportSamples();
213   bool PrintSampleInProtobuf(const ThreadId& thread_id, const SampleEntry& sample);
214   void AddUnwindingResultInProtobuf(const UnwindingResult& unwinding_result,
215                                     proto::Sample_UnwindingResult* proto_unwinding_result);
216   bool ProcessSwitchRecord(Record* r);
217   bool WriteRecordInProtobuf(proto::Record& proto_record);
218   bool PrintLostSituationInProtobuf();
219   bool PrintFileInfoInProtobuf();
220   bool PrintThreadInfoInProtobuf();
221   bool PrintSample(const ThreadId& thread_id, const SampleEntry& sample);
222   void PrintLostSituation();
223 
224   std::string record_filename_;
225   std::unique_ptr<RecordFileReader> record_file_reader_;
226   std::string dump_protobuf_report_file_;
227   bool show_callchain_;
228   bool use_protobuf_;
229   ThreadTree thread_tree_;
230   std::string report_filename_;
231   FILE* report_fp_;
232   google::protobuf::io::CodedOutputStream* coded_os_;
233   size_t sample_count_;
234   size_t lost_count_;
235   bool trace_offcpu_;
236   std::vector<std::string> event_types_;
237   bool remove_unknown_kernel_symbols_;
238   bool kernel_symbols_available_;
239   bool show_execution_type_ = false;
240   CallChainReportBuilder callchain_report_builder_;
241   std::unordered_map<ThreadId, ThreadData, ThreadIdHash> per_thread_data_;
242   std::unique_ptr<UnwindingResultRecord> last_unwinding_result_;
243   RecordFilter record_filter_;
244   uint32_t max_remove_gap_length_ = 3;
245 };
246 
Run(const std::vector<std::string> & args)247 bool ReportSampleCommand::Run(const std::vector<std::string>& args) {
248   // 1. Parse options.
249   if (!ParseOptions(args)) {
250     return false;
251   }
252   // 2. Prepare report fp.
253   report_fp_ = stdout;
254   std::unique_ptr<FILE, decltype(&fclose)> fp(nullptr, fclose);
255   if (!report_filename_.empty()) {
256     const char* open_mode = use_protobuf_ ? "wb" : "w";
257     fp.reset(fopen(report_filename_.c_str(), open_mode));
258     if (fp == nullptr) {
259       PLOG(ERROR) << "failed to open " << report_filename_;
260       return false;
261     }
262     report_fp_ = fp.get();
263   }
264 
265   // 3. Dump protobuf report.
266   if (!dump_protobuf_report_file_.empty()) {
267     return DumpProtobufReport(dump_protobuf_report_file_);
268   }
269 
270   // 4. Open record file.
271   if (!OpenRecordFile()) {
272     return false;
273   }
274   if (use_protobuf_) {
275     GOOGLE_PROTOBUF_VERIFY_VERSION;
276   } else {
277     thread_tree_.ShowMarkForUnknownSymbol();
278     thread_tree_.ShowIpForUnknownSymbol();
279   }
280 
281   // 5. Prepare protobuf output stream.
282   std::unique_ptr<ProtobufFileWriter> protobuf_writer;
283   std::unique_ptr<google::protobuf::io::CopyingOutputStreamAdaptor> protobuf_os;
284   std::unique_ptr<google::protobuf::io::CodedOutputStream> protobuf_coded_os;
285   if (use_protobuf_) {
286     if (fprintf(report_fp_, "%s", PROT_FILE_MAGIC) != 10 ||
287         fwrite(&PROT_FILE_VERSION, sizeof(uint16_t), 1, report_fp_) != 1u) {
288       PLOG(ERROR) << "Failed to write magic/version";
289       return false;
290     }
291     protobuf_writer.reset(new ProtobufFileWriter(report_fp_));
292     protobuf_os.reset(new google::protobuf::io::CopyingOutputStreamAdaptor(protobuf_writer.get()));
293     protobuf_coded_os.reset(new google::protobuf::io::CodedOutputStream(protobuf_os.get()));
294     coded_os_ = protobuf_coded_os.get();
295   }
296 
297   // 6. Read record file, and print samples online.
298   if (!PrintMetaInfo()) {
299     return false;
300   }
301   if (!record_file_reader_->ReadDataSection(
302           [this](std::unique_ptr<Record> record) { return ProcessRecord(std::move(record)); })) {
303     return false;
304   }
305 
306   if (!FinishReportSamples()) {
307     return false;
308   }
309 
310   if (use_protobuf_) {
311     if (!PrintLostSituationInProtobuf()) {
312       return false;
313     }
314     if (!PrintFileInfoInProtobuf()) {
315       return false;
316     }
317     if (!PrintThreadInfoInProtobuf()) {
318       return false;
319     }
320     coded_os_->WriteLittleEndian32(0);
321     if (coded_os_->HadError()) {
322       LOG(ERROR) << "print protobuf report failed";
323       return false;
324     }
325     protobuf_coded_os.reset(nullptr);
326   } else {
327     PrintLostSituation();
328     fflush(report_fp_);
329   }
330   if (ferror(report_fp_) != 0) {
331     PLOG(ERROR) << "print report failed";
332     return false;
333   }
334   return true;
335 }
336 
ParseOptions(const std::vector<std::string> & args)337 bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) {
338   OptionFormatMap option_formats = {
339       {"--dump-protobuf-report", {OptionValueType::STRING, OptionType::SINGLE}},
340       {"-i", {OptionValueType::STRING, OptionType::SINGLE}},
341       {"-o", {OptionValueType::STRING, OptionType::SINGLE}},
342       {"--proguard-mapping-file", {OptionValueType::STRING, OptionType::MULTIPLE}},
343       {"--protobuf", {OptionValueType::NONE, OptionType::SINGLE}},
344       {"--show-callchain", {OptionValueType::NONE, OptionType::SINGLE}},
345       {"--remove-gaps", {OptionValueType::UINT, OptionType::SINGLE}},
346       {"--remove-unknown-kernel-symbols", {OptionValueType::NONE, OptionType::SINGLE}},
347       {"--show-art-frames", {OptionValueType::NONE, OptionType::SINGLE}},
348       {"--show-execution-type", {OptionValueType::NONE, OptionType::SINGLE}},
349       {"--symdir", {OptionValueType::STRING, OptionType::MULTIPLE}},
350   };
351   OptionFormatMap record_filter_options = GetRecordFilterOptionFormats(false);
352   option_formats.insert(record_filter_options.begin(), record_filter_options.end());
353   OptionValueMap options;
354   std::vector<std::pair<OptionName, OptionValue>> ordered_options;
355   if (!PreprocessOptions(args, option_formats, &options, &ordered_options, nullptr)) {
356     return false;
357   }
358   options.PullStringValue("--dump-protobuf-report", &dump_protobuf_report_file_);
359   options.PullStringValue("-i", &record_filename_);
360   options.PullStringValue("-o", &report_filename_);
361   for (const OptionValue& value : options.PullValues("--proguard-mapping-file")) {
362     if (!callchain_report_builder_.AddProguardMappingFile(value.str_value)) {
363       return false;
364     }
365   }
366   use_protobuf_ = options.PullBoolValue("--protobuf");
367   show_callchain_ = options.PullBoolValue("--show-callchain");
368   if (!options.PullUintValue("--remove-gaps", &max_remove_gap_length_)) {
369     return false;
370   }
371   remove_unknown_kernel_symbols_ = options.PullBoolValue("--remove-unknown-kernel-symbols");
372   if (options.PullBoolValue("--show-art-frames")) {
373     callchain_report_builder_.SetRemoveArtFrame(false);
374   }
375   show_execution_type_ = options.PullBoolValue("--show-execution-type");
376   for (const OptionValue& value : options.PullValues("--symdir")) {
377     if (!Dso::AddSymbolDir(value.str_value)) {
378       return false;
379     }
380   }
381   if (!record_filter_.ParseOptions(options)) {
382     return false;
383   }
384   CHECK(options.values.empty());
385 
386   if (use_protobuf_ && report_filename_.empty()) {
387     report_filename_ = "report_sample.trace";
388   }
389   return true;
390 }
391 
DumpProtobufReport(const std::string & filename)392 bool ReportSampleCommand::DumpProtobufReport(const std::string& filename) {
393   GOOGLE_PROTOBUF_VERIFY_VERSION;
394   std::unique_ptr<FILE, decltype(&fclose)> fp(fopen(filename.c_str(), "rb"), fclose);
395   if (fp == nullptr) {
396     PLOG(ERROR) << "failed to open " << filename;
397     return false;
398   }
399   char magic[11] = {};
400   if (fread(magic, 10, 1, fp.get()) != 1u || memcmp(magic, PROT_FILE_MAGIC, 10) != 0) {
401     PLOG(ERROR) << filename << " isn't a file generated by report-sample command.";
402     return false;
403   }
404   FprintIndented(report_fp_, 0, "magic: %s\n", magic);
405   uint16_t version;
406   if (fread(&version, sizeof(uint16_t), 1, fp.get()) != 1u || version != PROT_FILE_VERSION) {
407     PLOG(ERROR) << filename << " doesn't have the expected version.";
408     return false;
409   }
410   FprintIndented(report_fp_, 0, "version: %u\n", version);
411 
412   ProtobufFileReader protobuf_reader(fp.get());
413   google::protobuf::io::CopyingInputStreamAdaptor adaptor(&protobuf_reader);
414   google::protobuf::io::CodedInputStream coded_is(&adaptor);
415   // map from file_id to max_symbol_id requested on the file.
416   std::unordered_map<uint32_t, int32_t> max_symbol_id_map;
417   // files[file_id] is the number of symbols in the file.
418   std::vector<uint32_t> files;
419   uint32_t max_message_size = 64 * (1 << 20);
420   coded_is.SetTotalBytesLimit(max_message_size);
421   while (true) {
422     uint32_t size;
423     if (!coded_is.ReadLittleEndian32(&size)) {
424       PLOG(ERROR) << "failed to read " << filename;
425       return false;
426     }
427     if (size == 0) {
428       break;
429     }
430     // Handle files having large symbol table.
431     if (size > max_message_size) {
432       max_message_size = size;
433       coded_is.SetTotalBytesLimit(max_message_size);
434     }
435     auto limit = coded_is.PushLimit(size);
436     proto::Record proto_record;
437     if (!proto_record.ParseFromCodedStream(&coded_is)) {
438       PLOG(ERROR) << "failed to read " << filename;
439       return false;
440     }
441     coded_is.PopLimit(limit);
442     if (proto_record.has_sample()) {
443       auto& sample = proto_record.sample();
444       static size_t sample_count = 0;
445       FprintIndented(report_fp_, 0, "sample %zu:\n", ++sample_count);
446       FprintIndented(report_fp_, 1, "event_type_id: %zu\n", sample.event_type_id());
447       FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", sample.time());
448       FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", sample.event_count());
449       FprintIndented(report_fp_, 1, "thread_id: %d\n", sample.thread_id());
450       FprintIndented(report_fp_, 1, "callchain:\n");
451       for (int i = 0; i < sample.callchain_size(); ++i) {
452         const proto::Sample_CallChainEntry& callchain = sample.callchain(i);
453         FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n", callchain.vaddr_in_file());
454         FprintIndented(report_fp_, 2, "file_id: %u\n", callchain.file_id());
455         int32_t symbol_id = callchain.symbol_id();
456         FprintIndented(report_fp_, 2, "symbol_id: %d\n", symbol_id);
457         if (symbol_id < -1) {
458           LOG(ERROR) << "unexpected symbol_id " << symbol_id;
459           return false;
460         }
461         if (symbol_id != -1) {
462           max_symbol_id_map[callchain.file_id()] =
463               std::max(max_symbol_id_map[callchain.file_id()], symbol_id);
464         }
465         if (callchain.has_execution_type()) {
466           FprintIndented(report_fp_, 2, "execution_type: %s\n",
467                          ProtoExecutionTypeToString(callchain.execution_type()));
468         }
469       }
470       if (sample.has_unwinding_result()) {
471         FprintIndented(report_fp_, 1, "unwinding_result:\n");
472         FprintIndented(report_fp_, 2, "raw_error_code: %u\n",
473                        sample.unwinding_result().raw_error_code());
474         FprintIndented(report_fp_, 2, "error_addr: 0x%" PRIx64 "\n",
475                        sample.unwinding_result().error_addr());
476         FprintIndented(report_fp_, 2, "error_code: %s\n",
477                        ProtoUnwindingErrorCodeToString(sample.unwinding_result().error_code()));
478       }
479     } else if (proto_record.has_lost()) {
480       auto& lost = proto_record.lost();
481       FprintIndented(report_fp_, 0, "lost_situation:\n");
482       FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n", lost.sample_count());
483       FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n", lost.lost_count());
484     } else if (proto_record.has_file()) {
485       auto& file = proto_record.file();
486       FprintIndented(report_fp_, 0, "file:\n");
487       FprintIndented(report_fp_, 1, "id: %u\n", file.id());
488       FprintIndented(report_fp_, 1, "path: %s\n", file.path().c_str());
489       for (int i = 0; i < file.symbol_size(); ++i) {
490         FprintIndented(report_fp_, 1, "symbol: %s\n", file.symbol(i).c_str());
491       }
492       for (int i = 0; i < file.mangled_symbol_size(); ++i) {
493         FprintIndented(report_fp_, 1, "mangled_symbol: %s\n", file.mangled_symbol(i).c_str());
494       }
495       if (file.id() != files.size()) {
496         LOG(ERROR) << "file id doesn't increase orderly, expected " << files.size() << ", really "
497                    << file.id();
498         return false;
499       }
500       files.push_back(file.symbol_size());
501     } else if (proto_record.has_thread()) {
502       auto& thread = proto_record.thread();
503       FprintIndented(report_fp_, 0, "thread:\n");
504       FprintIndented(report_fp_, 1, "thread_id: %u\n", thread.thread_id());
505       FprintIndented(report_fp_, 1, "process_id: %u\n", thread.process_id());
506       FprintIndented(report_fp_, 1, "thread_name: %s\n", thread.thread_name().c_str());
507     } else if (proto_record.has_meta_info()) {
508       auto& meta_info = proto_record.meta_info();
509       FprintIndented(report_fp_, 0, "meta_info:\n");
510       for (int i = 0; i < meta_info.event_type_size(); ++i) {
511         FprintIndented(report_fp_, 1, "event_type: %s\n", meta_info.event_type(i).c_str());
512       }
513       if (meta_info.has_app_package_name()) {
514         FprintIndented(report_fp_, 1, "app_package_name: %s\n",
515                        meta_info.app_package_name().c_str());
516       }
517       if (meta_info.has_app_type()) {
518         FprintIndented(report_fp_, 1, "app_type: %s\n", meta_info.app_type().c_str());
519       }
520       if (meta_info.has_android_sdk_version()) {
521         FprintIndented(report_fp_, 1, "android_sdk_version: %s\n",
522                        meta_info.android_sdk_version().c_str());
523       }
524       if (meta_info.has_android_build_type()) {
525         FprintIndented(report_fp_, 1, "android_build_type: %s\n",
526                        meta_info.android_build_type().c_str());
527       }
528       if (meta_info.has_trace_offcpu()) {
529         FprintIndented(report_fp_, 1, "trace_offcpu: %s\n",
530                        meta_info.trace_offcpu() ? "true" : "false");
531       }
532     } else if (proto_record.has_context_switch()) {
533       auto& context_switch = proto_record.context_switch();
534       FprintIndented(report_fp_, 0, "context_switch:\n");
535       FprintIndented(report_fp_, 1, "switch_on: %s\n",
536                      context_switch.switch_on() ? "true" : "false");
537       FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", context_switch.time());
538       FprintIndented(report_fp_, 1, "thread_id: %u\n", context_switch.thread_id());
539     } else {
540       LOG(ERROR) << "unexpected record type ";
541       return false;
542     }
543   }
544   for (auto pair : max_symbol_id_map) {
545     if (pair.first >= files.size()) {
546       LOG(ERROR) << "file_id(" << pair.first << ") >= file count (" << files.size() << ")";
547       return false;
548     }
549     if (static_cast<uint32_t>(pair.second) >= files[pair.first]) {
550       LOG(ERROR) << "symbol_id(" << pair.second << ") >= symbol count (" << files[pair.first]
551                  << ") in file_id( " << pair.first << ")";
552       return false;
553     }
554   }
555   return true;
556 }
557 
OpenRecordFile()558 bool ReportSampleCommand::OpenRecordFile() {
559   record_file_reader_ = RecordFileReader::CreateInstance(record_filename_);
560   if (record_file_reader_ == nullptr) {
561     return false;
562   }
563   if (!record_file_reader_->LoadBuildIdAndFileFeatures(thread_tree_)) {
564     return false;
565   }
566   auto& meta_info = record_file_reader_->GetMetaInfoFeature();
567   if (auto it = meta_info.find("trace_offcpu"); it != meta_info.end()) {
568     trace_offcpu_ = it->second == "true";
569     if (trace_offcpu_) {
570       std::string event_name = GetEventNameByAttr(record_file_reader_->AttrSection()[0].attr);
571       if (!android::base::StartsWith(event_name, "cpu-clock") &&
572           !android::base::StartsWith(event_name, "task-clock")) {
573         LOG(ERROR) << "Recording file " << record_filename_ << " is no longer supported. "
574                    << "--trace-offcpu must be used with `-e cpu-clock` or `-e task-clock`.";
575         return false;
576       }
577     }
578   }
579   if (auto it = meta_info.find("kernel_symbols_available"); it != meta_info.end()) {
580     kernel_symbols_available_ = it->second == "true";
581   }
582   if (!record_filter_.CheckClock(record_file_reader_->GetClockId())) {
583     return false;
584   }
585   for (const EventAttrWithId& attr : record_file_reader_->AttrSection()) {
586     event_types_.push_back(GetEventNameByAttr(attr.attr));
587   }
588   return true;
589 }
590 
PrintMetaInfo()591 bool ReportSampleCommand::PrintMetaInfo() {
592   auto& meta_info = record_file_reader_->GetMetaInfoFeature();
593 
594   auto get_meta_info_value = [&meta_info](const char* key) -> std::string {
595     if (auto it = meta_info.find(key); it != meta_info.end()) {
596       return it->second;
597     }
598     return "";
599   };
600 
601   std::string app_package_name = get_meta_info_value("app_package_name");
602   std::string app_type = get_meta_info_value("app_type");
603   std::string android_sdk_version = get_meta_info_value("android_sdk_version");
604   std::string android_build_type = get_meta_info_value("android_build_type");
605 
606   if (use_protobuf_) {
607     proto::Record proto_record;
608     proto::MetaInfo* proto_meta_info = proto_record.mutable_meta_info();
609     for (auto& event_type : event_types_) {
610       *(proto_meta_info->add_event_type()) = event_type;
611     }
612     if (!app_package_name.empty()) {
613       proto_meta_info->set_app_package_name(app_package_name);
614     }
615     if (!app_type.empty()) {
616       proto_meta_info->set_app_type(app_type);
617     }
618     if (!android_sdk_version.empty()) {
619       proto_meta_info->set_android_sdk_version(android_sdk_version);
620     }
621     if (!android_build_type.empty()) {
622       proto_meta_info->set_android_build_type(android_build_type);
623     }
624     proto_meta_info->set_trace_offcpu(trace_offcpu_);
625     return WriteRecordInProtobuf(proto_record);
626   }
627   FprintIndented(report_fp_, 0, "meta_info:\n");
628   FprintIndented(report_fp_, 1, "trace_offcpu: %s\n", trace_offcpu_ ? "true" : "false");
629   for (auto& event_type : event_types_) {
630     FprintIndented(report_fp_, 1, "event_type: %s\n", event_type.c_str());
631   }
632   if (!app_package_name.empty()) {
633     FprintIndented(report_fp_, 1, "app_package_name: %s\n", app_package_name.c_str());
634   }
635   if (!app_type.empty()) {
636     FprintIndented(report_fp_, 1, "app_type: %s\n", app_type.c_str());
637   }
638   if (!android_sdk_version.empty()) {
639     FprintIndented(report_fp_, 1, "android_sdk_version: %s\n", android_sdk_version.c_str());
640   }
641   if (!android_build_type.empty()) {
642     FprintIndented(report_fp_, 1, "android_build_type: %s\n", android_build_type.c_str());
643   }
644   return true;
645 }
646 
ProcessRecord(std::unique_ptr<Record> record)647 bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) {
648   thread_tree_.Update(*record);
649   bool result = true;
650   switch (record->type()) {
651     case PERF_RECORD_SAMPLE: {
652       result = ProcessSampleRecord(*static_cast<SampleRecord*>(record.get()));
653       last_unwinding_result_.reset();
654       break;
655     }
656     case SIMPLE_PERF_RECORD_UNWINDING_RESULT: {
657       last_unwinding_result_.reset(static_cast<UnwindingResultRecord*>(record.release()));
658       break;
659     }
660     case PERF_RECORD_LOST: {
661       lost_count_ += static_cast<const LostRecord*>(record.get())->lost;
662       break;
663     }
664     case PERF_RECORD_SWITCH:
665       [[fallthrough]];
666     case PERF_RECORD_SWITCH_CPU_WIDE: {
667       result = ProcessSwitchRecord(record.get());
668       break;
669     }
670   }
671   return result;
672 }
673 
IsThreadStartPoint(CallChainReportEntry & entry)674 static bool IsThreadStartPoint(CallChainReportEntry& entry) {
675   // Android studio wants a clear call chain end to notify whether a call chain is complete.
676   // For the main thread, the call chain ends at __libc_init in libc.so. For other threads,
677   // the call chain ends at __start_thread in libc.so.
678   // The call chain of the main thread can go beyond __libc_init, to _start (<= android O) or
679   // _start_main (> android O).
680   return entry.dso->FileName() == "libc.so" &&
681          (strcmp(entry.symbol->Name(), "__libc_init") == 0 ||
682           strcmp(entry.symbol->Name(), "__start_thread") == 0);
683 }
684 
ProcessSampleRecord(const SampleRecord & r)685 bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) {
686   if (!record_filter_.Check(r)) {
687     return true;
688   }
689   size_t kernel_ip_count;
690   std::vector<uint64_t> ips = r.GetCallChain(&kernel_ip_count);
691   if (kernel_ip_count > 0u && remove_unknown_kernel_symbols_ && !kernel_symbols_available_) {
692     ips.erase(ips.begin(), ips.begin() + kernel_ip_count);
693     kernel_ip_count = 0;
694   }
695   if (ips.empty()) {
696     return true;
697   }
698   if (!show_callchain_) {
699     ips.resize(1);
700     kernel_ip_count = std::min(kernel_ip_count, static_cast<size_t>(1u));
701   }
702   const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid);
703   std::vector<CallChainReportEntry> callchain =
704       callchain_report_builder_.Build(thread, ips, kernel_ip_count);
705 
706   bool complete_callchain = false;
707   for (size_t i = 1; i < callchain.size(); i++) {
708     // Stop at unknown callchain.
709     if (thread_tree_.IsUnknownDso(callchain[i].dso)) {
710       callchain.resize(i);
711       break;
712     }
713     // Stop at thread start point. Because Android studio wants a clear call chain end.
714     if (IsThreadStartPoint(callchain[i])) {
715       complete_callchain = true;
716       callchain.resize(i + 1);
717       break;
718     }
719   }
720   SampleEntry sample;
721   sample.time = r.time_data.time;
722   sample.period = r.period_data.period;
723   sample.event_type_id = record_file_reader_->GetAttrIndexOfRecord(&r);
724   sample.is_complete_callchain = complete_callchain;
725   sample.callchain = std::move(callchain);
726   // No need to add unwinding result for callchains fixed by callchain joiner.
727   if (!complete_callchain && last_unwinding_result_) {
728     sample.unwinding_result = last_unwinding_result_->unwinding_result;
729   }
730 
731   return ProcessSample(*thread, sample);
732 }
733 
ProcessSample(const ThreadEntry & thread,SampleEntry & sample)734 bool ReportSampleCommand::ProcessSample(const ThreadEntry& thread, SampleEntry& sample) {
735   ThreadId thread_id(thread.pid, thread.tid);
736   ThreadData& data = per_thread_data_[thread_id];
737   if (data.thread_name != thread.comm) {
738     data.thread_name = thread.comm;
739   }
740 
741   // If the sample has incomplete callchain, we push it to stack gap sample queue, to calculate
742   // stack gap length later.
743   if (!sample.is_complete_callchain) {
744     data.stack_gap_samples.push(std::move(sample));
745     return true;
746   }
747   // Otherwise, we can clean up stack gap sample queue and report the sample immediately.
748   size_t gap_length = data.stack_gap_samples.size();
749   while (!data.stack_gap_samples.empty()) {
750     if (!ReportSample(thread_id, data.stack_gap_samples.front(), gap_length)) {
751       return false;
752     }
753     data.stack_gap_samples.pop();
754   }
755   return ReportSample(thread_id, sample, 0);
756 }
757 
ReportSample(const ThreadId & thread_id,const SampleEntry & sample,size_t stack_gap_length)758 bool ReportSampleCommand::ReportSample(const ThreadId& thread_id, const SampleEntry& sample,
759                                        size_t stack_gap_length) {
760   // Remove samples within a stack gap <= max_remove_gap_length_.
761   if (stack_gap_length > 0 && stack_gap_length <= max_remove_gap_length_) {
762     return true;
763   }
764   sample_count_++;
765   if (use_protobuf_) {
766     return PrintSampleInProtobuf(thread_id, sample);
767   }
768   return PrintSample(thread_id, sample);
769 }
770 
FinishReportSamples()771 bool ReportSampleCommand::FinishReportSamples() {
772   for (auto& p : per_thread_data_) {
773     const auto& thread_id = p.first;
774     auto& sample_queue = p.second.stack_gap_samples;
775     size_t gap_length = sample_queue.size();
776     while (!sample_queue.empty()) {
777       if (!ReportSample(thread_id, sample_queue.front(), gap_length)) {
778         return false;
779       }
780       sample_queue.pop();
781     }
782   }
783   return true;
784 }
785 
PrintSampleInProtobuf(const ThreadId & thread_id,const SampleEntry & sample)786 bool ReportSampleCommand::PrintSampleInProtobuf(const ThreadId& thread_id,
787                                                 const SampleEntry& sample) {
788   proto::Record proto_record;
789   proto::Sample* proto_sample = proto_record.mutable_sample();
790   proto_sample->set_time(sample.time);
791   proto_sample->set_event_count(sample.period);
792   proto_sample->set_thread_id(thread_id.tid);
793   proto_sample->set_event_type_id(sample.event_type_id);
794 
795   for (const auto& node : sample.callchain) {
796     proto::Sample_CallChainEntry* callchain = proto_sample->add_callchain();
797     uint32_t file_id;
798     if (!node.dso->GetDumpId(&file_id)) {
799       file_id = node.dso->CreateDumpId();
800     }
801     int32_t symbol_id = -1;
802     if (node.symbol != thread_tree_.UnknownSymbol()) {
803       if (!node.symbol->GetDumpId(reinterpret_cast<uint32_t*>(&symbol_id))) {
804         symbol_id = node.dso->CreateSymbolDumpId(node.symbol);
805       }
806     }
807     callchain->set_vaddr_in_file(node.vaddr_in_file);
808     callchain->set_file_id(file_id);
809     callchain->set_symbol_id(symbol_id);
810     if (show_execution_type_) {
811       callchain->set_execution_type(ToProtoExecutionType(node.execution_type));
812     }
813   }
814   if (sample.unwinding_result.has_value()) {
815     AddUnwindingResultInProtobuf(sample.unwinding_result.value(),
816                                  proto_sample->mutable_unwinding_result());
817   }
818   return WriteRecordInProtobuf(proto_record);
819 }
820 
AddUnwindingResultInProtobuf(const UnwindingResult & unwinding_result,proto::Sample_UnwindingResult * proto_unwinding_result)821 void ReportSampleCommand::AddUnwindingResultInProtobuf(
822     const UnwindingResult& unwinding_result,
823     proto::Sample_UnwindingResult* proto_unwinding_result) {
824   proto_unwinding_result->set_raw_error_code(unwinding_result.error_code);
825   proto_unwinding_result->set_error_addr(unwinding_result.error_addr);
826   proto::Sample_UnwindingResult_ErrorCode error_code;
827   switch (unwinding_result.error_code) {
828     case UnwindStackErrorCode::ERROR_NONE:
829       error_code = proto::Sample_UnwindingResult::ERROR_NONE;
830       break;
831     case UnwindStackErrorCode::ERROR_MEMORY_INVALID: {
832       // We dumped stack data in range [stack_start, stack_end) for dwarf unwinding.
833       // If the failed-to-read memory addr is within [stack_end, stack_end + 128k], then
834       // probably we didn't dump enough stack data.
835       // 128k is a guess number. The size of stack used in one function layer is usually smaller
836       // than it. And using a bigger value is more likely to be false positive.
837       if (unwinding_result.error_addr >= unwinding_result.stack_end &&
838           unwinding_result.error_addr <= unwinding_result.stack_end + 128 * 1024) {
839         error_code = proto::Sample_UnwindingResult::ERROR_NOT_ENOUGH_STACK;
840       } else {
841         error_code = proto::Sample_UnwindingResult::ERROR_MEMORY_INVALID;
842       }
843       break;
844     }
845     case UnwindStackErrorCode::ERROR_UNWIND_INFO:
846       error_code = proto::Sample_UnwindingResult::ERROR_UNWIND_INFO;
847       break;
848     case UnwindStackErrorCode::ERROR_INVALID_MAP:
849       error_code = proto::Sample_UnwindingResult::ERROR_INVALID_MAP;
850       break;
851     case UnwindStackErrorCode::ERROR_MAX_FRAMES_EXCEEDED:
852       error_code = proto::Sample_UnwindingResult::ERROR_MAX_FRAME_EXCEEDED;
853       break;
854     case UnwindStackErrorCode::ERROR_REPEATED_FRAME:
855       error_code = proto::Sample_UnwindingResult::ERROR_REPEATED_FRAME;
856       break;
857     case UnwindStackErrorCode::ERROR_INVALID_ELF:
858       error_code = proto::Sample_UnwindingResult::ERROR_INVALID_ELF;
859       break;
860     case UnwindStackErrorCode::ERROR_UNSUPPORTED:
861     case UnwindStackErrorCode::ERROR_THREAD_DOES_NOT_EXIST:
862     case UnwindStackErrorCode::ERROR_THREAD_TIMEOUT:
863     case UnwindStackErrorCode::ERROR_SYSTEM_CALL:
864       // These error_codes shouldn't happen in simpleperf's use of libunwindstack.
865       error_code = proto::Sample_UnwindingResult::ERROR_UNKNOWN;
866       break;
867     default:
868       LOG(ERROR) << "unknown unwinding error code: " << unwinding_result.error_code;
869       error_code = proto::Sample_UnwindingResult::ERROR_UNKNOWN;
870       break;
871   }
872   proto_unwinding_result->set_error_code(error_code);
873 }
874 
ProcessSwitchRecord(Record * r)875 bool ReportSampleCommand::ProcessSwitchRecord(Record* r) {
876   bool switch_on = !(r->header.misc & PERF_RECORD_MISC_SWITCH_OUT);
877   uint64_t time = r->Timestamp();
878   uint32_t tid = r->sample_id.tid_data.tid;
879   if (use_protobuf_) {
880     proto::Record proto_record;
881     proto::ContextSwitch* proto_switch = proto_record.mutable_context_switch();
882     proto_switch->set_switch_on(switch_on);
883     proto_switch->set_time(time);
884     proto_switch->set_thread_id(tid);
885     return WriteRecordInProtobuf(proto_record);
886   }
887   FprintIndented(report_fp_, 0, "context_switch:\n");
888   FprintIndented(report_fp_, 1, "switch_on: %s\n", switch_on ? "true" : "false");
889   FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", time);
890   FprintIndented(report_fp_, 1, "thread_id: %u\n", tid);
891   return true;
892 }
893 
WriteRecordInProtobuf(proto::Record & proto_record)894 bool ReportSampleCommand::WriteRecordInProtobuf(proto::Record& proto_record) {
895   coded_os_->WriteLittleEndian32(static_cast<uint32_t>(proto_record.ByteSizeLong()));
896   if (!proto_record.SerializeToCodedStream(coded_os_)) {
897     LOG(ERROR) << "failed to write record to protobuf";
898     return false;
899   }
900   return true;
901 }
902 
PrintLostSituationInProtobuf()903 bool ReportSampleCommand::PrintLostSituationInProtobuf() {
904   proto::Record proto_record;
905   proto::LostSituation* lost = proto_record.mutable_lost();
906   lost->set_sample_count(sample_count_);
907   lost->set_lost_count(lost_count_);
908   return WriteRecordInProtobuf(proto_record);
909 }
910 
CompareDsoByDumpId(Dso * d1,Dso * d2)911 static bool CompareDsoByDumpId(Dso* d1, Dso* d2) {
912   uint32_t id1 = UINT_MAX;
913   d1->GetDumpId(&id1);
914   uint32_t id2 = UINT_MAX;
915   d2->GetDumpId(&id2);
916   return id1 < id2;
917 }
918 
PrintFileInfoInProtobuf()919 bool ReportSampleCommand::PrintFileInfoInProtobuf() {
920   std::vector<Dso*> dsos = thread_tree_.GetAllDsos();
921   std::sort(dsos.begin(), dsos.end(), CompareDsoByDumpId);
922   for (Dso* dso : dsos) {
923     uint32_t file_id;
924     if (!dso->GetDumpId(&file_id)) {
925       continue;
926     }
927     proto::Record proto_record;
928     proto::File* file = proto_record.mutable_file();
929     file->set_id(file_id);
930     file->set_path(std::string{dso->GetReportPath()});
931     const std::vector<Symbol>& symbols = dso->GetSymbols();
932     std::vector<const Symbol*> dump_symbols;
933     for (const auto& sym : symbols) {
934       if (sym.HasDumpId()) {
935         dump_symbols.push_back(&sym);
936       }
937     }
938     std::sort(dump_symbols.begin(), dump_symbols.end(), Symbol::CompareByDumpId);
939 
940     for (const auto& sym : dump_symbols) {
941       file->add_symbol(sym->DemangledName());
942       file->add_mangled_symbol(sym->Name());
943     }
944     if (!WriteRecordInProtobuf(proto_record)) {
945       return false;
946     }
947   }
948   return true;
949 }
950 
PrintThreadInfoInProtobuf()951 bool ReportSampleCommand::PrintThreadInfoInProtobuf() {
952   for (const auto& p : per_thread_data_) {
953     const auto& thread_id = p.first;
954     const auto& thread_data = p.second;
955     proto::Record proto_record;
956     proto::Thread* proto_thread = proto_record.mutable_thread();
957     proto_thread->set_thread_id(thread_id.tid);
958     proto_thread->set_process_id(thread_id.pid);
959     proto_thread->set_thread_name(thread_data.thread_name);
960     if (!WriteRecordInProtobuf(proto_record)) {
961       return false;
962     }
963   }
964   return true;
965 }
966 
PrintSample(const ThreadId & thread_id,const SampleEntry & sample)967 bool ReportSampleCommand::PrintSample(const ThreadId& thread_id, const SampleEntry& sample) {
968   FprintIndented(report_fp_, 0, "sample:\n");
969   FprintIndented(report_fp_, 1, "event_type: %s\n", event_types_[sample.event_type_id].data());
970   FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", sample.time);
971   FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", sample.period);
972   FprintIndented(report_fp_, 1, "thread_id: %d\n", thread_id.tid);
973   FprintIndented(report_fp_, 1, "thread_name: %s\n",
974                  per_thread_data_[thread_id].thread_name.c_str());
975   const auto& entries = sample.callchain;
976   CHECK(!entries.empty());
977   FprintIndented(report_fp_, 1, "vaddr_in_file: %" PRIx64 "\n", entries[0].vaddr_in_file);
978   FprintIndented(report_fp_, 1, "file: %s\n", entries[0].dso->GetReportPath().data());
979   FprintIndented(report_fp_, 1, "symbol: %s\n", entries[0].symbol->DemangledName());
980   if (show_execution_type_) {
981     FprintIndented(report_fp_, 1, "execution_type: %s\n",
982                    ProtoExecutionTypeToString(ToProtoExecutionType(entries[0].execution_type)));
983   }
984 
985   if (entries.size() > 1u) {
986     FprintIndented(report_fp_, 1, "callchain:\n");
987     for (size_t i = 1u; i < entries.size(); ++i) {
988       FprintIndented(report_fp_, 2, "vaddr_in_file: %" PRIx64 "\n", entries[i].vaddr_in_file);
989       FprintIndented(report_fp_, 2, "file: %s\n", entries[i].dso->GetReportPath().data());
990       FprintIndented(report_fp_, 2, "symbol: %s\n", entries[i].symbol->DemangledName());
991       if (show_execution_type_) {
992         FprintIndented(report_fp_, 2, "execution_type: %s\n",
993                        ProtoExecutionTypeToString(ToProtoExecutionType(entries[i].execution_type)));
994       }
995     }
996   }
997   return true;
998 }
999 
PrintLostSituation()1000 void ReportSampleCommand::PrintLostSituation() {
1001   FprintIndented(report_fp_, 0, "lost_situation:\n");
1002   FprintIndented(report_fp_, 1, "sample_count: %" PRIu64 "\n", sample_count_);
1003   FprintIndented(report_fp_, 1, "lost_count: %" PRIu64 "\n", lost_count_);
1004 }
1005 
1006 }  // namespace
1007 
RegisterReportSampleCommand()1008 void RegisterReportSampleCommand() {
1009   RegisterCommand("report-sample",
1010                   [] { return std::unique_ptr<Command>(new ReportSampleCommand()); });
1011 }
1012 
1013 }  // namespace simpleperf
1014