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:
@@ -8,6 +8,7 @@ add_lldb_library(lldbPluginProcessLinux
|
||||
NativeRegisterContextLinux_s390x.cpp
|
||||
NativeRegisterContextLinux_x86_64.cpp
|
||||
NativeThreadLinux.cpp
|
||||
Perf.cpp
|
||||
SingleStepCheck.cpp
|
||||
|
||||
LINK_LIBS
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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.
|
||||
|
||||
181
lldb/source/Plugins/Process/Linux/Perf.cpp
Normal file
181
lldb/source/Plugins/Process/Linux/Perf.cpp
Normal 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};
|
||||
}
|
||||
262
lldb/source/Plugins/Process/Linux/Perf.h
Normal file
262
lldb/source/Plugins/Process/Linux/Perf.h
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
85
lldb/unittests/Process/Linux/PerfTests.cpp
Normal file
85
lldb/unittests/Process/Linux/PerfTests.cpp
Normal 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());
|
||||
}
|
||||
Reference in New Issue
Block a user