OSDN Git Service

simpleperf: support profiling jited java code.
authorYabin Cui <yabinc@google.com>
Tue, 20 Mar 2018 22:29:03 +0000 (15:29 -0700)
committerYabin Cui <yabinc@google.com>
Wed, 21 Mar 2018 18:25:08 +0000 (11:25 -0700)
1. For each jit symfile, generate a Mmap2Record with a special flag
PROT_JIT_SYMFILE_MAP.
2. Call ReadMmapEventData() before dumping jit Mmap2Records, to keep
the order of samples and mmap records.
3. Handle finding symbols from maps with PROT_JIT_SYMFILE_MAP flags.
4. Pass PROT_JIT_SYMFILE_MAP flag to libunwindstack, to unwind
through jited methods.

Bug: http://b/73127105
Test: run simpleperf manually.
Test: run simpleperf_unit_test.
Change-Id: I2b2f77ff457f7eb2f10193e987a181e4791a29ee

simpleperf/JITDebugReader.cpp
simpleperf/OfflineUnwinder.cpp
simpleperf/cmd_record.cpp
simpleperf/environment.cpp
simpleperf/event_selection_set.h
simpleperf/record.cpp
simpleperf/record.h
simpleperf/thread_tree.cpp
simpleperf/thread_tree.h

index 1185229..ae680e7 100644 (file)
@@ -76,7 +76,7 @@ struct JITCodeEntry {
   }
 };
 
