#include <base/strings.h>
#include "command.h"
+#include "dwarf_unwind.h"
#include "environment.h"
#include "event_selection_set.h"
#include "event_type.h"
" 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"
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") {
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);
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_;
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;
}
}
} 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;
}
}
+ 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)
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)) {
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));
}
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();
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);
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);
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();
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);
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);
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);
}
}
+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) {
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();
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) {
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));
}
}
}
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 {
} 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;
}
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;
}
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;
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;
ForkRecord(const perf_event_attr& attr, const perf_event_header* pheader)
: ExitOrForkRecord(attr, pheader) {
}
- std::vector<char> BinaryFormat() const;
};
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;
}
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;