From cb84c9885e7a9f82cefba566d74e5c71214ab4c9 Mon Sep 17 00:00:00 2001 From: Yabin Cui Date: Wed, 30 Sep 2015 17:22:35 -0700 Subject: [PATCH] Simpleperf: do dwarf unwinding in record command. As libbacktrace only supports unwinding for the same architecture it is running on, simpleperf report command running on host can't unwind perf.data collected on device. So we'd better do unwinding work in record command on device. Bug: 22229391 Change-Id: I085ca074ea83dab79f08563523bdbc7a36650a64 --- simpleperf/cmd_record.cpp | 58 ++++++++++++++- simpleperf/cmd_record_test.cpp | 10 +++ simpleperf/record.cpp | 151 ++++++++++++++++++++++++++++++++------ simpleperf/record.h | 29 ++++++-- simpleperf/record_file.h | 1 + simpleperf/record_file_writer.cpp | 22 ++++++ 6 files changed, 240 insertions(+), 31 deletions(-) diff --git a/simpleperf/cmd_record.cpp b/simpleperf/cmd_record.cpp index 8776c0fe..04c6d7d4 100644 --- a/simpleperf/cmd_record.cpp +++ b/simpleperf/cmd_record.cpp @@ -28,6 +28,7 @@ #include #include "command.h" +#include "dwarf_unwind.h" #include "environment.h" #include "event_selection_set.h" #include "event_type.h" @@ -90,6 +91,9 @@ class RecordCommand : public Command { " any_call, any_ret, ind_call.\n" " --no-inherit\n" " Don't record created child threads/processes.\n" + " --no-unwind If `--call-graph dwarf` option is used, then the user's stack will\n" + " be unwound by default. Use this option to disable the unwinding of\n" + " the user's stack.\n" " -o record_file_name Set record file name, default is perf.data.\n" " -p pid1,pid2,...\n" " Record events on existing processes. Mutually exclusive with -a.\n" @@ -102,6 +106,7 @@ class RecordCommand : public Command { fp_callchain_sampling_(false), dwarf_callchain_sampling_(false), dump_stack_size_in_dwarf_sampling_(8192), + unwind_dwarf_callchain_(true), child_inherit_(true), perf_mmap_pages_(256), record_filename_("perf.data") { @@ -122,6 +127,7 @@ class RecordCommand : public Command { bool WriteData(const char* data, size_t size); bool DumpKernelAndModuleMmaps(); bool DumpThreadCommAndMmaps(bool all_threads, const std::vector& selected_threads); + bool UnwindDwarfCallChain(); bool DumpAdditionalFeatures(const std::vector& args); bool DumpBuildIdFeature(); bool GetHitFiles(std::set* kernel_modules, std::set* user_files); @@ -135,6 +141,7 @@ class RecordCommand : public Command { bool fp_callchain_sampling_; bool dwarf_callchain_sampling_; uint32_t dump_stack_size_in_dwarf_sampling_; + bool unwind_dwarf_callchain_; bool child_inherit_; std::vector monitored_threads_; std::vector measured_event_types_; @@ -225,7 +232,14 @@ bool RecordCommand::Run(const std::vector& args) { poll(&pollfds[0], pollfds.size(), -1); } - // 6. Dump additional features, and close record file. + // 6. Unwind dwarf callchain. + if (unwind_dwarf_callchain_) { + if (!UnwindDwarfCallChain()) { + return false; + } + } + + // 7. Dump additional features, and close record file. if (!DumpAdditionalFeatures(args)) { return false; } @@ -322,6 +336,8 @@ bool RecordCommand::ParseOptions(const std::vector& args, } } else if (args[i] == "--no-inherit") { child_inherit_ = false; + } else if (args[i] == "--no-unwind") { + unwind_dwarf_callchain_ = false; } else if (args[i] == "-o") { if (!NextArgumentOrError(args, &i)) { return false; @@ -347,6 +363,14 @@ bool RecordCommand::ParseOptions(const std::vector& args, } } + if (!dwarf_callchain_sampling_) { + if (!unwind_dwarf_callchain_) { + LOG(ERROR) << "--no-unwind is only used with `--call-graph dwarf` option."; + return false; + } + unwind_dwarf_callchain_ = false; + } + monitored_threads_.insert(monitored_threads_.end(), tid_set.begin(), tid_set.end()); if (system_wide_collection_ && !monitored_threads_.empty()) { LOG(ERROR) @@ -532,6 +556,38 @@ bool RecordCommand::DumpThreadCommAndMmaps(bool all_threads, return true; } +bool RecordCommand::UnwindDwarfCallChain() { + std::vector> records; + if (!record_file_writer_->ReadDataSection(&records)) { + return false; + } + ThreadTree thread_tree; + for (auto& record : records) { + BuildThreadTree(*record, &thread_tree); + if (record->header.type == PERF_RECORD_SAMPLE) { + SampleRecord& r = *static_cast(record.get()); + if ((r.sample_type & PERF_SAMPLE_CALLCHAIN) && (r.sample_type & PERF_SAMPLE_REGS_USER) && + (r.regs_user_data.reg_mask != 0) && (r.sample_type & PERF_SAMPLE_STACK_USER) && + (!r.stack_user_data.data.empty())) { + ThreadEntry* thread = thread_tree.FindThreadOrNew(r.tid_data.pid, r.tid_data.tid); + RegSet regs = CreateRegSet(r.regs_user_data.reg_mask, r.regs_user_data.regs); + std::vector& stack = r.stack_user_data.data; + std::vector unwind_ips = UnwindCallChain(*thread, regs, stack); + r.callchain_data.ips.push_back(PERF_CONTEXT_USER); + r.callchain_data.ips.insert(r.callchain_data.ips.end(), unwind_ips.begin(), + unwind_ips.end()); + r.regs_user_data.abi = 0; + r.regs_user_data.reg_mask = 0; + r.regs_user_data.regs.clear(); + r.stack_user_data.data.clear(); + r.stack_user_data.dyn_size = 0; + r.AdjustSizeBasedOnData(); + } + } + } + return record_file_writer_->WriteDataSection(records); +} + bool RecordCommand::DumpAdditionalFeatures(const std::vector& args) { size_t feature_count = (branch_sampling_ != 0 ? 5 : 4); if (!record_file_writer_->WriteFeatureHeader(feature_count)) { diff --git a/simpleperf/cmd_record_test.cpp b/simpleperf/cmd_record_test.cpp index a68a8715..29ddf765 100644 --- a/simpleperf/cmd_record_test.cpp +++ b/simpleperf/cmd_record_test.cpp @@ -121,6 +121,16 @@ TEST(record_cmd, dwarf_callchain_sampling) { } } +TEST(record_cmd, no_unwind_option) { + if (IsDwarfCallChainSamplingSupported()) { + ASSERT_TRUE(RecordCmd()->Run({"--call-graph", "dwarf", "--no-unwind", "sleep", "1"})); + } else { + GTEST_LOG_(INFO) + << "This test does nothing as dwarf callchain sampling is not supported on this device."; + } + ASSERT_FALSE(RecordCmd()->Run({"--no-unwind", "sleep", "1"})); +} + TEST(record_cmd, existing_processes) { std::vector> workloads; CreateProcesses(2, &workloads); diff --git a/simpleperf/record.cpp b/simpleperf/record.cpp index 5398d468..3ebd325c 100644 --- a/simpleperf/record.cpp +++ b/simpleperf/record.cpp @@ -57,6 +57,13 @@ void MoveToBinaryFormat(const T& data, char*& p) { p += sizeof(T); } +template +void MoveToBinaryFormat(const T* data_p, size_t n, char*& p) { + size_t size = n * sizeof(T); + memcpy(p, data_p, size); + p += size; +} + SampleId::SampleId() { memset(this, 0, sizeof(SampleId)); } @@ -180,12 +187,6 @@ MmapRecord::MmapRecord(const perf_event_attr& attr, const perf_event_header* phe sample_id.ReadFromBinaryFormat(attr, p, end); } -void MmapRecord::DumpData(size_t indent) const { - PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid, - data.tid, data.addr, data.len); - PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str()); -} - std::vector MmapRecord::BinaryFormat() const { std::vector buf(header.size); char* p = buf.data(); @@ -197,6 +198,12 @@ std::vector MmapRecord::BinaryFormat() const { return buf; } +void MmapRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid, + data.tid, data.addr, data.len); + PrintIndented(indent, "pgoff 0x%" PRIx64 ", filename %s\n", data.pgoff, filename.c_str()); +} + Mmap2Record::Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); @@ -208,6 +215,17 @@ Mmap2Record::Mmap2Record(const perf_event_attr& attr, const perf_event_header* p sample_id.ReadFromBinaryFormat(attr, p, end); } +std::vector Mmap2Record::BinaryFormat() const { + std::vector buf(header.size); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + MoveToBinaryFormat(data, p); + strcpy(p, filename.c_str()); + p += ALIGN(filename.size() + 1, 8); + sample_id.WriteToBinaryFormat(p); + return buf; +} + void Mmap2Record::DumpData(size_t indent) const { PrintIndented(indent, "pid %u, tid %u, addr 0x%" PRIx64 ", len 0x%" PRIx64 "\n", data.pid, data.tid, data.addr, data.len); @@ -229,10 +247,6 @@ CommRecord::CommRecord(const perf_event_attr& attr, const perf_event_header* phe sample_id.ReadFromBinaryFormat(attr, p, end); } -void CommRecord::DumpData(size_t indent) const { - PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str()); -} - std::vector CommRecord::BinaryFormat() const { std::vector buf(header.size); char* p = buf.data(); @@ -244,6 +258,10 @@ std::vector CommRecord::BinaryFormat() const { return buf; } +void CommRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u, tid %u, comm %s\n", data.pid, data.tid, comm.c_str()); +} + ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); @@ -253,12 +271,7 @@ ExitOrForkRecord::ExitOrForkRecord(const perf_event_attr& attr, const perf_event sample_id.ReadFromBinaryFormat(attr, p, end); } -void ExitOrForkRecord::DumpData(size_t indent) const { - PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data.pid, data.ppid, data.tid, - data.ptid); -} - -std::vector ForkRecord::BinaryFormat() const { +std::vector ExitOrForkRecord::BinaryFormat() const { std::vector buf(header.size); char* p = buf.data(); MoveToBinaryFormat(header, p); @@ -267,6 +280,11 @@ std::vector ForkRecord::BinaryFormat() const { return buf; } +void ExitOrForkRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u, ppid %u, tid %u, ptid %u\n", data.pid, data.ppid, data.tid, + data.ptid); +} + SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader) : Record(pheader) { const char* p = reinterpret_cast(pheader + 1); @@ -349,6 +367,76 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* } } +std::vector SampleRecord::BinaryFormat() const { + std::vector buf(header.size); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + if (sample_type & PERF_SAMPLE_IP) { + MoveToBinaryFormat(ip_data, p); + } + if (sample_type & PERF_SAMPLE_TID) { + MoveToBinaryFormat(tid_data, p); + } + if (sample_type & PERF_SAMPLE_TIME) { + MoveToBinaryFormat(time_data, p); + } + if (sample_type & PERF_SAMPLE_ADDR) { + MoveToBinaryFormat(addr_data, p); + } + if (sample_type & PERF_SAMPLE_ID) { + MoveToBinaryFormat(id_data, p); + } + if (sample_type & PERF_SAMPLE_STREAM_ID) { + MoveToBinaryFormat(stream_id_data, p); + } + if (sample_type & PERF_SAMPLE_CPU) { + MoveToBinaryFormat(cpu_data, p); + } + if (sample_type & PERF_SAMPLE_PERIOD) { + MoveToBinaryFormat(period_data, p); + } + if (sample_type & PERF_SAMPLE_CALLCHAIN) { + uint64_t nr = callchain_data.ips.size(); + MoveToBinaryFormat(nr, p); + MoveToBinaryFormat(callchain_data.ips.data(), nr, p); + } + if (sample_type & PERF_SAMPLE_RAW) { + uint32_t size = raw_data.data.size(); + MoveToBinaryFormat(size, p); + MoveToBinaryFormat(raw_data.data.data(), size, p); + } + if (sample_type & PERF_SAMPLE_BRANCH_STACK) { + uint64_t nr = branch_stack_data.stack.size(); + MoveToBinaryFormat(nr, p); + MoveToBinaryFormat(branch_stack_data.stack.data(), nr, p); + } + if (sample_type & PERF_SAMPLE_REGS_USER) { + MoveToBinaryFormat(regs_user_data.abi, p); + if (regs_user_data.abi != 0) { + MoveToBinaryFormat(regs_user_data.regs.data(), regs_user_data.regs.size(), p); + } + } + if (sample_type & PERF_SAMPLE_STACK_USER) { + uint64_t size = stack_user_data.data.size(); + MoveToBinaryFormat(size, p); + if (size != 0) { + MoveToBinaryFormat(stack_user_data.data.data(), size, p); + MoveToBinaryFormat(stack_user_data.dyn_size, p); + } + } + + // If record command does stack unwinding, sample records' size may be decreased. + // So we can't trust header.size here, and should adjust buffer size based on real need. + buf.resize(p - buf.data()); + return buf; +} + +void SampleRecord::AdjustSizeBasedOnData() { + size_t size = BinaryFormat().size(); + LOG(DEBUG) << "SampleRecord size is changed from " << header.size << " to " << size; + header.size = size; +} + void SampleRecord::DumpData(size_t indent) const { PrintIndented(indent, "sample_type: 0x%" PRIx64 "\n", sample_type); if (sample_type & PERF_SAMPLE_IP) { @@ -432,12 +520,6 @@ BuildIdRecord::BuildIdRecord(const perf_event_header* pheader) : Record(pheader) CHECK_EQ(p, end); } -void BuildIdRecord::DumpData(size_t indent) const { - PrintIndented(indent, "pid %u\n", pid); - PrintIndented(indent, "build_id %s\n", build_id.ToString().c_str()); - PrintIndented(indent, "filename %s\n", filename.c_str()); -} - std::vector BuildIdRecord::BinaryFormat() const { std::vector buf(header.size); char* p = buf.data(); @@ -450,6 +532,29 @@ std::vector BuildIdRecord::BinaryFormat() const { return buf; } +void BuildIdRecord::DumpData(size_t indent) const { + PrintIndented(indent, "pid %u\n", pid); + PrintIndented(indent, "build_id %s\n", build_id.ToString().c_str()); + PrintIndented(indent, "filename %s\n", filename.c_str()); +} + +UnknownRecord::UnknownRecord(const perf_event_header* pheader) : Record(pheader) { + const char* p = reinterpret_cast(pheader + 1); + const char* end = reinterpret_cast(pheader) + pheader->size; + data.insert(data.end(), p, end); +} + +std::vector UnknownRecord::BinaryFormat() const { + std::vector buf(header.size); + char* p = buf.data(); + MoveToBinaryFormat(header, p); + MoveToBinaryFormat(data.data(), data.size(), p); + return buf; +} + +void UnknownRecord::DumpData(size_t) const { +} + static std::unique_ptr ReadRecordFromBuffer(const perf_event_attr& attr, const perf_event_header* pheader) { switch (pheader->type) { @@ -466,7 +571,7 @@ static std::unique_ptr ReadRecordFromBuffer(const perf_event_attr& attr, case PERF_RECORD_SAMPLE: return std::unique_ptr(new SampleRecord(attr, pheader)); default: - return std::unique_ptr(new Record(pheader)); + return std::unique_ptr(new UnknownRecord(pheader)); } } diff --git a/simpleperf/record.h b/simpleperf/record.h index 5997b78f..d7dfe19f 100644 --- a/simpleperf/record.h +++ b/simpleperf/record.h @@ -141,10 +141,10 @@ struct Record { } void Dump(size_t indent = 0) const; + virtual std::vector BinaryFormat() const = 0; protected: - virtual void DumpData(size_t) const { - } + virtual void DumpData(size_t) const = 0; }; struct MmapRecord : public Record { @@ -156,11 +156,11 @@ struct MmapRecord : public Record { } data; std::string filename; - MmapRecord() { // For storage in std::vector. + MmapRecord() { // For CreateMmapRecord. } MmapRecord(const perf_event_attr& attr, const perf_event_header* pheader); - std::vector BinaryFormat() const; + std::vector BinaryFormat() const override; protected: void DumpData(size_t indent) const override; @@ -184,6 +184,7 @@ struct Mmap2Record : public Record { } Mmap2Record(const perf_event_attr& attr, const perf_event_header* pheader); + std::vector BinaryFormat() const override; protected: void DumpData(size_t indent) const override; @@ -199,7 +200,7 @@ struct CommRecord : public Record { } CommRecord(const perf_event_attr& attr, const perf_event_header* pheader); - std::vector BinaryFormat() const; + std::vector BinaryFormat() const override; protected: void DumpData(size_t indent) const override; @@ -215,6 +216,7 @@ struct ExitOrForkRecord : public Record { ExitOrForkRecord() { } ExitOrForkRecord(const perf_event_attr& attr, const perf_event_header* pheader); + std::vector BinaryFormat() const override; protected: void DumpData(size_t indent) const override; @@ -232,7 +234,6 @@ struct ForkRecord : public ExitOrForkRecord { ForkRecord(const perf_event_attr& attr, const perf_event_header* pheader) : ExitOrForkRecord(attr, pheader) { } - std::vector BinaryFormat() const; }; struct SampleRecord : public Record { @@ -254,6 +255,8 @@ struct SampleRecord : public Record { PerfSampleStackUserType stack_user_data; // Valid if PERF_SAMPLE_STACK_USER. SampleRecord(const perf_event_attr& attr, const perf_event_header* pheader); + std::vector BinaryFormat() const override; + void AdjustSizeBasedOnData(); protected: void DumpData(size_t indent) const override; @@ -269,7 +272,19 @@ struct BuildIdRecord : public Record { } BuildIdRecord(const perf_event_header* pheader); - std::vector BinaryFormat() const; + std::vector BinaryFormat() const override; + + protected: + void DumpData(size_t indent) const override; +}; + +// UnknownRecord is used for unknown record types, it makes sure all unknown records +// are not changed when modifying perf.data. +struct UnknownRecord : public Record { + std::vector data; + + UnknownRecord(const perf_event_header* pheader); + std::vector BinaryFormat() const override; protected: void DumpData(size_t indent) const override; diff --git a/simpleperf/record_file.h b/simpleperf/record_file.h index 4cc17574..946c791f 100644 --- a/simpleperf/record_file.h +++ b/simpleperf/record_file.h @@ -50,6 +50,7 @@ class RecordFileWriter { // Read data section that has been written, for further processing. bool ReadDataSection(std::vector>* records); + bool WriteDataSection(const std::vector>& records); bool WriteFeatureHeader(size_t feature_count); bool WriteBuildIdFeature(const std::vector& build_id_records); diff --git a/simpleperf/record_file_writer.cpp b/simpleperf/record_file_writer.cpp index 48ed325c..2d667294 100644 --- a/simpleperf/record_file_writer.cpp +++ b/simpleperf/record_file_writer.cpp @@ -155,6 +155,28 @@ bool RecordFileWriter::ReadDataSection(std::vector>* rec return true; } +bool RecordFileWriter::WriteDataSection(const std::vector>& records) { + // Truncate data section written before. + if (ftruncate(fileno(record_fp_), data_section_offset_) != 0) { + PLOG(ERROR) << "ftruncate() failed"; + return false; + } + uint64_t file_end; + if (!SeekFileEnd(&file_end)) { + return false; + } + CHECK_EQ(data_section_offset_, file_end); + uint64_t old_size = data_section_size_; + data_section_size_ = 0; + for (auto& r : records) { + if (!WriteData(r->BinaryFormat())) { + return false; + } + } + LOG(DEBUG) << "data section is changed from " << old_size << " to " << data_section_size_; + return true; +} + bool RecordFileWriter::SeekFileEnd(uint64_t* file_end) { if (fseek(record_fp_, 0, SEEK_END) == -1) { PLOG(ERROR) << "fseek() failed"; -- 2.11.0