-// Match the format of JITCodeEntry in art/runtime/jit/debugger_itnerface.cc.
+// Match the format of JITCodeEntry in art/runtime/jit/debugger_interface.cc.
 template <typename ADDRT>
 struct __attribute__((packed)) PackedJITCodeEntry {
   ADDRT next_addr;
index 9d56144..ebb0be9 100644 (file)
@@ -23,6 +23,7 @@
 #include <unwindstack/MachineArm64.h>
 #include <unwindstack/MachineX86.h>
 #include <unwindstack/MachineX86_64.h>
+#include <unwindstack/Maps.h>
 #include <unwindstack/Regs.h>
 #include <unwindstack/RegsArm.h>
 #include <unwindstack/RegsArm64.h>
 #include "read_apk.h"
 #include "thread_tree.h"
 
+static_assert(simpleperf::map_flags::PROT_JIT_SYMFILE_MAP == PROT_JIT_SYMFILE_MAP, "");
+static_assert(simpleperf::map_flags::PROT_JIT_SYMFILE_MAP ==
+              unwindstack::MAPS_FLAGS_JIT_SYMFILE_MAP, "");
+
 namespace simpleperf {
 
 static unwindstack::Regs* GetBacktraceRegs(const RegSet& regs) {
@@ -154,7 +159,7 @@ bool OfflineUnwinder::UnwindCallChain(const ThreadEntry& thread, const RegSet& r
           }
         }
       }
-      bt_map.flags = PROT_READ | PROT_EXEC;
+      bt_map.flags = PROT_READ | PROT_EXEC | map->flags;
     }
     cached_map.map.reset(BacktraceMap::CreateOffline(thread.pid, bt_maps));
     if (!cached_map.map) {
index 35d0f93..6b7ce94 100644 (file)
@@ -224,7 +224,8 @@ class RecordCommand : public Command {
         trace_offcpu_(false),
         exclude_kernel_callchain_(false),
         allow_callchain_joiner_(true),
-        callchain_joiner_min_matching_nodes_(1u) {
+        callchain_joiner_min_matching_nodes_(1u),
+        last_record_timestamp_(0u) {
     // If we run `adb shell simpleperf record xxx` and stop profiling by ctrl-c, adb closes
     // sockets connecting simpleperf. After that, simpleperf will receive SIGPIPE when writing
     // to stdout/stderr, which is a problem when we use '--app' option. So ignore SIGPIPE to
@@ -305,6 +306,7 @@ class RecordCommand : public Command {
   std::unique_ptr<CallChainJoiner> callchain_joiner_;
 
   std::unique_ptr<JITDebugReader> jit_debug_reader_;
+  uint64_t last_record_timestamp_;  // used to insert Mmap2Records for JIT debug info
 };
 
 bool RecordCommand::Run(const std::vector<std::string>& args) {
@@ -457,7 +459,8 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
     }
   }
   // Profiling JITed/interpreted code is supported starting from Android P.
-  if (app_pid != 0 && GetAndroidVersion() >= 9) {
+  const int kAndroidVersionP = 9;
+  if (app_pid != 0 && GetAndroidVersion() >= kAndroidVersionP) {
     // JIT symfiles are stored in temporary files, and are deleted after recording. But if
     // `-g --no-unwind` option is used, we want to keep symfiles to support unwinding in
     // the debug-unwind cmd.
@@ -467,7 +470,13 @@ bool RecordCommand::PrepareRecording(Workload* workload) {
     if (!UpdateJITDebugInfo()) {
       return false;
     }
-    if (!loop->AddPeriodicEvent(SecondToTimeval(0.1), [&]() { return UpdateJITDebugInfo(); })) {
+    // It takes about 30us-130us on Pixel (depending on the cpu frequency) to check update when
+    // no update happens (most time spent in process_vm_preadv). We want to know the JIT debug
+    // info change as soon as possible, while not wasting too much time checking updates. So use
+    // a period of 100 ms.
+    const double kUpdateJITDebugInfoPeriodInSecond = 0.1;
+    if (!loop->AddPeriodicEvent(SecondToTimeval(kUpdateJITDebugInfoPeriodInSecond),
+                                [&]() { return UpdateJITDebugInfo(); })) {
       return false;
     }
   }
@@ -1038,6 +1047,7 @@ bool RecordCommand::DumpThreadCommAndMmaps(const perf_event_attr& attr,
 }
 
 bool RecordCommand::ProcessRecord(Record* record) {
+  last_record_timestamp_ = record->Timestamp();
   if (unwind_dwarf_callchain_) {
     if (post_unwind_) {
       return SaveRecordForPostUnwinding(record);
@@ -1105,7 +1115,23 @@ bool RecordCommand::UpdateJITDebugInfo() {
   std::vector<JITSymFile> jit_symfiles;
   std::vector<DexSymFile> dex_symfiles;
   jit_debug_reader_->ReadUpdate(&jit_symfiles, &dex_symfiles);
-  // TODO: Handle jit/dex symfiles.
+  if (jit_symfiles.empty() && dex_symfiles.empty()) {
+    return true;
+  }
+  // Process records before dumping symfiles, so new symfiles won't affect old samples.
+  if (!event_selection_set_.ReadMmapEventData()) {
+    return false;
+  }
+  EventAttrWithId attr_id = event_selection_set_.GetEventAttrWithId()[0];
+  for (auto& symfile : jit_symfiles) {
+    Mmap2Record record(*attr_id.attr, false, jit_debug_reader_->Pid(), jit_debug_reader_->Pid(),
+                       symfile.addr, symfile.len, 0, map_flags::PROT_JIT_SYMFILE_MAP,
+                       symfile.file_path, attr_id.ids[0], last_record_timestamp_);
+    if (!ProcessRecord(&record)) {
+      return false;
+    }
+  }
+  // TODO: Handle dex symfiles.
   return true;
 }
 
index 3267870..bd93d8d 100644 (file)
@@ -732,7 +732,10 @@ bool SignalIsIgnored(int signo) {
 int GetAndroidVersion() {
 #if defined(__ANDROID__)
   std::string s = android::base::GetProperty("ro.build.version.release", "");
+  // The release string can be a list of numbers (like 8.1.0), a character (like Q)
+  // or many characters (like OMR1).
   if (!s.empty()) {
+    // Each Android version has a version number: L is 5, M is 6, N is 7, O is 8, etc.
     if (s[0] >= 'A' && s[0] <= 'Z') {
       return s[0] - 'O' + 8;
     }
index 7397c6f..9e2a064 100644 (file)
@@ -128,6 +128,7 @@ class EventSelectionSet {
   bool ReadCounters(std::vector<CountersInfo>* counters);
   bool MmapEventFiles(size_t min_mmap_pages, size_t max_mmap_pages);
   bool PrepareToReadMmapEventData(const std::function<bool(Record*)>& callback);
+  bool ReadMmapEventData();
   bool FinishReadMmapEventData();
 
   // If monitored_cpus is empty, monitor all cpus.
@@ -160,7 +161,6 @@ class EventSelectionSet {
                              std::string* failed_event_type);
 
   bool MmapEventFiles(size_t mmap_pages, bool report_error);
-  bool ReadMmapEventData();
 
   bool DetectCpuHotplugEvents();
   bool HandleCpuOnlineEvent(int cpu);
index a03f377..5ae2dd6 100644 (file)
@@ -265,6 +265,22 @@ Mmap2Record::Mmap2Record(const perf_event_attr& attr, char* p) : Record(p) {
   sample_id.ReadFromBinaryFormat(attr, p, end);
 }
 
+Mmap2Record::Mmap2Record(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+                         uint64_t addr, uint64_t len, uint64_t pgoff, uint32_t prot,
+                         const std::string& filename, uint64_t event_id, uint64_t time) {
+  SetTypeAndMisc(PERF_RECORD_MMAP2, in_kernel ? PERF_RECORD_MISC_KERNEL : PERF_RECORD_MISC_USER);
+  sample_id.CreateContent(attr, event_id);
+  sample_id.time_data.time = time;
+  Mmap2RecordDataType data;
+  data.pid = pid;
+  data.tid = tid;
+  data.addr = addr;
+  data.len = len;
+  data.pgoff = pgoff;
+  data.prot = prot;
+  SetDataAndFilename(data, filename);
+}
+
 void Mmap2Record::SetDataAndFilename(const Mmap2RecordDataType& data,
                                      const std::string& filename) {
   SetSize(header_size() + sizeof(data) + Align(filename.size() + 1, 8) +
@@ -285,7 +301,7 @@ 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);
-  PrintIndented(indent, "pgoff 0x" PRIx64 ", maj %u, min %u, ino %" PRId64
+  PrintIndented(indent, "pgoff 0x%" PRIx64 ", maj %u, min %u, ino %" PRId64
                         ", ino_generation %" PRIu64 "\n",
                 data->pgoff, data->maj, data->min, data->ino,
                 data->ino_generation);
index b862e40..6e5806c 100644 (file)
@@ -303,6 +303,9 @@ struct Mmap2Record : public Record {
   const char* filename;
 
   Mmap2Record(const perf_event_attr& attr, char* p);
+  Mmap2Record(const perf_event_attr& attr, bool in_kernel, uint32_t pid, uint32_t tid,
+              uint64_t addr, uint64_t len, uint64_t pgoff, uint32_t prot,
+              const std::string& filename, uint64_t event_id, uint64_t time = 0);
 
   void SetDataAndFilename(const Mmap2RecordDataType& data,
                           const std::string& filename);
index 283c697..01ba8db 100644 (file)
@@ -128,11 +128,11 @@ Dso* ThreadTree::FindKernelDsoOrNew(const std::string& filename) {
 
 void ThreadTree::AddThreadMap(int pid, int tid, uint64_t start_addr,
                               uint64_t len, uint64_t pgoff, uint64_t time,
-                              const std::string& filename) {
+                              const std::string& filename, uint32_t flags) {
   ThreadEntry* thread = FindThreadOrNew(pid, tid);
   Dso* dso = FindUserDsoOrNew(filename, start_addr);
   MapEntry* map =
-      AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false));
+      AllocateMap(MapEntry(start_addr, len, pgoff, time, dso, false, flags));
   FixOverlappedMap(thread->maps, map);
   auto pair = thread->maps->maps.insert(map);
   CHECK(pair.second);
@@ -227,7 +227,11 @@ const Symbol* ThreadTree::FindSymbol(const MapEntry* map, uint64_t ip,
   Dso* dso = map->dso;
   if (!map->in_kernel) {
     // Find symbol in user space shared libraries.
-    vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
+    if (map->flags & map_flags::PROT_JIT_SYMFILE_MAP) {
+      vaddr_in_file = ip;
+    } else {
+      vaddr_in_file = ip - map->start_addr + map->dso->MinVirtualAddress();
+    }
     symbol = dso->FindSymbol(vaddr_in_file);
   } else {
     if (dso != kernel_dso_.get()) {
@@ -311,7 +315,7 @@ void ThreadTree::Update(const Record& record) {
                                  ? "[unknown]"
                                  : r.filename;
       AddThreadMap(r.data->pid, r.data->tid, r.data->addr, r.data->len,
-                   r.data->pgoff, r.sample_id.time_data.time, filename);
+                   r.data->pgoff, r.sample_id.time_data.time, filename, r.data->prot);
     }
   } else if (record.type() == PERF_RECORD_COMM) {
     const CommRecord& r = *static_cast<const CommRecord*>(&record);
index a3653f5..1117702 100644 (file)
@@ -34,6 +34,10 @@ constexpr char DEFAULT_EXECNAME_FOR_THREAD_MMAP[] = "//anon";
 
 namespace simpleperf {
 
+namespace map_flags {
+constexpr uint32_t PROT_JIT_SYMFILE_MAP = 0x4000;
+}  // namespace map_flags
+
 struct MapEntry {
   uint64_t start_addr;
   uint64_t len;
@@ -41,15 +45,18 @@ struct MapEntry {
   uint64_t time;  // Map creation time.
   Dso* dso;
   bool in_kernel;
+  uint32_t flags;
+
 
   MapEntry(uint64_t start_addr, uint64_t len, uint64_t pgoff, uint64_t time,
-           Dso* dso, bool in_kernel)
+           Dso* dso, bool in_kernel, uint32_t flags = 0)
       : start_addr(start_addr),
         len(len),
         pgoff(pgoff),
         time(time),
         dso(dso),
-        in_kernel(in_kernel) {}
+        in_kernel(in_kernel),
+        flags(flags) {}
   MapEntry() {}
 
   uint64_t get_end_addr() const { return start_addr + len; }
@@ -95,7 +102,8 @@ class ThreadTree {
   void AddKernelMap(uint64_t start_addr, uint64_t len, uint64_t pgoff,
                     uint64_t time, const std::string& filename);
   void AddThreadMap(int pid, int tid, uint64_t start_addr, uint64_t len,
-                    uint64_t pgoff, uint64_t time, const std::string& filename);
+                    uint64_t pgoff, uint64_t time, const std::string& filename,
+                    uint32_t flags = 0);
   const MapEntry* FindMap(const ThreadEntry* thread, uint64_t ip,
                           bool in_kernel);
   // Find map for an ip address when we don't know whether it is in kernel.