diff options
Diffstat (limited to 'simpleperf/cmd_report_sample.cpp')
-rw-r--r-- | simpleperf/cmd_report_sample.cpp | 256 |
1 files changed, 182 insertions, 74 deletions
diff --git a/simpleperf/cmd_report_sample.cpp b/simpleperf/cmd_report_sample.cpp index 12e3ed61..94f799b0 100644 --- a/simpleperf/cmd_report_sample.cpp +++ b/simpleperf/cmd_report_sample.cpp @@ -121,6 +121,38 @@ static const char* ProtoUnwindingErrorCodeToString( } } +struct SampleEntry { + uint64_t time; + uint64_t period; + uint32_t event_type_id; + bool is_complete_callchain; + std::vector<CallChainReportEntry> callchain; + std::optional<UnwindingResult> unwinding_result; +}; + +struct ThreadId { + uint32_t pid; + uint32_t tid; + + ThreadId(uint32_t pid, uint32_t tid) : pid(pid), tid(tid) {} + + bool operator==(const ThreadId& other) const { return pid == other.pid && tid == other.tid; } +}; + +struct ThreadIdHash { + size_t operator()(const ThreadId& thread_id) const noexcept { + size_t seed = 0; + HashCombine(seed, thread_id.pid); + HashCombine(seed, thread_id.tid); + return seed; + } +}; + +struct ThreadData { + std::string thread_name; + std::queue<SampleEntry> stack_gap_samples; +}; + class ReportSampleCommand : public Command { public: ReportSampleCommand() @@ -128,22 +160,25 @@ class ReportSampleCommand : public Command { "report-sample", "report raw sample information in perf.data", // clang-format off "Usage: simpleperf report-sample [options]\n" -"--dump-protobuf-report <file>\n" -" Dump report file generated by\n" -" `simpleperf report-sample --protobuf -o <file>`.\n" -"-i <file> Specify path of record file, default is perf.data.\n" -"-o report_file_name Set report file name. Default report file name is\n" -" report_sample.trace if --protobuf is used, otherwise\n" -" the report is written to stdout.\n" -"--proguard-mapping-file <file> Add proguard mapping file to de-obfuscate symbols.\n" -"--protobuf Use protobuf format in cmd_report_sample.proto to output samples.\n" -" Need to set a report_file_name when using this option.\n" -"--show-callchain Print callchain samples.\n" -"--remove-unknown-kernel-symbols Remove kernel callchains when kernel symbols\n" -" are not available in perf.data.\n" -"--show-art-frames Show frames of internal methods in the ART Java interpreter.\n" -"--show-execution-type Show execution type of a method\n" -"--symdir <dir> Look for files with symbols in a directory recursively.\n" +"--dump-protobuf-report <file> Dump report file generated by\n" +" `simpleperf report-sample --protobuf -o <file>`.\n" +"-i <file> Specify path of record file, default is perf.data.\n" +"-o report_file_name Set report file name. When --protobuf is used, default is\n" +" report_sample.trace. Otherwise, default writes to stdout.\n" +"--proguard-mapping-file <file> Add proguard mapping file to de-obfuscate symbols.\n" +"--protobuf Use protobuf format in cmd_report_sample.proto to output\n" +" samples.\n" +"--remove-gaps MAX_GAP_LENGTH Ideally all callstacks are complete. But some may be broken\n" +" for different reasons. To create a smooth view in Stack\n" +" Chart, remove small gaps of broken callstacks. MAX_GAP_LENGTH\n" +" is the max length of continuous broken-stack samples we want\n" +" to remove. Default is 3.\n" +"--remove-unknown-kernel-symbols Remove kernel callchains when kernel symbols are not\n" +" available.\n" +"--show-art-frames Show frames of internal methods in the ART Java interpreter.\n" +"--show-callchain Show callchain with samples.\n" +"--show-execution-type Show execution type of a method\n" +"--symdir <dir> Look for files with symbols in a directory recursively.\n" "\n" "Sample filter options:\n" RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING @@ -172,16 +207,18 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING bool ProcessRecord(std::unique_ptr<Record> record); void UpdateThreadName(uint32_t pid, uint32_t tid); bool ProcessSampleRecord(const SampleRecord& r); - bool PrintSampleRecordInProtobuf(const SampleRecord& record, - const std::vector<CallChainReportEntry>& entries); - void AddUnwindingResultInProtobuf(proto::Sample_UnwindingResult* proto_unwinding_result); + bool ProcessSample(const ThreadEntry& thread, SampleEntry& sample); + bool ReportSample(const ThreadId& thread_id, const SampleEntry& sample, size_t stack_gap_length); + bool FinishReportSamples(); + bool PrintSampleInProtobuf(const ThreadId& thread_id, const SampleEntry& sample); + void AddUnwindingResultInProtobuf(const UnwindingResult& unwinding_result, + proto::Sample_UnwindingResult* proto_unwinding_result); bool ProcessSwitchRecord(Record* r); bool WriteRecordInProtobuf(proto::Record& proto_record); bool PrintLostSituationInProtobuf(); bool PrintFileInfoInProtobuf(); bool PrintThreadInfoInProtobuf(); - bool PrintSampleRecord(const SampleRecord& record, - const std::vector<CallChainReportEntry>& entries); + bool PrintSample(const ThreadId& thread_id, const SampleEntry& sample); void PrintLostSituation(); std::string record_filename_; @@ -201,10 +238,10 @@ RECORD_FILTER_OPTION_HELP_MSG_FOR_REPORTING bool kernel_symbols_available_; bool show_execution_type_ = false; CallChainReportBuilder callchain_report_builder_; - // map from <pid, tid> to thread name - std::map<uint64_t, const char*> thread_names_; + std::unordered_map<ThreadId, ThreadData, ThreadIdHash> per_thread_data_; std::unique_ptr<UnwindingResultRecord> last_unwinding_result_; RecordFilter record_filter_; + uint32_t max_remove_gap_length_ = 3; }; bool ReportSampleCommand::Run(const std::vector<std::string>& args) { @@ -266,6 +303,10 @@ bool ReportSampleCommand::Run(const std::vector<std::string>& args) { return false; } + if (!FinishReportSamples()) { + return false; + } + if (use_protobuf_) { if (!PrintLostSituationInProtobuf()) { return false; @@ -301,6 +342,7 @@ bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) { {"--proguard-mapping-file", {OptionValueType::STRING, OptionType::MULTIPLE}}, {"--protobuf", {OptionValueType::NONE, OptionType::SINGLE}}, {"--show-callchain", {OptionValueType::NONE, OptionType::SINGLE}}, + {"--remove-gaps", {OptionValueType::UINT, OptionType::SINGLE}}, {"--remove-unknown-kernel-symbols", {OptionValueType::NONE, OptionType::SINGLE}}, {"--show-art-frames", {OptionValueType::NONE, OptionType::SINGLE}}, {"--show-execution-type", {OptionValueType::NONE, OptionType::SINGLE}}, @@ -323,6 +365,9 @@ bool ReportSampleCommand::ParseOptions(const std::vector<std::string>& args) { } use_protobuf_ = options.PullBoolValue("--protobuf"); show_callchain_ = options.PullBoolValue("--show-callchain"); + if (!options.PullUintValue("--remove-gaps", &max_remove_gap_length_)) { + return false; + } remove_unknown_kernel_symbols_ = options.PullBoolValue("--remove-unknown-kernel-symbols"); if (options.PullBoolValue("--show-art-frames")) { callchain_report_builder_.SetRemoveArtFrame(false); @@ -626,8 +671,19 @@ bool ReportSampleCommand::ProcessRecord(std::unique_ptr<Record> record) { return result; } +static bool IsThreadStartPoint(CallChainReportEntry& entry) { + // Android studio wants a clear call chain end to notify whether a call chain is complete. + // For the main thread, the call chain ends at __libc_init in libc.so. For other threads, + // the call chain ends at __start_thread in libc.so. + // The call chain of the main thread can go beyond __libc_init, to _start (<= android O) or + // _start_main (> android O). + return entry.dso->FileName() == "libc.so" && + (strcmp(entry.symbol->Name(), "__libc_init") == 0 || + strcmp(entry.symbol->Name(), "__start_thread") == 0); +} + bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) { - if (!record_filter_.Check(&r)) { + if (!record_filter_.Check(r)) { return true; } size_t kernel_ip_count; @@ -643,37 +699,101 @@ bool ReportSampleCommand::ProcessSampleRecord(const SampleRecord& r) { ips.resize(1); kernel_ip_count = std::min(kernel_ip_count, static_cast<size_t>(1u)); } - sample_count_++; const ThreadEntry* thread = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); - std::vector<CallChainReportEntry> entries = + std::vector<CallChainReportEntry> callchain = callchain_report_builder_.Build(thread, ips, kernel_ip_count); - for (size_t i = 1; i < entries.size(); i++) { - if (thread_tree_.IsUnknownDso(entries[i].dso)) { - entries.resize(i); + bool complete_callchain = false; + for (size_t i = 1; i < callchain.size(); i++) { + // Stop at unknown callchain. + if (thread_tree_.IsUnknownDso(callchain[i].dso)) { + callchain.resize(i); + break; + } + // Stop at thread start point. Because Android studio wants a clear call chain end. + if (IsThreadStartPoint(callchain[i])) { + complete_callchain = true; + callchain.resize(i + 1); break; } } + SampleEntry sample; + sample.time = r.time_data.time; + sample.period = r.period_data.period; + sample.event_type_id = record_file_reader_->GetAttrIndexOfRecord(&r); + sample.is_complete_callchain = complete_callchain; + sample.callchain = std::move(callchain); + // No need to add unwinding result for callchains fixed by callchain joiner. + if (!complete_callchain && last_unwinding_result_) { + sample.unwinding_result = last_unwinding_result_->unwinding_result; + } + + return ProcessSample(*thread, sample); +} + +bool ReportSampleCommand::ProcessSample(const ThreadEntry& thread, SampleEntry& sample) { + ThreadId thread_id(thread.pid, thread.tid); + ThreadData& data = per_thread_data_[thread_id]; + if (data.thread_name != thread.comm) { + data.thread_name = thread.comm; + } + + // If the sample has incomplete callchain, we push it to stack gap sample queue, to calculate + // stack gap length later. + if (!sample.is_complete_callchain) { + data.stack_gap_samples.push(std::move(sample)); + return true; + } + // Otherwise, we can clean up stack gap sample queue and report the sample immediately. + size_t gap_length = data.stack_gap_samples.size(); + while (!data.stack_gap_samples.empty()) { + if (!ReportSample(thread_id, data.stack_gap_samples.front(), gap_length)) { + return false; + } + data.stack_gap_samples.pop(); + } + return ReportSample(thread_id, sample, 0); +} + +bool ReportSampleCommand::ReportSample(const ThreadId& thread_id, const SampleEntry& sample, + size_t stack_gap_length) { + // Remove samples within a stack gap <= max_remove_gap_length_. + if (stack_gap_length > 0 && stack_gap_length <= max_remove_gap_length_) { + return true; + } + sample_count_++; if (use_protobuf_) { - uint64_t key = (static_cast<uint64_t>(r.tid_data.pid) << 32) | r.tid_data.tid; - thread_names_[key] = thread->comm; - return PrintSampleRecordInProtobuf(r, entries); + return PrintSampleInProtobuf(thread_id, sample); } - return PrintSampleRecord(r, entries); + return PrintSample(thread_id, sample); } -bool ReportSampleCommand::PrintSampleRecordInProtobuf( - const SampleRecord& r, const std::vector<CallChainReportEntry>& entries) { - proto::Record proto_record; - proto::Sample* sample = proto_record.mutable_sample(); - sample->set_time(r.time_data.time); - sample->set_event_count(r.period_data.period); - sample->set_thread_id(r.tid_data.tid); - sample->set_event_type_id(record_file_reader_->GetAttrIndexOfRecord(&r)); +bool ReportSampleCommand::FinishReportSamples() { + for (auto& p : per_thread_data_) { + const auto& thread_id = p.first; + auto& sample_queue = p.second.stack_gap_samples; + size_t gap_length = sample_queue.size(); + while (!sample_queue.empty()) { + if (!ReportSample(thread_id, sample_queue.front(), gap_length)) { + return false; + } + sample_queue.pop(); + } + } + return true; +} - bool complete_callchain = false; - for (const auto& node : entries) { - proto::Sample_CallChainEntry* callchain = sample->add_callchain(); +bool ReportSampleCommand::PrintSampleInProtobuf(const ThreadId& thread_id, + const SampleEntry& sample) { + proto::Record proto_record; + proto::Sample* proto_sample = proto_record.mutable_sample(); + proto_sample->set_time(sample.time); + proto_sample->set_event_count(sample.period); + proto_sample->set_thread_id(thread_id.tid); + proto_sample->set_event_type_id(sample.event_type_id); + + for (const auto& node : sample.callchain) { + proto::Sample_CallChainEntry* callchain = proto_sample->add_callchain(); uint32_t file_id; if (!node.dso->GetDumpId(&file_id)) { file_id = node.dso->CreateDumpId(); @@ -690,28 +810,17 @@ bool ReportSampleCommand::PrintSampleRecordInProtobuf( if (show_execution_type_) { callchain->set_execution_type(ToProtoExecutionType(node.execution_type)); } - - // Android studio wants a clear call chain end to notify whether a call chain is complete. - // For the main thread, the call chain ends at __libc_init in libc.so. For other threads, - // the call chain ends at __start_thread in libc.so. - // The call chain of the main thread can go beyond __libc_init, to _start (<= android O) or - // _start_main (> android O). - if (node.dso->FileName() == "libc.so" && (strcmp(node.symbol->Name(), "__libc_init") == 0 || - strcmp(node.symbol->Name(), "__start_thread") == 0)) { - complete_callchain = true; - break; - } } - // No need to add unwinding result for callchains fixed by callchain joiner. - if (!complete_callchain && last_unwinding_result_) { - AddUnwindingResultInProtobuf(sample->mutable_unwinding_result()); + if (sample.unwinding_result.has_value()) { + AddUnwindingResultInProtobuf(sample.unwinding_result.value(), + proto_sample->mutable_unwinding_result()); } return WriteRecordInProtobuf(proto_record); } void ReportSampleCommand::AddUnwindingResultInProtobuf( + const UnwindingResult& unwinding_result, proto::Sample_UnwindingResult* proto_unwinding_result) { - const UnwindingResult& unwinding_result = last_unwinding_result_->unwinding_result; proto_unwinding_result->set_raw_error_code(unwinding_result.error_code); proto_unwinding_result->set_error_addr(unwinding_result.error_addr); proto::Sample_UnwindingResult_ErrorCode error_code; @@ -840,14 +949,14 @@ bool ReportSampleCommand::PrintFileInfoInProtobuf() { } bool ReportSampleCommand::PrintThreadInfoInProtobuf() { - for (const auto& p : thread_names_) { - uint32_t pid = p.first >> 32; - uint32_t tid = p.first & std::numeric_limits<uint32_t>::max(); + for (const auto& p : per_thread_data_) { + const auto& thread_id = p.first; + const auto& thread_data = p.second; proto::Record proto_record; proto::Thread* proto_thread = proto_record.mutable_thread(); - proto_thread->set_thread_id(tid); - proto_thread->set_process_id(pid); - proto_thread->set_thread_name(p.second); + proto_thread->set_thread_id(thread_id.tid); + proto_thread->set_process_id(thread_id.pid); + proto_thread->set_thread_name(thread_data.thread_name); if (!WriteRecordInProtobuf(proto_record)) { return false; } @@ -855,16 +964,15 @@ bool ReportSampleCommand::PrintThreadInfoInProtobuf() { return true; } -bool ReportSampleCommand::PrintSampleRecord(const SampleRecord& r, - const std::vector<CallChainReportEntry>& entries) { +bool ReportSampleCommand::PrintSample(const ThreadId& thread_id, const SampleEntry& sample) { FprintIndented(report_fp_, 0, "sample:\n"); - FprintIndented(report_fp_, 1, "event_type: %s\n", - event_types_[record_file_reader_->GetAttrIndexOfRecord(&r)].data()); - FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", r.time_data.time); - FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", r.period_data.period); - FprintIndented(report_fp_, 1, "thread_id: %d\n", r.tid_data.tid); - const char* thread_name = thread_tree_.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid)->comm; - FprintIndented(report_fp_, 1, "thread_name: %s\n", thread_name); + FprintIndented(report_fp_, 1, "event_type: %s\n", event_types_[sample.event_type_id].data()); + FprintIndented(report_fp_, 1, "time: %" PRIu64 "\n", sample.time); + FprintIndented(report_fp_, 1, "event_count: %" PRIu64 "\n", sample.period); + FprintIndented(report_fp_, 1, "thread_id: %d\n", thread_id.tid); + FprintIndented(report_fp_, 1, "thread_name: %s\n", + per_thread_data_[thread_id].thread_name.c_str()); + const auto& entries = sample.callchain; CHECK(!entries.empty()); FprintIndented(report_fp_, 1, "vaddr_in_file: %" PRIx64 "\n", entries[0].vaddr_in_file); FprintIndented(report_fp_, 1, "file: %s\n", entries[0].dso->GetReportPath().data()); |