#![cfg_attr(debug_assertions, warn(missing_docs))]
#![cfg_attr(not(debug_assertions), deny(missing_docs))]
#![allow(clippy::assign_op_pattern)]
macro_rules! used_in_docs {
($t:ident) => {
const _: () = {
mod use_item {
#[allow(unused_imports)]
use super::$t;
}
};
};
}
use std::convert::TryInto;
use std::fs::File;
use std::os::fd::{AsRawFd, IntoRawFd, RawFd};
use std::time::Duration;
use std::{fmt, io};
use crate::data::endian::Native;
use crate::data::parse::ParseConfig;
use crate::sys::bindings::PERF_IOC_FLAG_GROUP;
use crate::sys::ioctls;
pub mod events;
mod builder;
mod flags;
mod group;
mod group_data;
mod sampler;
#[doc = include_str!("../README.md")]
mod readme {}
#[cfg(feature = "hooks")]
pub mod hooks;
#[cfg(feature = "hooks")]
use hooks::sys;
#[doc(inline)]
pub use perf_event_data as data;
#[cfg(not(feature = "hooks"))]
use perf_event_open_sys as sys;
pub use crate::builder::{Builder, UnsupportedOptionsError};
#[doc(inline)]
pub use crate::data::{ReadFormat, SampleFlags as SampleFlag};
pub use crate::flags::{Clock, SampleBranchFlag, SampleSkid};
pub use crate::group::Group;
pub use crate::group_data::{GroupData, GroupEntry, GroupIter};
pub use crate::sampler::{Record, Sampler, UserReadData};
pub struct Counter {
file: File,
id: u64,
config: ParseConfig<Native>,
member_count: u32,
}
impl Counter {
pub(crate) fn new_internal(file: File, config: ParseConfig<Native>) -> std::io::Result<Self> {
let mut counter = Self {
file,
id: 0,
config,
member_count: 1,
};
let mut id = 0;
counter.ioctl(|fd| unsafe { ioctls::ID(fd, &mut id) })?;
counter.id = id;
Ok(counter)
}
pub fn id(&self) -> u64 {
self.id
}
pub fn config(&self) -> &ParseConfig<Native> {
&self.config
}
pub fn enable(&mut self) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::ENABLE(fd, 0) })
}
pub fn enable_group(&mut self) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::ENABLE(fd, PERF_IOC_FLAG_GROUP) })
}
pub fn disable(&mut self) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::DISABLE(fd, 0) })
}
pub fn disable_group(&mut self) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::DISABLE(fd, PERF_IOC_FLAG_GROUP) })
}
pub fn reset(&mut self) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::RESET(fd, 0) })
}
pub fn reset_group(&mut self) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::RESET(fd, PERF_IOC_FLAG_GROUP) })
}
pub fn set_bpf(&mut self, bpf: RawFd) -> io::Result<()> {
self.ioctl(|fd| unsafe { ioctls::SET_BPF(fd, bpf as _) })
.map(drop)
}
pub fn sampled(self, map_len: usize) -> io::Result<Sampler> {
let pagesize =
check_errno_syscall(|| unsafe { libc::sysconf(libc::_SC_PAGESIZE) })? as usize;
let len = pagesize
+ map_len
.checked_next_power_of_two()
.unwrap_or((usize::MAX >> 1) + 1)
.max(pagesize);
let mmap = memmap2::MmapOptions::new().len(len).map_raw(&self.file)?;
Ok(Sampler::new(self, mmap))
}
pub(crate) fn ioctl<F>(&self, ioctl: F) -> io::Result<()>
where
F: FnOnce(RawFd) -> libc::c_int,
{
check_errno_syscall(|| ioctl(self.as_raw_fd())).map(drop)
}
}
impl Counter {
pub fn read(&mut self) -> io::Result<u64> {
Ok(self.read_full()?.count())
}
pub fn read_full(&mut self) -> io::Result<CounterData> {
if !self.is_group() {
return self.do_read_single();
}
let group = self.do_read_group()?;
let entry = group.get(self).unwrap();
let data = crate::data::ReadValue::from_group_and_entry(&group.data, &entry.0);
Ok(CounterData(data))
}
pub fn read_group(&mut self) -> io::Result<GroupData> {
if self.is_group() {
self.do_read_group()
} else {
Ok(GroupData::new(self.do_read_single()?.0.into()))
}
}
pub fn read_count_and_time(&mut self) -> io::Result<CountAndTime> {
let data = self.read_full()?;
Ok(CountAndTime {
count: data.count(),
time_enabled: data
.time_enabled()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"time_enabled was not enabled within read_format",
)
})?
.as_nanos() as _,
time_running: data
.time_running()
.ok_or_else(|| {
io::Error::new(
io::ErrorKind::Other,
"time_running was not enabled within read_format",
)
})?
.as_nanos() as _,
})
}
fn is_group(&self) -> bool {
self.config.read_format().contains(ReadFormat::GROUP)
}
fn do_read_single(&mut self) -> io::Result<CounterData> {
use std::io::Read;
use std::mem::size_of;
use crate::flags::ReadFormatExt;
debug_assert!(!self.is_group());
let mut data = [0u8; ReadFormat::MAX_NON_GROUP_SIZE * size_of::<u64>()];
let len = self.file.read(&mut data)?;
if len == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"the kernel was unable to schedule the counter or group",
));
}
let mut parser = crate::data::parse::Parser::new(&data[..len], self.config.clone());
let value: crate::data::ReadValue = parser
.parse()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
Ok(CounterData(value))
}
fn do_read_group(&mut self) -> io::Result<GroupData> {
use std::io::Read;
use std::mem::size_of;
use crate::data::ReadGroup;
use crate::flags::ReadFormatExt;
let read_format = self.config.read_format();
let prefix_len = read_format.prefix_len();
let element_len = read_format.element_len();
let mut elements = (self.member_count as usize).max(1);
let mut data = vec![0u8; (prefix_len + elements * element_len) * size_of::<u64>()];
let len = loop {
match self.file.read(&mut data) {
Ok(len) => break len,
Err(e) if e.raw_os_error() == Some(libc::ENOSPC) => {
elements *= 2;
data.resize((prefix_len + elements * element_len) * size_of::<u64>(), 0);
}
Err(e) => return Err(e),
}
};
if len == 0 {
return Err(io::Error::new(
io::ErrorKind::UnexpectedEof,
"the kernel was unable to schedule the counter or group",
));
}
data.truncate(len);
let mut parser = crate::data::parse::Parser::new(data.as_slice(), self.config.clone());
let data: ReadGroup = parser
.parse::<ReadGroup>()
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?
.into_owned();
let data = GroupData::new(data);
self.member_count = data
.len()
.try_into()
.expect("group had more than u32::MAX elements");
Ok(data)
}
}
impl AsRawFd for Counter {
fn as_raw_fd(&self) -> RawFd {
self.file.as_raw_fd()
}
}
impl IntoRawFd for Counter {
fn into_raw_fd(self) -> RawFd {
self.file.into_raw_fd()
}
}
impl AsRef<Counter> for &'_ Counter {
fn as_ref(&self) -> &Counter {
self
}
}
impl AsMut<Counter> for &'_ mut Counter {
fn as_mut(&mut self) -> &mut Counter {
self
}
}
impl fmt::Debug for Counter {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Counter")
.field("fd", &self.as_raw_fd())
.field("id", &self.id())
.finish_non_exhaustive()
}
}
#[derive(Clone, Debug)]
pub struct CounterData(crate::data::ReadValue);
impl CounterData {
pub fn count(&self) -> u64 {
self.0.value()
}
pub fn time_enabled(&self) -> Option<Duration> {
self.0.time_enabled().map(Duration::from_nanos)
}
pub fn time_running(&self) -> Option<Duration> {
self.0.time_running().map(Duration::from_nanos)
}
pub fn lost(&self) -> Option<u64> {
self.0.lost()
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug)]
pub struct CountAndTime {
pub count: u64,
pub time_enabled: u64,
pub time_running: u64,
}
fn check_errno_syscall<F, R>(f: F) -> io::Result<R>
where
F: FnOnce() -> R,
R: PartialOrd + Default,
{
let result = f();
if result < R::default() {
Err(io::Error::last_os_error())
} else {
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn simple_build() {
Builder::new(crate::events::Software::DUMMY)
.build()
.expect("Couldn't build default Counter");
}
#[test]
#[cfg(target_os = "linux")]
fn test_error_code_is_correct() {
let builder = Builder::new(events::Software::CPU_CLOCK)
.one_cpu(i32::MAX as usize)
.clone();
match builder.build() {
Ok(_) => panic!("counter construction was not supposed to succeed"),
Err(e) => assert_eq!(e.raw_os_error(), Some(libc::EINVAL)),
}
}
#[test]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
fn test_sampler_rdpmc() {
let mut sampler = Builder::new(events::Hardware::INSTRUCTIONS)
.enabled(true)
.build()
.expect("failed to build counter")
.sampled(1024)
.expect("failed to build sampler");
let read = sampler.read_user();
sampler.disable().unwrap();
let value = sampler.read_full().unwrap();
assert!(read.time_running() <= value.time_running().unwrap());
assert!(read.time_enabled() <= value.time_enabled().unwrap());
if let Some(count) = read.count() {
assert!(count <= value.count(), "{count} <= {}", value.count());
}
}
}