[Lldb-commits] [lldb] e6c84f8 - Add thin wrapper for perf_event_open API

Jakob Johnson via lldb-commits lldb-commits at lists.llvm.org
Mon Mar 21 13:39:11 PDT 2022


Author: Jakob Johnson
Date: 2022-03-21T13:38:52-07:00
New Revision: e6c84f82b87576a57d1fa1c7e8c289d3d4fa7ab1

URL: https://github.com/llvm/llvm-project/commit/e6c84f82b87576a57d1fa1c7e8c289d3d4fa7ab1
DIFF: https://github.com/llvm/llvm-project/commit/e6c84f82b87576a57d1fa1c7e8c289d3d4fa7ab1.diff

LOG: Add thin wrapper for perf_event_open API
  - Add PerfEvent class to handle creating ring buffers and handle the resources associated with a perf_event
  - Refactor IntelPT collection code to use this new API
  - Add TSC to timestamp conversion logic with unittest

Differential Revision: https://reviews.llvm.org/D121734

Added: 
    lldb/source/Plugins/Process/Linux/Perf.cpp
    lldb/source/Plugins/Process/Linux/Perf.h
    lldb/unittests/Process/Linux/PerfTests.cpp

Modified: 
    lldb/source/Plugins/Process/Linux/CMakeLists.txt
    lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp
    lldb/source/Plugins/Process/Linux/IntelPTCollector.h
    lldb/unittests/Process/Linux/CMakeLists.txt
    lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp

Removed: 
    


