#![deny(missing_docs)]
use events::Event;
use libc::pid_t;
use perf_event_open_sys::bindings::perf_event_attr;
use std::fs::File;
use std::io::{self, Read};
use std::os::raw::{c_int, c_uint, c_ulong};
use std::os::unix::io::{AsRawFd, FromRawFd};
pub mod events;
#[cfg(feature = "hooks")]
pub mod hooks;
#[cfg(not(feature = "hooks"))]
use perf_event_open_sys as sys;
#[cfg(feature = "hooks")]
use hooks::sys;
pub struct Counter {
file: File,
id: u64,
}
pub struct Builder<'a> {
attrs: perf_event_attr,
who: EventPid<'a>,
cpu: Option<usize>,
group: Option<&'a mut Group>,
}
#[derive(Debug)]
enum EventPid<'a> {
ThisProcess,
Other(pid_t),
CGroup(&'a File),
}
pub struct Group {
file: File,
id: u64,
max_members: usize,
}
pub struct Counts {
data: Vec<u64>,
}
#[repr(C)]
pub struct CountAndTime {
pub count: u64,
pub time_enabled: u64,
pub time_running: u64,
}
impl<'a> EventPid<'a> {
fn as_args(&self) -> (pid_t, u32) {
match self {
EventPid::ThisProcess => (0, 0),
EventPid::Other(pid) => (*pid, 0),
EventPid::CGroup(file) => (file.as_raw_fd(), sys::bindings::PERF_FLAG_PID_CGROUP),
}
}
}
impl<'a> Default for Builder<'a> {
fn default() -> Builder<'a> {
let mut attrs = perf_event_attr {
size: std::mem::size_of::<perf_event_attr>() as u32,
..perf_event_attr::default()
};
attrs.set_disabled(1);
attrs.set_exclude_kernel(1); attrs.set_exclude_hv(1);
attrs.read_format |= sys::bindings::PERF_FORMAT_TOTAL_TIME_ENABLED as u64
| sys::bindings::PERF_FORMAT_TOTAL_TIME_RUNNING as u64;
let kind = Event::Hardware(events::Hardware::INSTRUCTIONS);
attrs.type_ = kind.r#type();
attrs.config = kind.config();
Builder {
attrs,
who: EventPid::ThisProcess,
cpu: None,
group: None,
}
}
}
impl<'a> Builder<'a> {
pub fn new() -> Builder<'a> {
Builder::default()
}
pub fn observe_self(mut self) -> Builder<'a> {
self.who = EventPid::ThisProcess;
self
}
pub fn observe_pid(mut self, pid: pid_t) -> Builder<'a> {
self.who = EventPid::Other(pid);
self
}
pub fn observe_cgroup(mut self, cgroup: &'a File) -> Builder<'a> {
self.who = EventPid::CGroup(cgroup);
self
}
pub fn one_cpu(mut self, cpu: usize) -> Builder<'a> {
self.cpu = Some(cpu);
self
}
pub fn any_cpu(mut self) -> Builder<'a> {
self.cpu = None;
self
}
pub fn inherit(mut self, inherit: bool) -> Builder<'a> {
let flag = if inherit { 1 } else { 0 };
self.attrs.set_inherit(flag);
self
}
pub fn kind<K: Into<Event>>(mut self, kind: K) -> Builder<'a> {
let kind = kind.into();
self.attrs.type_ = kind.r#type();
self.attrs.config = kind.config();
self
}
pub fn group(mut self, group: &'a mut Group) -> Builder<'a> {
self.group = Some(group);
self.attrs.set_disabled(0);
self
}
pub fn build(mut self) -> std::io::Result<Counter> {
let cpu = match self.cpu {
Some(cpu) => cpu as c_int,
None => -1,
};
let (pid, flags) = self.who.as_args();
let group_fd = match self.group {
Some(ref mut g) => {
g.max_members += 1;
g.file.as_raw_fd() as c_int
}
None => -1,
};
let file = unsafe {
File::from_raw_fd(check_errno_syscall(|| {
sys::perf_event_open(&mut self.attrs, pid, cpu, group_fd, flags as c_ulong)
})?)
};
let mut id = 0_u64;
check_errno_syscall(|| unsafe { sys::ioctls::ID(file.as_raw_fd(), &mut id) })?;
Ok(Counter { file, id })
}
}
impl Counter {
pub fn id(&self) -> u64 {
self.id
}
pub fn enable(&mut self) -> io::Result<()> {
check_errno_syscall(|| unsafe { sys::ioctls::ENABLE(self.file.as_raw_fd(), 0) }).map(|_| ())
}
pub fn disable(&mut self) -> io::Result<()> {
check_errno_syscall(|| unsafe { sys::ioctls::DISABLE(self.file.as_raw_fd(), 0) })
.map(|_| ())
}
pub fn reset(&mut self) -> io::Result<()> {
check_errno_syscall(|| unsafe { sys::ioctls::RESET(self.file.as_raw_fd(), 0) }).map(|_| ())
}
pub fn read(&mut self) -> io::Result<u64> {
Ok(self.read_count_and_time()?.count)
}
pub fn read_count_and_time(&mut self) -> io::Result<CountAndTime> {
let mut buf = [0_u64; 3];
self.file.read_exact(u64::slice_as_bytes_mut(&mut buf))?;
let cat = CountAndTime {
count: buf[0],
time_enabled: buf[1],
time_running: buf[2],
};
assert!(cat.time_running <= cat.time_enabled);
Ok(cat)
}
}
impl std::fmt::Debug for Counter {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
fmt,
"Counter {{ fd: {}, id: {} }}",
self.file.as_raw_fd(),
self.id
)
}
}
impl Group {
#[allow(unused_parens)]
pub fn new() -> io::Result<Group> {
let mut attrs = perf_event_attr {
size: std::mem::size_of::<perf_event_attr>() as u32,
type_: sys::bindings::PERF_TYPE_SOFTWARE,
config: sys::bindings::PERF_COUNT_SW_DUMMY as u64,
..perf_event_attr::default()
};
attrs.set_disabled(1);
attrs.set_exclude_kernel(1);
attrs.set_exclude_hv(1);
attrs.read_format = (sys::bindings::PERF_FORMAT_TOTAL_TIME_ENABLED
| sys::bindings::PERF_FORMAT_TOTAL_TIME_RUNNING
| sys::bindings::PERF_FORMAT_ID
| sys::bindings::PERF_FORMAT_GROUP) as u64;
let file = unsafe {
File::from_raw_fd(check_errno_syscall(|| {
sys::perf_event_open(&mut attrs, 0, -1, -1, 0)
})?)
};
let mut id = 0_u64;
check_errno_syscall(|| unsafe { sys::ioctls::ID(file.as_raw_fd(), &mut id) })?;
Ok(Group {
file,
id,
max_members: 1,
})
}
pub fn enable(&mut self) -> io::Result<()> {
self.generic_ioctl(sys::ioctls::ENABLE)
}
pub fn disable(&mut self) -> io::Result<()> {
self.generic_ioctl(sys::ioctls::DISABLE)
}
pub fn reset(&mut self) -> io::Result<()> {
self.generic_ioctl(sys::ioctls::RESET)
}
fn generic_ioctl(&mut self, f: unsafe fn(c_int, c_uint) -> c_int) -> io::Result<()> {
check_errno_syscall(|| unsafe {
f(self.file.as_raw_fd(), sys::bindings::PERF_IOC_FLAG_GROUP)
})
.map(|_| ())
}
pub fn read(&mut self) -> io::Result<Counts> {
let mut data = vec![0_u64; 3 + 2 * self.max_members];
assert_eq!(
self.file.read(u64::slice_as_bytes_mut(&mut data))?,
std::mem::size_of_val(&data[..])
);
let counts = Counts { data };
assert_eq!(counts.nth_ref(0).0, self.id);
assert!(counts.time_running() <= counts.time_enabled());
self.max_members = counts.len();
Ok(counts)
}
}
impl std::fmt::Debug for Group {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(
fmt,
"Group {{ fd: {}, id: {} }}",
self.file.as_raw_fd(),
self.id
)
}
}
impl Counts {
#[allow(clippy::len_without_is_empty)] pub fn len(&self) -> usize {
self.data[0] as usize
}
pub fn time_enabled(&self) -> u64 {
self.data[1]
}
pub fn time_running(&self) -> u64 {
self.data[2]
}
fn nth_index(n: usize) -> std::ops::Range<usize> {
let base = 3 + 2 * n;
base..base + 2
}
fn nth_ref(&self, n: usize) -> (u64, &u64) {
let id_val = &self.data[Counts::nth_index(n)];
(id_val[1], &id_val[0])
}
}
pub struct CountsIter<'c> {
counts: &'c Counts,
next: usize,
}
impl<'c> Iterator for CountsIter<'c> {
type Item = (u64, &'c u64);
fn next(&mut self) -> Option<(u64, &'c u64)> {
if self.next >= self.counts.len() {
return None;
}
let result = self.counts.nth_ref(self.next);
self.next += 1;
Some(result)
}
}
impl<'c> IntoIterator for &'c Counts {
type Item = (u64, &'c u64);
type IntoIter = CountsIter<'c>;
fn into_iter(self) -> CountsIter<'c> {
CountsIter {
counts: self,
next: 1, }
}
}
impl Counts {
pub fn get(&self, member: &Counter) -> Option<&u64> {
self.into_iter()
.find(|&(id, _)| id == member.id)
.map(|(_, value)| value)
}
pub fn iter(&self) -> CountsIter {
<&Counts as IntoIterator>::into_iter(self)
}
}
impl std::ops::Index<&Counter> for Counts {
type Output = u64;
fn index(&self, index: &Counter) -> &u64 {
self.get(index).unwrap()
}
}
impl std::fmt::Debug for Counts {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
fmt.debug_map().entries(self.into_iter()).finish()
}
}
unsafe trait SliceAsBytesMut: Sized {
fn slice_as_bytes_mut(slice: &mut [Self]) -> &mut [u8] {
unsafe {
std::slice::from_raw_parts_mut(
slice.as_mut_ptr() as *mut u8,
std::mem::size_of_val(slice),
)
}
}
}
unsafe impl SliceAsBytesMut for 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)
}
}
#[test]
fn simple_build() {
Builder::new()
.build()
.expect("Couldn't build default Counter");
}
#[test]
#[cfg(target_os = "linux")]
fn test_error_code_is_correct() {
let builder = Builder::new()
.kind(events::Software::CPU_CLOCK)
.one_cpu(i32::MAX as usize);
match builder.build() {
Ok(_) => panic!("counter construction was not supposed to succeed"),
Err(e) => assert_eq!(e.raw_os_error(), Some(libc::EINVAL)),
}
}