OSDN Git Service

Simpleperf: do dwarf unwinding in record command.
authorYabin Cui <yabinc@google.com>
Thu, 1 Oct 2015 00:22:35 +0000 (17:22 -0700)
committerYabin Cui <yabinc@google.com>
Fri, 2 Oct 2015 17:47:45 +0000 (10:47 -0700)
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
simpleperf/cmd_record_test.cpp
simpleperf/record.cpp
simpleperf/record.h
simpleperf/record_file.h
simpleperf/record_file_writer.cpp

index 8776c0f..04c6d7d 100644 (file)
@@ -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)) {
index a68a871..29ddf76 100644 (file)
@@ -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);
index 5398d46..3ebd325 100644 (file)
@@ -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));
   }
 }
 
index 5997b78..d7dfe19 100644 (file)
@@ -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;
index 4cc1757..946c791 100644 (file)
@@ -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);
index 48ed325..2d66729 100644 (file)
@@ -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";