summaryrefslogtreecommitdiff
path: root/simpleperf/cmd_report_sample.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'simpleperf/cmd_report_sample.cpp')
-rw-r--r--simpleperf/cmd_report_sample.cpp256
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());