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