################################################################################
diff  --git a/lldb/source/Plugins/Process/Linux/CMakeLists.txt b/lldb/source/Plugins/Process/Linux/CMakeLists.txt
index 60958bb913960..cc70edba3483e 100644
--- a/lldb/source/Plugins/Process/Linux/CMakeLists.txt
+++ b/lldb/source/Plugins/Process/Linux/CMakeLists.txt
@@ -8,6 +8,7 @@ add_lldb_library(lldbPluginProcessLinux
   NativeRegisterContextLinux_s390x.cpp
   NativeRegisterContextLinux_x86_64.cpp
   NativeThreadLinux.cpp
+  Perf.cpp
   SingleStepCheck.cpp
 
   LINK_LIBS

diff  --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp
index 0e65c88a1f765..5f31c10f8b88b 100644
--- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp
+++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.cpp
@@ -6,19 +6,23 @@
 //
 //===----------------------------------------------------------------------===//
 
-#include <algorithm>
-#include <fstream>
-#include <sstream>
+#include "IntelPTCollector.h"
 
-#include "llvm/ADT/StringRef.h"
-#include "llvm/Support/Error.h"
-#include "llvm/Support/MathExtras.h"
+#include "Perf.h"
 
-#include "IntelPTCollector.h"
 #include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
 #include "lldb/Host/linux/Support.h"
 #include "lldb/Utility/StreamString.h"
 
+#include "llvm/ADT/StringRef.h"
+#include "llvm/Support/Error.h"
+#include "llvm/Support/MathExtras.h"
+
+#include <algorithm>
+#include <cstddef>
+#include <fstream>
+#include <linux/perf_event.h>
+#include <sstream>
 #include <sys/ioctl.h>
 #include <sys/syscall.h>
 
@@ -53,6 +57,21 @@ enum IntelPTConfigFileType {
   BitOffset
 };
 
+/// Get the content of /proc/cpuinfo that can be later used to decode traces.
+static Expected<ArrayRef<uint8_t>> GetCPUInfo() {
+  static llvm::Optional<std::vector<uint8_t>> cpu_info;
+  if (!cpu_info) {
+    auto buffer_or_error = errorOrToExpected(getProcFile("cpuinfo"));
+    if (!buffer_or_error)
+      return buffer_or_error.takeError();
+    MemoryBuffer &buffer = **buffer_or_error;
+    cpu_info = std::vector<uint8_t>(
+        reinterpret_cast<const uint8_t *>(buffer.getBufferStart()),
+        reinterpret_cast<const uint8_t *>(buffer.getBufferEnd()));
+  }
+  return *cpu_info;
+}
+
 static Expected<uint32_t> ReadIntelPTConfigFile(const char *file,
                                                 IntelPTConfigFileType type) {
   ErrorOr<std::unique_ptr<MemoryBuffer>> stream =
@@ -106,6 +125,7 @@ static Expected<uint32_t> ReadIntelPTConfigFile(const char *file,
   }
   return value;
 }
+
 /// Return the Linux perf event type for Intel PT.
 static Expected<uint32_t> GetOSEventType() {
   return ReadIntelPTConfigFile(kOSEventIntelPTTypeFile,
@@ -148,7 +168,7 @@ size_t IntelPTThreadTrace::GetTraceBufferSize() const {
 #ifndef PERF_ATTR_SIZE_VER5
   llvm_unreachable("Intel PT Linux perf event not supported");
 #else
-  return m_mmap_meta->aux_size;
+  return m_perf_event.GetAuxBuffer().size();
 #endif
 }
 
@@ -176,30 +196,9 @@ GeneratePerfEventConfigValue(bool enable_tsc, Optional<size_t> psb_period) {
   return config;
 }
 
-Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid,
-                                     uint64_t buffer_size, bool enable_tsc,
-                                     Optional<size_t> psb_period) {
-#ifndef PERF_ATTR_SIZE_VER5
-  llvm_unreachable("Intel PT Linux perf event not supported");
-#else
-  Log *log = GetLog(POSIXLog::Ptrace);
-
-  m_tid = tid;
-  LLDB_LOG(log, "called thread id {0}", tid);
-  uint64_t page_size = getpagesize();
-
-  if (__builtin_popcount(buffer_size) != 1 || buffer_size < 4096) {
-    return createStringError(
-        inconvertibleErrorCode(),
-        "The trace buffer size must be a power of 2 greater than or equal to "
-        "4096 (2^12) bytes. It was %" PRIu64 ".",
-        buffer_size);
-  }
-  uint64_t numpages = static_cast<uint64_t>(
-      llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size));
-  numpages = std::max<uint64_t>(1, numpages);
-  buffer_size = page_size * numpages;
-
+llvm::Expected<perf_event_attr>
+IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(
+    bool enable_tsc, Optional<size_t> psb_period) {
   perf_event_attr attr;
   memset(&attr, 0, sizeof(attr));
   attr.size = sizeof(attr);
@@ -213,106 +212,59 @@ Error IntelPTThreadTrace::StartTrace(lldb::pid_t pid, lldb::tid_t tid,
   if (Expected<uint64_t> config_value =
           GeneratePerfEventConfigValue(enable_tsc, psb_period)) {
     attr.config = *config_value;
-    LLDB_LOG(log, "intel pt config {0}", attr.config);
   } else {
     return config_value.takeError();
   }
 
   if (Expected<uint32_t> intel_pt_type = GetOSEventType()) {
     attr.type = *intel_pt_type;
-    LLDB_LOG(log, "intel pt type {0}", attr.type);
   } else {
     return intel_pt_type.takeError();
   }
 
-  LLDB_LOG(log, "buffer size {0} ", buffer_size);
-
-  errno = 0;
-  auto fd =
-      syscall(SYS_perf_event_open, &attr, static_cast<::tid_t>(tid), -1, -1, 0);
-  if (fd == -1) {
-    LLDB_LOG(log, "syscall error {0}", errno);
-    return createStringError(inconvertibleErrorCode(),
-                             "perf event syscall failed");
-  }
-
-  m_fd = std::unique_ptr<int, file_close>(new int(fd), file_close());
-
-  errno = 0;
-  auto base =
-      mmap(nullptr, (buffer_size + page_size), PROT_WRITE, MAP_SHARED, fd, 0);
-
-  if (base == MAP_FAILED) {
-    LLDB_LOG(log, "mmap base error {0}", errno);
-    return createStringError(inconvertibleErrorCode(),
-                             "Meta buffer allocation failed");
-  }
-
-  m_mmap_meta = std::unique_ptr<perf_event_mmap_page, munmap_delete>(
-      reinterpret_cast<perf_event_mmap_page *>(base),
-      munmap_delete(buffer_size + page_size));
-
-  m_mmap_meta->aux_offset = m_mmap_meta->data_offset + m_mmap_meta->data_size;
-  m_mmap_meta->aux_size = buffer_size;
-
-  errno = 0;
-  auto mmap_aux = mmap(nullptr, buffer_size, PROT_READ, MAP_SHARED, fd,
-                       static_cast<long int>(m_mmap_meta->aux_offset));
-
-  if (mmap_aux == MAP_FAILED) {
-    LLDB_LOG(log, "second mmap done {0}", errno);
-    return createStringError(inconvertibleErrorCode(),
-                             "Trace buffer allocation failed");
-  }
-  m_mmap_aux = std::unique_ptr<uint8_t, munmap_delete>(
-      reinterpret_cast<uint8_t *>(mmap_aux), munmap_delete(buffer_size));
-  return Error::success();
-#endif
+  return attr;
 }
 
-llvm::MutableArrayRef<uint8_t> IntelPTThreadTrace::GetDataBuffer() const {
+llvm::Expected<IntelPTThreadTraceUP>
+IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size,
+                           bool enable_tsc, Optional<size_t> psb_period) {
 #ifndef PERF_ATTR_SIZE_VER5
   llvm_unreachable("Intel PT Linux perf event not supported");
 #else
-  return MutableArrayRef<uint8_t>(
-      (reinterpret_cast<uint8_t *>(m_mmap_meta.get()) +
-       m_mmap_meta->data_offset),
-      m_mmap_meta->data_size);
-#endif
-}
+  Log *log = GetLog(POSIXLog::Ptrace);
 
-llvm::MutableArrayRef<uint8_t> IntelPTThreadTrace::GetAuxBuffer() const {
-#ifndef PERF_ATTR_SIZE_VER5
-  llvm_unreachable("Intel PT Linux perf event not supported");
-#else
-  return MutableArrayRef<uint8_t>(m_mmap_aux.get(), m_mmap_meta->aux_size);
-#endif
-}
+  LLDB_LOG(log, "called thread id {0}", tid);
 
-Expected<ArrayRef<uint8_t>> IntelPTThreadTrace::GetCPUInfo() {
-  static llvm::Optional<std::vector<uint8_t>> cpu_info;
-  if (!cpu_info) {
-    auto buffer_or_error = getProcFile("cpuinfo");
-    if (!buffer_or_error)
-      return Status(buffer_or_error.getError()).ToError();
-    MemoryBuffer &buffer = **buffer_or_error;
-    cpu_info = std::vector<uint8_t>(
-        reinterpret_cast<const uint8_t *>(buffer.getBufferStart()),
-        reinterpret_cast<const uint8_t *>(buffer.getBufferEnd()));
+  if (__builtin_popcount(buffer_size) != 1 || buffer_size < 4096) {
+    return createStringError(
+        inconvertibleErrorCode(),
+        "The trace buffer size must be a power of 2 greater than or equal to "
+        "4096 (2^12) bytes. It was %" PRIu64 ".",
+        buffer_size);
   }
-  return *cpu_info;
-}
+  uint64_t page_size = getpagesize();
+  uint64_t buffer_numpages = static_cast<uint64_t>(
+      llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size));
 
-llvm::Expected<IntelPTThreadTraceUP>
-IntelPTThreadTrace::Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size,
-                           bool enable_tsc, Optional<size_t> psb_period) {
-  IntelPTThreadTraceUP thread_trace_up(new IntelPTThreadTrace());
+  Expected<perf_event_attr> attr =
+      IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc,
+                                                              psb_period);
+  if (!attr)
+    return attr.takeError();
 
-  if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size,
-                                                    enable_tsc, psb_period))
-    return std::move(err);
+  LLDB_LOG(log, "buffer size {0} ", buffer_size);
 
