summaryrefslogtreecommitdiff
path: root/simpleperf
diff options
context:
space:
mode:
authorYabin Cui <yabinc@google.com>2015-10-06 22:02:30 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2015-10-06 22:02:30 +0000
commitb1f20fa48b65b2f03f9303ffa2aa381cd1d3dd6f (patch)
treecf7b44d7e09f9359abe38c7b458b614acc656234 /simpleperf
parentd210ccde345d11dd56f6239e8427d66754b6026c (diff)
parentcb84c9885e7a9f82cefba566d74e5c71214ab4c9 (diff)
downloadextras-b1f20fa48b65b2f03f9303ffa2aa381cd1d3dd6f.tar.gz
Merge "Simpleperf: do dwarf unwinding in record command."
Diffstat (limited to 'simpleperf')
-rw-r--r--simpleperf/cmd_record.cpp58
-rw-r--r--simpleperf/cmd_record_test.cpp10
-rw-r--r--simpleperf/record.cpp151
-rw-r--r--simpleperf/record.h29
-rw-r--r--simpleperf/record_file.h1
-rw-r--r--simpleperf/record_file_writer.cpp22
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";