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
This commit is contained in:
Jakob Johnson
2022-03-15 12:22:17 -07:00
parent 9b1fa6f89f
commit e6c84f82b8
8 changed files with 655 additions and 189 deletions

View File

@@ -8,6 +8,7 @@ add_lldb_library(lldbPluginProcessLinux
NativeRegisterContextLinux_s390x.cpp
NativeRegisterContextLinux_x86_64.cpp
NativeThreadLinux.cpp
Perf.cpp
SingleStepCheck.cpp
LINK_LIBS

View File

@@ -6,19 +6,23 @@
//
//===----------------------------------------------------------------------===//
#include <algorithm>
#include <fstream>
#include <sstream>
#include "IntelPTCollector.h"
#include "Perf.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 "IntelPTCollector.h"
#include "Plugins/Process/POSIX/ProcessPOSIXLog.h"
#include "lldb/Host/linux/Support.h"
#include "lldb/Utility/StreamString.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
}
llvm::MutableArrayRef<uint8_t> IntelPTThreadTrace::GetDataBuffer() const {
#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
}
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
}
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()));
}
return *cpu_info;
return attr;
}
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());
#ifndef PERF_ATTR_SIZE_VER5
llvm_unreachable("Intel PT Linux perf event not supported");
#else
Log *log = GetLog(POSIXLog::Ptrace);
if (llvm::Error err = thread_trace_up->StartTrace(pid, tid, buffer_size,
enable_tsc, psb_period))
return std::move(err);
LLDB_LOG(log, "called thread id {0}", tid);
return std::move(thread_trace_up);
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 page_size = getpagesize();
uint64_t buffer_numpages = static_cast<uint64_t>(
llvm::PowerOf2Floor((buffer_size + page_size - 1) / page_size));
Expected<perf_event_attr> attr =
IntelPTThreadTrace::CreateIntelPTPerfEventConfiguration(enable_tsc,
psb_period);
if (!attr)
return attr.takeError();
LLDB_LOG(log, "buffer size {0} ", buffer_size);
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 different 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",

View File

@@ -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 @@ public:
/// \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.

View File

@@ -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};
}

View File

@@ -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

View File

@@ -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)

View File

@@ -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();
}

View File

@@ -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 difference 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 difference 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());
}