[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