:q! This diff is massive, but it's because it connects the client with lldb-server and also ensures that the postmortem case works. - Flatten the postmortem trace schema. The reason is that the schema has become quite complex due to the new multicore case, which defeats the original purpose of having a schema that could work for every trace plug-in. At this point, it's better that each trace plug-in defines it's own full schema. This means that the only common field is "type". -- Because of this new approach, I merged the "common" trace load and saving functionalities into the IntelPT one. This simplified the code quite a bit. If we eventually implement another trace plug-in, we can see then what we could reuse. -- The new schema, which is flattened, has now better comments and is parsed better. A change I did was to disallow hex addresses, because they are a bit error prone. I'm asking now to print the address in decimal. -- Renamed "intel" to "GenuineIntel" in the schema because that's what you see in /proc/cpuinfo. - Implemented reading the context switch trace data buffer. I had to do some refactors to do that cleanly. -- A major change that I did here was to simplify the perf_event circular buffer reading logic. It was too complex. Maybe the original Intel author had something different in mind. - Implemented all the necessary bits to read trace.json files with per-core data. - Implemented all the necessary bits to save to disk per-core trace session. - Added a test that ensures that parsing and saving to disk works. Differential Revision: https://reviews.llvm.org/D126015
333 lines
12 KiB
C++
333 lines
12 KiB
C++
//===-- 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/Utility/TraceIntelPTGDBRemotePackets.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 {
|
|
enum class CollectionState {
|
|
Enabled,
|
|
Disabled,
|
|
};
|
|
|
|
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 or thread to be monitored by the event. If \b None, then
|
|
/// all processes and threads are monitored.
|
|
///
|
|
/// \param[in] cpu
|
|
/// The cpu to be monitored by the event. If \b None, then all cpus are
|
|
/// monitored.
|
|
///
|
|
/// \param[in] group_fd
|
|
/// File descriptor of the group leader. If \b None, then this perf_event
|
|
/// doesn't belong to a preexisting group.
|
|
///
|
|
/// \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,
|
|
llvm::Optional<lldb::pid_t> pid,
|
|
llvm::Optional<lldb::core_id_t> cpu,
|
|
llvm::Optional<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 or thread to be monitored by the event. If \b None, then
|
|
/// all threads and processes are monitored.
|
|
static llvm::Expected<PerfEvent>
|
|
Init(perf_event_attr &attr, llvm::Optional<lldb::pid_t> pid,
|
|
llvm::Optional<lldb::core_id_t> core = llvm::None);
|
|
|
|
/// 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.
|
|
///
|
|
/// \param[in] data_buffer_write
|
|
/// Whether to mmap the data buffer with WRITE permissions. This changes
|
|
/// the behavior of how the kernel writes to the data 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,
|
|
bool data_buffer_write);
|
|
|
|
/// 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;
|
|
|
|
/// Read the aux buffer managed by this perf event. To ensure that the
|
|
/// data is up-to-date and is not corrupted by read-write race conditions, the
|
|
/// underlying perf_event is paused during read, and later it's returned to
|
|
/// its initial state. The returned data will be linear, i.e. it will fix the
|
|
/// circular wrapping the might exist int he buffer.
|
|
///
|
|
/// \param[in] offset
|
|
/// Offset of the data to read.
|
|
///
|
|
/// \param[in] size
|
|
/// Number of bytes to read.
|
|
///
|
|
/// \return
|
|
/// A vector with the requested binary data. The vector will have the
|
|
/// size of the requested \a size. Non-available positions will be
|
|
/// filled with zeroes.
|
|
llvm::Expected<std::vector<uint8_t>>
|
|
ReadFlushedOutAuxCyclicBuffer(size_t offset, size_t size);
|
|
|
|
/// Read the data buffer managed by this perf event. To ensure that the
|
|
/// data is up-to-date and is not corrupted by read-write race conditions, the
|
|
/// underlying perf_event is paused during read, and later it's returned to
|
|
/// its initial state. The returned data will be linear, i.e. it will fix the
|
|
/// circular wrapping the might exist int he buffer.
|
|
///
|
|
/// \param[in] offset
|
|
/// Offset of the data to read.
|
|
///
|
|
/// \param[in] size
|
|
/// Number of bytes to read.
|
|
///
|
|
/// \return
|
|
/// A vector with the requested binary data. The vector will have the
|
|
/// size of the requested \a size. Non-available positions will be
|
|
/// filled with zeroes.
|
|
llvm::Expected<std::vector<uint8_t>>
|
|
ReadFlushedOutDataCyclicBuffer(size_t offset, size_t size);
|
|
|
|
/// Use the ioctl API to disable the perf event and all the events in its
|
|
/// group. This doesn't terminate the perf event.
|
|
///
|
|
/// This is no-op if the perf event is already disabled.
|
|
///
|
|
/// \return
|
|
/// An Error if the perf event couldn't be disabled.
|
|
llvm::Error DisableWithIoctl();
|
|
|
|
/// Use the ioctl API to enable the perf event and all the events in its
|
|
/// group.
|
|
///
|
|
/// This is no-op if the perf event is already enabled.
|
|
///
|
|
/// \return
|
|
/// An Error if the perf event couldn't be enabled.
|
|
llvm::Error EnableWithIoctl();
|
|
|
|
/// \return
|
|
/// The size in bytes of the section of the data buffer that has effective
|
|
/// data.
|
|
size_t GetEffectiveDataBufferSize() const;
|
|
|
|
private:
|
|
/// Create new \a PerfEvent.
|
|
///
|
|
/// \param[in] fd
|
|
/// File descriptor of the perf event.
|
|
///
|
|
/// \param[in] initial_state
|
|
/// Initial collection state configured for this perf_event.
|
|
PerfEvent(long fd, CollectionState initial_state)
|
|
: m_fd(new long(fd), resource_handle::FileDescriptorDeleter()),
|
|
m_collection_state(initial_state) {}
|
|
|
|
/// 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.
|
|
///
|
|
/// \param[in] data_buffer_write
|
|
/// Whether to mmap the data buffer with WRITE permissions. This changes
|
|
/// the behavior of how the kernel writes to the data buffer.
|
|
llvm::Error MmapMetadataAndDataBuffer(size_t num_data_pages,
|
|
bool data_buffer_write);
|
|
|
|
/// 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;
|
|
/// The state of the underlying perf_event.
|
|
CollectionState m_collection_state;
|
|
};
|
|
|
|
/// Load \a PerfTscConversionParameters from \a perf_event_mmap_page, if
|
|
/// available.
|
|
llvm::Expected<LinuxPerfZeroTscConversion> LoadPerfTscConversionParameters();
|
|
|
|
} // namespace process_linux
|
|
} // namespace lldb_private
|
|
|
|
#endif // LLDB_SOURCE_PLUGINS_PROCESS_LINUX_PERF_H
|