-  return std::move(thread_trace_up);
+  if (Expected<PerfEvent> perf_event = PerfEvent::Init(*attr, tid)) {
+    if (Error mmap_err = perf_event->MmapMetadataAndBuffers(buffer_numpages,
+                                                            buffer_numpages)) {
+      return std::move(mmap_err);
+    }
+    return IntelPTThreadTraceUP(
+        new IntelPTThreadTrace(std::move(*perf_event), tid));
+  } else {
+    return perf_event.takeError();
+  }
+#endif
 }
 
 Expected<std::vector<uint8_t>>
@@ -331,6 +283,8 @@ IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer,
 #ifndef PERF_ATTR_SIZE_VER5
   llvm_unreachable("perf event not supported");
 #else
+  auto fd = m_perf_event.GetFd();
+  perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
   // Disable the perf event to force a flush out of the CPU's internal buffer.
   // Besides, we can guarantee that the CPU won't override any data as we are
   // reading the buffer.
@@ -346,13 +300,13 @@ IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer,
   //
   // This is achieved by the PERF_EVENT_IOC_DISABLE ioctl request, as mentioned
   // in the man page of perf_event_open.
-  ioctl(*m_fd, PERF_EVENT_IOC_DISABLE);
+  ioctl(fd, PERF_EVENT_IOC_DISABLE);
 
   Log *log = GetLog(POSIXLog::Ptrace);
   Status error;
-  uint64_t head = m_mmap_meta->aux_head;
+  uint64_t head = mmap_metadata.aux_head;
 
-  LLDB_LOG(log, "Aux size -{0} , Head - {1}", m_mmap_meta->aux_size, head);
+  LLDB_LOG(log, "Aux size -{0} , Head - {1}", mmap_metadata.aux_size, head);
 
   /**
    * When configured as ring buffer, the aux buffer keeps wrapping around
@@ -366,11 +320,12 @@ IntelPTThreadTrace::ReadPerfTraceAux(llvm::MutableArrayRef<uint8_t> &buffer,
    *
    * */
 
-  ReadCyclicBuffer(buffer, GetAuxBuffer(), static_cast<size_t>(head), offset);
-  LLDB_LOG(log, "ReadCyclic BUffer Done");
+  ReadCyclicBuffer(buffer, m_perf_event.GetAuxBuffer(),
+                   static_cast<size_t>(head), offset);
+  LLDB_LOG(log, "ReadCyclic Buffer Done");
 
   // Reenable tracing now we have read the buffer
-  ioctl(*m_fd, PERF_EVENT_IOC_ENABLE);
+  ioctl(fd, PERF_EVENT_IOC_ENABLE);
   return error;
 #endif
 }
@@ -385,7 +340,8 @@ IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer,
   uint64_t bytes_remaining = buffer.size();
   Status error;
 
