diff options
author | Yabin Cui <yabinc@google.com> | 2015-10-06 22:02:30 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2015-10-06 22:02:30 +0000 |
commit | b1f20fa48b65b2f03f9303ffa2aa381cd1d3dd6f (patch) | |
tree | cf7b44d7e09f9359abe38c7b458b614acc656234 /simpleperf | |
parent | d210ccde345d11dd56f6239e8427d66754b6026c (diff) | |
parent | cb84c9885e7a9f82cefba566d74e5c71214ab4c9 (diff) | |
download | extras-b1f20fa48b65b2f03f9303ffa2aa381cd1d3dd6f.tar.gz |
Merge "Simpleperf: do dwarf unwinding in record command."
Diffstat (limited to 'simpleperf')
-rw-r--r-- | simpleperf/cmd_record.cpp | 58 | ||||
-rw-r--r-- | simpleperf/cmd_record_test.cpp | 10 | ||||
-rw-r--r-- | simpleperf/record.cpp | 151 | ||||
-rw-r--r-- | simpleperf/record.h | 29 | ||||
-rw-r--r-- | simpleperf/record_file.h | 1 | ||||
-rw-r--r-- | 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 <base/strings.h> #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<pid_t>& selected_threads); + bool UnwindDwarfCallChain(); bool DumpAdditionalFeatures(const std::vector<std::string>& args); bool DumpBuildIdFeature(); bool GetHitFiles(std::set<std::string>* kernel_modules, std::set<std::string>* 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<pid_t> monitored_threads_; std::vector<EventTypeAndModifier> measured_event_types_; @@ -225,7 +232,14 @@ bool RecordCommand::Run(const std::vector<std::string>& 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<std::string>& 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<std::string>& 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<std::unique_ptr<Record>> 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<SampleRecord*>(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<char>& stack = r.stack_user_data.data; + std::vector<uint64_t> 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<std::string>& 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<std::unique_ptr<Workload>> 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 <class T> +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<char> MmapRecord::BinaryFormat() const { std::vector<char> buf(header.size); char* p = buf.data(); @@ -197,6 +198,12 @@ std::vector<char> 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<const char*>(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<char> Mmap2Record::BinaryFormat() const { + std::vector<char> 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<char> CommRecord::BinaryFormat() const { std::vector<char> buf(header.size); char* p = buf.data(); @@ -244,6 +258,10 @@ std::vector<char> 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<const char*>(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<char> ForkRecord::BinaryFormat() const { +std::vector<char> ExitOrForkRecord::BinaryFormat() const { std::vector<char> buf(header.size); char* p = buf.data(); MoveToBinaryFormat(header, p); @@ -267,6 +280,11 @@ std::vector<char> 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<const char*>(pheader + 1); @@ -349,6 +367,76 @@ SampleRecord::SampleRecord(const perf_event_attr& attr, const perf_event_header* } } +std::vector<char> SampleRecord::BinaryFormat() const { + std::vector<char> 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<char> BuildIdRecord::BinaryFormat() const { std::vector<char> buf(header.size); char* p = buf.data(); @@ -450,6 +532,29 @@ std::vector<char> 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<const char*>(pheader + 1); + const char* end = reinterpret_cast<const char*>(pheader) + pheader->size; + data.insert(data.end(), p, end); +} + +std::vector<char> UnknownRecord::BinaryFormat() const { + std::vector<char> 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<Record> ReadRecordFromBuffer(const perf_event_attr& attr, const perf_event_header* pheader) { switch (pheader->type) { @@ -466,7 +571,7 @@ static std::unique_ptr<Record> ReadRecordFromBuffer(const perf_event_attr& attr, case PERF_RECORD_SAMPLE: return std::unique_ptr<Record>(new SampleRecord(attr, pheader)); default: - return std::unique_ptr<Record>(new Record(pheader)); + return std::unique_ptr<Record>(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<char> 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<char> BinaryFormat() const; + std::vector<char> 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<char> 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<char> BinaryFormat() const; + std::vector<char> 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<char> 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<char> 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<char> 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<char> BinaryFormat() const; + std::vector<char> 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<char> data; + + UnknownRecord(const perf_event_header* pheader); + std::vector<char> 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<std::unique_ptr<Record>>* records); + bool WriteDataSection(const std::vector<std::unique_ptr<Record>>& records); bool WriteFeatureHeader(size_t feature_count); bool WriteBuildIdFeature(const std::vector<BuildIdRecord>& 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<std::unique_ptr<Record>>* rec return true; } +bool RecordFileWriter::WriteDataSection(const std::vector<std::unique_ptr<Record>>& 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"; |