-  uint64_t head = m_mmap_meta->data_head;
+  perf_event_mmap_page &mmap_metadata = m_perf_event.GetMetadataPage();
+  uint64_t head = mmap_metadata.data_head;
 
   /*
    * The data buffer and aux buffer have 
diff erent implementations
@@ -397,11 +353,11 @@ IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer,
 
   LLDB_LOG(log, "bytes_remaining - {0}", bytes_remaining);
 
-  auto data_buffer = GetDataBuffer();
+  auto data_buffer = m_perf_event.GetDataBuffer();
 
   if (head > data_buffer.size()) {
     head = head % data_buffer.size();
-    LLDB_LOG(log, "Data size -{0} Head - {1}", m_mmap_meta->data_size, head);
+    LLDB_LOG(log, "Data size -{0} Head - {1}", mmap_metadata.data_size, head);
 
     ReadCyclicBuffer(buffer, data_buffer, static_cast<size_t>(head), offset);
     bytes_remaining -= buffer.size();
@@ -424,7 +380,7 @@ IntelPTThreadTrace::ReadPerfTraceData(llvm::MutableArrayRef<uint8_t> &buffer,
 }
 
 void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
-                                          llvm::MutableArrayRef<uint8_t> src,
+                                          llvm::ArrayRef<uint8_t> src,
                                           size_t src_cyc_index, size_t offset) {
 
   Log *log = GetLog(POSIXLog::Ptrace);
@@ -450,7 +406,7 @@ void IntelPTThreadTrace::ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
     return;
   }
 
-  llvm::SmallVector<MutableArrayRef<uint8_t>, 2> parts = {
+  llvm::SmallVector<ArrayRef<uint8_t>, 2> parts = {
       src.slice(src_cyc_index), src.take_front(src_cyc_index)};
 
   if (offset > parts[0].size()) {
@@ -624,7 +580,7 @@ Error IntelPTCollector::OnThreadDestroyed(lldb::tid_t tid) {
 }
 
 Expected<json::Value> IntelPTCollector::GetState() const {
-  Expected<ArrayRef<uint8_t>> cpu_info = IntelPTThreadTrace::GetCPUInfo();
+  Expected<ArrayRef<uint8_t>> cpu_info = GetCPUInfo();
   if (!cpu_info)
     return cpu_info.takeError();
 
@@ -661,7 +617,7 @@ IntelPTCollector::GetBinaryData(const TraceGetBinaryDataRequest &request) const
     else
       return trace.takeError();
   } else if (request.kind == "cpuInfo") {
-    return IntelPTThreadTrace::GetCPUInfo();
+    return GetCPUInfo();
   }
   return createStringError(inconvertibleErrorCode(),
                            "Unsuported trace binary data kind: %s",

diff  --git a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h
index 051b1ad285336..12fa12dc299d8 100644
--- a/lldb/source/Plugins/Process/Linux/IntelPTCollector.h
+++ b/lldb/source/Plugins/Process/Linux/IntelPTCollector.h
@@ -9,11 +9,11 @@
 #ifndef liblldb_IntelPTCollector_H_
 #define liblldb_IntelPTCollector_H_
 
+#include "Perf.h"
+
 #include "lldb/Utility/Status.h"
 #include "lldb/Utility/TraceIntelPTGDBRemotePackets.h"
 #include "lldb/lldb-types.h"
-#include "llvm/ADT/DenseMap.h"
-#include "llvm/ADT/DenseSet.h"
 
 #include <linux/perf_event.h>
 #include <sys/mman.h>
@@ -31,42 +31,15 @@ class IntelPTThreadTrace;
 typedef std::unique_ptr<IntelPTThreadTrace> IntelPTThreadTraceUP;
 
 class IntelPTThreadTrace {
-
-  class munmap_delete {
-    size_t m_length;
-
-  public:
-    munmap_delete(size_t length) : m_length(length) {}
-    void operator()(void *ptr) {
-      if (m_length)
-        munmap(ptr, m_length);
-    }
-  };
-
-  class file_close {
-
-  public:
-    file_close() = default;
-    void operator()(int *ptr) {
-      if (ptr == nullptr)
-        return;
-      if (*ptr == -1)
-        return;
-      close(*ptr);
-      std::default_delete<int>()(ptr);
-    }
-  };
-
-  std::unique_ptr<perf_event_mmap_page, munmap_delete> m_mmap_meta;
-  std::unique_ptr<uint8_t, munmap_delete> m_mmap_aux;
-  std::unique_ptr<int, file_close> m_fd;
-  lldb::tid_t m_tid;
-
-  /// Start tracing a thread
+public:
+  /// Create a new \a IntelPTThreadTrace and start tracing the thread.
   ///
   /// \param[in] pid
   ///     The pid of the process whose thread will be traced.
   ///
+  /// \param[in] tid
+  ///     The tid of the thread to be traced.
+  ///
   /// \param[in] buffer_size
   ///     Size of the thread buffer in bytes.
   ///
@@ -79,33 +52,22 @@ class IntelPTThreadTrace {
   ///     More information in TraceIntelPT::GetStartConfigurationHelp().
   ///
   /// \return
-  ///     \a llvm::Error::success if tracing was successful, or an
-  ///     \a llvm::Error otherwise.
-  llvm::Error StartTrace(lldb::pid_t pid, lldb::tid_t tid, uint64_t buffer_size,
-                         bool enable_tsc, llvm::Optional<size_t> psb_period);
-
-  llvm::MutableArrayRef<uint8_t> GetAuxBuffer() const;
-  llvm::MutableArrayRef<uint8_t> GetDataBuffer() const;
-
-  IntelPTThreadTrace()
-      : m_mmap_meta(nullptr, munmap_delete(0)),
-        m_mmap_aux(nullptr, munmap_delete(0)), m_fd(nullptr, file_close()) {}
-
-public:
-  /// Get the content of /proc/cpuinfo that can be later used to decode traces.
-  static llvm::Expected<llvm::ArrayRef<uint8_t>> GetCPUInfo();
-
-  /// Start tracing a thread.
-  ///
-  /// See \a StartTrace.
-  ///
-  /// \return
   ///   A \a IntelPTThreadTrace instance if tracing was successful, or
   ///   an \a llvm::Error otherwise.
   static llvm::Expected<IntelPTThreadTraceUP>
   Create(lldb::pid_t pid, lldb::tid_t tid, size_t buffer_size, bool enable_tsc,
          llvm::Optional<size_t> psb_period);
 
+  /// Create a \a perf_event_attr configured for
+  /// an IntelPT event.
+  ///
+  /// \return
+  ///   A \a perf_event_attr if successful,
+  ///   or an \a llvm::Error otherwise.
+  static llvm::Expected<perf_event_attr>
+  CreateIntelPTPerfEventConfiguration(bool enable_tsc,
+                                      llvm::Optional<size_t> psb_period);
+
   /// Read the trace buffer of the currently traced thread.
   ///
   /// \param[in] offset
@@ -146,11 +108,29 @@ class IntelPTThreadTrace {
   /// \param[in] offset
   ///     The offset to begin reading the data in the cyclic buffer.
   static void ReadCyclicBuffer(llvm::MutableArrayRef<uint8_t> &dst,
-                               llvm::MutableArrayRef<uint8_t> src,
+                               llvm::ArrayRef<uint8_t> src,
                                size_t src_cyc_index, size_t offset);
 
   /// Return the thread-specific part of the jLLDBTraceGetState packet.
   TraceThreadState GetState() const;
+
+private:
+  /// Construct new \a IntelPTThreadTrace. Users are supposed to create
+  /// instances of this class via the \a Create() method and not invoke this one
+  /// directly.
+  ///
+  /// \param[in] perf_event
+  ///   perf event configured for IntelPT.
+  ///
+  /// \param[in] tid
+  ///   The thread being traced.
+  IntelPTThreadTrace(PerfEvent &&perf_event, lldb::tid_t tid)
+      : m_perf_event(std::move(perf_event)), m_tid(tid) {}
+
+  /// perf event configured for IntelPT.
+  PerfEvent m_perf_event;
+  /// The thread being traced.
+  lldb::tid_t m_tid;
 };
 
 /// Manages a list of thread traces.

diff  --git a/lldb/source/Plugins/Process/Linux/Perf.cpp b/lldb/source/Plugins/Process/Linux/Perf.cpp
new file mode 100644
index 0000000000000..944076784b36d
--- /dev/null
+++ b/lldb/source/Plugins/Process/Linux/Perf.cpp
@@ -0,0 +1,181 @@
+//===-- Perf.cpp ----------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Perf.h"
+
+#include "lldb/lldb-types.h"
+
+#include "llvm/Support/Error.h"
+#include "llvm/Support/FormatVariadic.h"
+#include "llvm/Support/MathExtras.h"
+
+#include <chrono>
+#include <cstdint>
+#include <linux/perf_event.h>
+#include <sys/mman.h>
+#include <sys/syscall.h>
+#include <unistd.h>
+
+using namespace lldb_private;
+using namespace process_linux;
+using namespace llvm;
+
+Expected<PerfTscConversionParameters>
+lldb_private::process_linux::FetchPerfTscConversionParameters() {
+  lldb::pid_t pid = getpid();
+  perf_event_attr attr;
+  memset(&attr, 0, sizeof(attr));
+  attr.size = sizeof(attr);
+  attr.type = PERF_TYPE_SOFTWARE;
+  attr.config = PERF_COUNT_SW_DUMMY;
+
+  Expected<PerfEvent> perf_event = PerfEvent::Init(attr, pid);
+  if (!perf_event)
+    return perf_event.takeError();
+  if (Error mmap_err = perf_event->MmapMetadataAndBuffers(/*num_data_pages*/ 0,
+                                                          /*num_aux_pages*/ 0))
+    return std::move(mmap_err);
+
+  perf_event_mmap_page &mmap_metada = perf_event->GetMetadataPage();
+  if (mmap_metada.cap_user_time && mmap_metada.cap_user_time_zero) {
+    return PerfTscConversionParameters{
+        mmap_metada.time_mult, mmap_metada.time_shift, mmap_metada.time_zero};
+  } else {
+    auto err_cap =
+        !mmap_metada.cap_user_time ? "cap_user_time" : "cap_user_time_zero";
+    std::string err_msg =
+        llvm::formatv("Can't get TSC to real time conversion values. "
+                      "perf_event capability '{0}' not supported.",
+                      err_cap);
+    return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg);
+  }
+}
+
+std::chrono::nanoseconds PerfTscConversionParameters::ToWallTime(uint64_t tsc) {
+  // See 'time_zero' section of
+  // https://man7.org/linux/man-pages/man2/perf_event_open.2.html
+  uint64_t quot = tsc >> m_time_shift;
+  uint64_t rem_flag = (((uint64_t)1 << m_time_shift) - 1);
+  uint64_t rem = tsc & rem_flag;
+  return std::chrono::nanoseconds{m_time_zero + quot * m_time_mult +
+                                  ((rem * m_time_mult) >> m_time_shift)};
+}
+
+void resource_handle::MmapDeleter::operator()(void *ptr) {
+  if (m_bytes && ptr != nullptr)
+    munmap(ptr, m_bytes);
+}
+
+void resource_handle::FileDescriptorDeleter::operator()(long *ptr) {
+  if (ptr == nullptr)
+    return;
+  if (*ptr == -1)
+    return;
+  close(*ptr);
+  std::default_delete<long>()(ptr);
+}
+
+llvm::Expected<PerfEvent> PerfEvent::Init(perf_event_attr &attr,
+                                          lldb::pid_t pid, int cpu,
+                                          int group_fd, unsigned long flags) {
+  errno = 0;
+  long fd = syscall(SYS_perf_event_open, &attr, pid, cpu, group_fd, flags);
+  if (fd == -1) {
+    std::string err_msg =
+        llvm::formatv("perf event syscall failed: {0}", std::strerror(errno));
+    return llvm::createStringError(llvm::inconvertibleErrorCode(), err_msg);
+  }
+  return PerfEvent{fd};
+}
+
+llvm::Expected<PerfEvent> PerfEvent::Init(perf_event_attr &attr,
+                                          lldb::pid_t pid) {
+  return Init(attr, pid, -1, -1, 0);
+}
+
+llvm::Expected<resource_handle::MmapUP>
+PerfEvent::DoMmap(void *addr, size_t length, int prot, int flags,
+                  long int offset, llvm::StringRef buffer_name) {
+  errno = 0;
+  auto mmap_result = ::mmap(nullptr, length, prot, flags, GetFd(), offset);
+
+  if (mmap_result == MAP_FAILED) {
+    std::string err_msg =
+        llvm::formatv("perf event mmap allocation failed for {0}: {1}",
+                      buffer_name, std::strerror(errno));
+    return createStringError(inconvertibleErrorCode(), err_msg);
+  }
+  return resource_handle::MmapUP(mmap_result, length);
+}
+
+llvm::Error PerfEvent::MmapMetadataAndDataBuffer(size_t num_data_pages) {
+  size_t mmap_size = (num_data_pages + 1) * getpagesize();
+  if (Expected<resource_handle::MmapUP> mmap_metadata_data =
+          DoMmap(nullptr, mmap_size, PROT_WRITE, MAP_SHARED, 0,
+                 "metadata and data buffer")) {
+    m_metadata_data_base = std::move(mmap_metadata_data.get());
+    return Error::success();
+  } else
+    return mmap_metadata_data.takeError();
+}
+
+llvm::Error PerfEvent::MmapAuxBuffer(size_t num_aux_pages) {
+  if (num_aux_pages == 0)
+    return Error::success();
+
+  perf_event_mmap_page &metadata_page = GetMetadataPage();
+  metadata_page.aux_offset =
+      metadata_page.data_offset + metadata_page.data_size;
+  metadata_page.aux_size = num_aux_pages * getpagesize();
+
+  if (Expected<resource_handle::MmapUP> mmap_aux =
+          DoMmap(nullptr, metadata_page.aux_size, PROT_READ, MAP_SHARED,
+                 metadata_page.aux_offset, "aux buffer")) {
+    m_aux_base = std::move(mmap_aux.get());
+    return Error::success();
+  } else
+    return mmap_aux.takeError();
+}
+
+llvm::Error PerfEvent::MmapMetadataAndBuffers(size_t num_data_pages,
+                                              size_t num_aux_pages) {
+  if (num_data_pages != 0 && !isPowerOf2_64(num_data_pages))
+    return llvm::createStringError(
+        llvm::inconvertibleErrorCode(),
+        llvm::formatv("Number of data pages must be a power of 2, got: {0}",
+                      num_data_pages));
+  if (num_aux_pages != 0 && !isPowerOf2_64(num_aux_pages))
+    return llvm::createStringError(
+        llvm::inconvertibleErrorCode(),
+        llvm::formatv("Number of aux pages must be a power of 2, got: {0}",
+                      num_aux_pages));
+  if (Error err = MmapMetadataAndDataBuffer(num_data_pages))
+    return err;
+  if (Error err = MmapAuxBuffer(num_aux_pages))
+    return err;
+  return Error::success();
+}
+
+long PerfEvent::GetFd() const { return *(m_fd.get()); }
+
+perf_event_mmap_page &PerfEvent::GetMetadataPage() const {
+  return *reinterpret_cast<perf_event_mmap_page *>(m_metadata_data_base.get());
+}
+
+ArrayRef<uint8_t> PerfEvent::GetDataBuffer() const {
+  perf_event_mmap_page &mmap_metadata = GetMetadataPage();
+  return {reinterpret_cast<uint8_t *>(m_metadata_data_base.get()) +
+              mmap_metadata.data_offset,
+          mmap_metadata.data_size};
+}
+
+ArrayRef<uint8_t> PerfEvent::GetAuxBuffer() const {
+  perf_event_mmap_page &mmap_metadata = GetMetadataPage();
+  return {reinterpret_cast<uint8_t *>(m_aux_base.get()),
+          mmap_metadata.aux_size};
+}

diff  --git a/lldb/source/Plugins/Process/Linux/Perf.h b/lldb/source/Plugins/Process/Linux/Perf.h
new file mode 100644
index 0000000000000..6853b58666b1d
--- /dev/null
+++ b/lldb/source/Plugins/Process/Linux/Perf.h
@@ -0,0 +1,262 @@
+//===-- Perf.h --------------------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+/// \file
+/// This file contains a thin wrapper of the perf_event_open API
+/// and classes to handle the destruction of file descriptors
+/// and mmap pointers.
+///
+//===----------------------------------------------------------------------===//
+
+#ifndef LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H
+#define LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H
+
+#include "lldb/lldb-types.h"
+
+#include "llvm/Support/Error.h"
+
+#include <chrono>
+#include <cstdint>
+#include <linux/perf_event.h>
+
+namespace lldb_private {
+namespace process_linux {
+namespace resource_handle {
+
+/// Custom deleter for the pointer returned by \a mmap.
+///
+/// This functor type is provided to \a unique_ptr to properly
+/// unmap the region at destruction time.
+class MmapDeleter {
+public:
+  /// Construct new \a MmapDeleter.
+  ///
+  /// \param[in] bytes
+  ///   Size of the mmap'ed region in bytes.
+  MmapDeleter(size_t bytes = 0) : m_bytes(bytes) {}
+
+  /// Unmap the mmap'ed region.
+  ///
+  /// If \a m_bytes==0 or \a ptr==nullptr, nothing is unmmapped.
+  ///
+  /// \param[in] ptr
+  ///   pointer to the region to be unmmapped.
+  void operator()(void *ptr);
+
+private:
+  /// Size of the mmap'ed region, in bytes, to be unmapped.
+  size_t m_bytes;
+};
+
+/// Custom deleter for a file descriptor.
+///
+/// This functor type is provided to \a unique_ptr to properly release
+/// the resources associated with the file descriptor at destruction time.
+class FileDescriptorDeleter {
+public:
+  /// Close and free the memory associated with the file descriptor pointer.
+  ///
+  /// Effectively a no-op if \a ptr==nullptr or \a*ptr==-1.
+  ///
+  /// \param[in] ptr
+  ///   Pointer to the file descriptor.
+  void operator()(long *ptr);
+};
+
+using FileDescriptorUP =
+    std::unique_ptr<long, resource_handle::FileDescriptorDeleter>;
+using MmapUP = std::unique_ptr<void, resource_handle::MmapDeleter>;
+
+} // namespace resource_handle
+
+/// Thin wrapper of the perf_event_open API.
+///
+/// Exposes the metadata page and data and aux buffers of a perf event.
+/// Handles the management of the event's file descriptor and mmap'ed
+/// regions.
+class PerfEvent {
+public:
+  /// Create a new performance monitoring event via the perf_event_open syscall.
+  ///
+  /// The parameters are directly forwarded to a perf_event_open syscall,
+  /// for additional information on the parameters visit
+  /// https://man7.org/linux/man-pages/man2/perf_event_open.2.html.
+  ///
+  /// \param[in] attr
+  ///     Configuration information for the event.
+  ///
+  /// \param[in] pid
+  ///     The process to be monitored by the event.
+  ///
+  /// \param[in] cpu
+  ///     The cpu to be monitored by the event.
+  ///
+  /// \param[in] group_fd
+  ///     File descriptor of the group leader.
+  ///
+  /// \param[in] flags
+  ///     Bitmask of additional configuration flags.
+  ///
+  /// \return
+  ///     If the perf_event_open syscall was successful, a minimal \a PerfEvent
+  ///     instance, or an \a llvm::Error otherwise.
+  static llvm::Expected<PerfEvent> Init(perf_event_attr &attr, lldb::pid_t pid,
+                                        int cpu, int group_fd,
+                                        unsigned long flags);
+
+  /// Create a new performance monitoring event via the perf_event_open syscall
+  /// with "default" values for the cpu, group_fd and flags arguments.
+  ///
+  /// Convenience method to be used when the perf event requires minimal
+  /// configuration. It handles the default values of all other arguments.
+  ///
+  /// \param[in] attr
+  ///     Configuration information for the event.
+  ///
+  /// \param[in] pid
+  ///     The process to be monitored by the event.
+  static llvm::Expected<PerfEvent> Init(perf_event_attr &attr, lldb::pid_t pid);
+
+  /// Mmap the metadata page and the data and aux buffers of the perf event and
+  /// expose them through \a PerfEvent::GetMetadataPage() , \a
+  /// PerfEvent::GetDataBuffer() and \a PerfEvent::GetAuxBuffer().
+  ///
+  /// This uses mmap underneath, which means that the number of pages mmap'ed
+  /// must be less than the actual data available by the kernel. The metadata
+  /// page is always mmap'ed.
+  ///
+  /// Mmap is needed because the underlying data might be changed by the kernel
+  /// dynamically.
+  ///
+  /// \param[in] num_data_pages
+  ///     Number of pages in the data buffer to mmap, must be a power of 2.
+  ///     A value of 0 is useful for "dummy" events that only want to access
+  ///     the metadata, \a perf_event_mmap_page, or the aux buffer.
+  ///
+  /// \param[in] num_aux_pages
+  ///     Number of pages in the aux buffer to mmap, must be a power of 2.
+  ///     A value of 0 effectively is a no-op and no data is mmap'ed for this
+  ///     buffer.
+  ///
+  /// \return
+  ///   \a llvm::Error::success if the mmap operations succeeded,
+  ///   or an \a llvm::Error otherwise.
+  llvm::Error MmapMetadataAndBuffers(size_t num_data_pages,
+                                     size_t num_aux_pages);
+
+  /// Get the file descriptor associated with the perf event.
+  long GetFd() const;
+
+  /// Get the metadata page from the data section's mmap buffer.
+  ///
+  /// The metadata page is always mmap'ed, even when \a num_data_pages is 0.
+  ///
+  /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers,
+  /// otherwise a failure might happen.
+  ///
+  /// \return
+  ///   The data section's \a perf_event_mmap_page.
+  perf_event_mmap_page &GetMetadataPage() const;
+
+  /// Get the data buffer from the data section's mmap buffer.
+  ///
+  /// The data buffer is the region of the data section's mmap buffer where
+  /// perf sample data is located.
+  ///
+  /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers,
+  /// otherwise a failure might happen.
+  ///
+  /// \return
+  ///   \a ArrayRef<uint8_t> extending \a data_size bytes from \a data_offset.
+  llvm::ArrayRef<uint8_t> GetDataBuffer() const;
+
+  /// Get the AUX buffer.
+  ///
+  /// AUX buffer is a region for high-bandwidth data streams
+  /// such as IntelPT. This is separate from the metadata and data buffer.
+  ///
+  /// This should be called only after \a PerfEvent::MmapMetadataAndBuffers,
+  /// otherwise a failure might happen.
+  ///
+  /// \return
+  ///   \a ArrayRef<uint8_t> extending \a aux_size bytes from \a aux_offset.
+  llvm::ArrayRef<uint8_t> GetAuxBuffer() const;
+
+private:
+  /// Create new \a PerfEvent.
+  ///
+  /// \param[in] fd
+  ///   File descriptor of the perf event.
+  PerfEvent(long fd)
+      : m_fd(new long(fd), resource_handle::FileDescriptorDeleter()),
+        m_metadata_data_base(), m_aux_base() {}
+
+  /// Wrapper for \a mmap to provide custom error messages.
+  ///
+  /// The parameters are directly forwarded to a \a mmap syscall,
+  /// for information on the parameters visit
+  /// https://man7.org/linux/man-pages/man2/mmap.2.html.
+  ///
+  /// The value of \a GetFd() is passed as the \a fd argument to \a mmap.
+  llvm::Expected<resource_handle::MmapUP> DoMmap(void *addr, size_t length,
+                                                 int prot, int flags,
+                                                 long int offset,
+                                                 llvm::StringRef buffer_name);
+
+  /// Mmap the data buffer of the perf event.
+  ///
+  /// \param[in] num_data_pages
+  ///     Number of pages in the data buffer to mmap, must be a power of 2.
+  ///     A value of 0 is useful for "dummy" events that only want to access
+  ///     the metadata, \a perf_event_mmap_page, or the aux buffer.
+  llvm::Error MmapMetadataAndDataBuffer(size_t num_data_pages);
+
+  /// Mmap the aux buffer of the perf event.
+  ///
+  /// \param[in] num_aux_pages
+  ///   Number of pages in the aux buffer to mmap, must be a power of 2.
+  ///   A value of 0 effectively is a no-op and no data is mmap'ed for this
+  ///   buffer.
+  llvm::Error MmapAuxBuffer(size_t num_aux_pages);
+
+  /// The file descriptor representing the perf event.
+  resource_handle::FileDescriptorUP m_fd;
+  /// Metadata page and data section where perf samples are stored.
+  resource_handle::MmapUP m_metadata_data_base;
+  /// AUX buffer is a separate region for high-bandwidth data streams
+  /// such as IntelPT.
+  resource_handle::MmapUP m_aux_base;
+};
+
+/// TSC to nanoseconds conversion values defined by the Linux perf_event API
+/// when the capibilities cap_user_time and cap_user_time_zero are set. See the
+/// documentation of `time_zero` in
+/// https://man7.org/linux/man-pages/man2/perf_event_open.2.html for more
+/// information.
+struct PerfTscConversionParameters {
+  uint32_t m_time_mult;
+  uint16_t m_time_shift;
+  uint64_t m_time_zero;
+
+  /// Convert TSC value to nanosecond wall time.
+  ///
+  /// \a param[in] tsc
+  ///   The TSC value to be converted.
+  ///
+  /// \return
+  ///   Nanosecond wall time.
+  std::chrono::nanoseconds ToWallTime(uint64_t tsc);
+};
+
+/// Fetch \a PerfTscConversionParameters from \a perf_event_mmap_page, if
+/// available.
+llvm::Expected<PerfTscConversionParameters> FetchPerfTscConversionParameters();
+
+} // namespace process_linux
+} // namespace lldb_private
+
+#endif // LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H

diff  --git a/lldb/unittests/Process/Linux/CMakeLists.txt b/lldb/unittests/Process/Linux/CMakeLists.txt
index 81f1e4bc5e7e6..30fa7e4ea57d9 100644
--- a/lldb/unittests/Process/Linux/CMakeLists.txt
+++ b/lldb/unittests/Process/Linux/CMakeLists.txt
@@ -1,9 +1,10 @@
-add_lldb_unittest(TraceIntelPTTests
+add_lldb_unittest(ProcessLinuxTests
   IntelPTCollectorTests.cpp
+  PerfTests.cpp
 
   LINK_LIBS
     lldbPluginProcessLinux
   )
 
-target_include_directories(TraceIntelPTTests PRIVATE
+target_include_directories(ProcessLinuxTests PRIVATE
   ${LLDB_SOURCE_DIR}/source/Plugins/Process/Linux)

diff  --git a/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp b/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp
index 392961b222bf5..2754b958a9d49 100644
--- a/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp
+++ b/lldb/unittests/Process/Linux/IntelPTCollectorTests.cpp
@@ -20,8 +20,8 @@ size_t ReadCylicBufferWrapper(void *buf, size_t buf_size, void *cyc_buf,
                               size_t offset) {
   llvm::MutableArrayRef<uint8_t> dst(reinterpret_cast<uint8_t *>(buf),
                                      buf_size);
-  llvm::MutableArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(cyc_buf),
-                                     cyc_buf_size);
+  llvm::ArrayRef<uint8_t> src(reinterpret_cast<uint8_t *>(cyc_buf),
+                              cyc_buf_size);
   IntelPTThreadTrace::ReadCyclicBuffer(dst, src, cyc_start, offset);
   return dst.size();
 }

diff  --git a/lldb/unittests/Process/Linux/PerfTests.cpp b/lldb/unittests/Process/Linux/PerfTests.cpp
new file mode 100644
index 0000000000000..f8467d7042ef7
--- /dev/null
+++ b/lldb/unittests/Process/Linux/PerfTests.cpp
@@ -0,0 +1,85 @@
+//===-- PerfTests.cpp -----------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "Perf.h"
+
+#include "llvm/Support/Error.h"
+
+#include "gtest/gtest.h"
+#include <chrono>
+#include <cstdint>
+
+using namespace lldb_private;
+using namespace process_linux;
+using namespace llvm;
+
+/// Helper function to read current TSC value.
+///
+/// This code is based on llvm/xray.
+static Expected<uint64_t> readTsc() {
+
+  unsigned int eax, ebx, ecx, edx;
+
+  // We check whether rdtscp support is enabled. According to the x86_64 manual,
+  // level should be set at 0x80000001, and we should have a look at bit 27 in
+  // EDX. That's 0x8000000 (or 1u << 27).
+  __asm__ __volatile__("cpuid"
+                       : "=a"(eax), "=b"(ebx), "=c"(ecx), "=d"(edx)
+                       : "0"(0x80000001));
+  if (!(edx & (1u << 27))) {
+    return createStringError(inconvertibleErrorCode(),
+                             "Missing rdtscp support.");
+  }
+
+  unsigned cpu;
+  unsigned long rax, rdx;
+
+  __asm__ __volatile__("rdtscp\n" : "=a"(rax), "=d"(rdx), "=c"(cpu)::);
+
+  return (rdx << 32) + rax;
+}
+
+// Test TSC to walltime conversion based on perf conversion values.
+TEST(Perf, TscConversion) {
+  // This test works by first reading the TSC value directly before
+  // and after sleeping, then converting these values to nanoseconds, and
+  // finally ensuring the 
diff erence is approximately equal to the sleep time.
+  //
+  // There will be slight overhead associated with the sleep call, so it isn't
+  // reasonable to expect the 
diff erence to be exactly equal to the sleep time.
+
+  const int SLEEP_SECS = 1;
+  std::chrono::nanoseconds SLEEP_NANOS{std::chrono::seconds(SLEEP_SECS)};
+
+  Expected<PerfTscConversionParameters> params =
+      FetchPerfTscConversionParameters();
+
+  // Skip the test if the conversion parameters aren't available.
+  if (!params)
+    GTEST_SKIP() << params.takeError();
+
+  Expected<uint64_t> tsc_before_sleep = readTsc();
+  sleep(SLEEP_SECS);
+  Expected<uint64_t> tsc_after_sleep = readTsc();
+
+  // Skip the test if we are unable to read the TSC value.
+  if (!tsc_before_sleep)
+    GTEST_SKIP() << tsc_before_sleep.takeError();
+  if (!tsc_after_sleep)
+    GTEST_SKIP() << tsc_after_sleep.takeError();
+
+  std::chrono::nanoseconds converted_tsc_
diff  =
+      params->ToWallTime(*tsc_after_sleep) -
+      params->ToWallTime(*tsc_before_sleep);
+
+  std::chrono::microseconds acceptable_overhead(500);
+
+  ASSERT_GE(converted_tsc_
diff .count(), SLEEP_NANOS.count());
+  ASSERT_LT(converted_tsc_
diff .count(),
+            (SLEEP_NANOS + acceptable_overhead).count());
+}


        


More information about the lldb-commits mailing list