use std::borrow::Borrow;
use std::cell::UnsafeCell;
use std::ffi::CStr;
use std::fs::File;
use std::io::{self, Error, ErrorKind, Result};
use std::os::fd::AsRawFd;
use std::ptr;
use std::sync::Arc;
use super::sample::Sampler;
use crate::config::attr::from;
use crate::config::{Opts, Target};
use crate::event::Event;
use crate::ffi::{bindings as b, syscall, Attr};
pub mod group;
mod stat;
pub use stat::*;
pub struct Counter {
pub(crate) target: Target,
pub(crate) attr: UnsafeCell<Attr>,
pub(crate) perf: Arc<File>,
pub(crate) read_buf: UnsafeCell<Vec<u8>>,
}
impl Counter {
pub fn new(
event: impl TryInto<Event, Error = io::Error>,
target: impl Into<Target>,
opts: impl Borrow<Opts>,
) -> Result<Self> {
let target = target.into();
let attr = from(event.try_into()?.0, opts.borrow())?;
let flags = target.flags | b::PERF_FLAG_FD_CLOEXEC as u64;
let perf = syscall!(perf_event_open, &attr, target.pid, target.cpu, -1, flags)?;
let read_buf = vec![0; Stat::read_buf_size(1, attr.read_format)];
Ok(Self {
target,
attr: UnsafeCell::new(attr),
perf: Arc::new(perf),
read_buf: UnsafeCell::new(read_buf),
})
}
pub fn enable_all() -> Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return crate::ffi::linux_syscall::prctl(libc::PR_TASK_PERF_EVENTS_ENABLE);
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return Err(std::io::ErrorKind::Unsupported.into());
}
pub fn disable_all() -> Result<()> {
#[cfg(any(target_os = "linux", target_os = "android"))]
return crate::ffi::linux_syscall::prctl(libc::PR_TASK_PERF_EVENTS_DISABLE);
#[cfg(not(any(target_os = "linux", target_os = "android")))]
return Err(std::io::ErrorKind::Unsupported.into());
}
pub fn sampler(&self, exp: u8) -> Result<Sampler> {
if Arc::strong_count(&self.perf) == 1 {
let attr = unsafe { &*self.attr.get() };
Sampler::new(Arc::clone(&self.perf), attr, exp)
} else {
let error = "There is already a sampler attached to this counter.";
Err(Error::new(ErrorKind::AlreadyExists, error))
}
}
pub fn file(&self) -> &File {
&self.perf
}
pub fn id(&self) -> Result<u64> {
let mut id = 0;
let id_addr = ptr::from_mut(&mut id) as u64;
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_ID as u64,
id_addr
)?;
Ok(id)
}
pub fn enable(&self) -> Result<()> {
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_ENABLE as u64,
0
)?;
Ok(())
}
pub fn disable(&self) -> Result<()> {
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_DISABLE as u64,
0
)?;
Ok(())
}
pub fn clear_count(&self) -> Result<()> {
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_RESET as u64,
0
)?;
Ok(())
}
pub fn stat(&self) -> Result<Stat> {
let buf = unsafe { &mut *self.read_buf.get() };
syscall!(read, &self.perf, buf)?;
let ptr = buf.as_ptr();
let read_format = unsafe { &*self.attr.get() }.read_format;
let stat = unsafe { Stat::from_ptr(ptr, read_format) };
Ok(stat)
}
pub fn attach_bpf(&self, file: &File) -> Result<()> {
let fd = file.as_raw_fd();
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_SET_BPF as u64,
fd as u64,
)?;
Ok(())
}
pub fn query_bpf(&self, buf_len: u32) -> Result<(Vec<u32>, Option<u32>)> {
#[cfg(feature = "linux-4.16")]
return {
use std::mem::{transmute, MaybeUninit};
let mut buf = vec![MaybeUninit::uninit(); (2 + buf_len) as _];
buf[0] = MaybeUninit::new(buf_len);
let buf_addr = buf.as_mut_ptr() as u64;
match syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_QUERY_BPF as u64,
buf_addr,
) {
Ok::<i32, _>(_) => {
let prog_cnt = unsafe { buf[1].assume_init() };
let ids = buf[2..2 + (prog_cnt as usize)].to_vec();
let ids = unsafe { transmute::<Vec<_>, Vec<u32>>(ids) };
Ok((ids, None))
}
Err(e) => {
let option = e.raw_os_error();
let errno = unsafe { option.unwrap_unchecked() };
if errno == libc::ENOSPC {
let prog_cnt = unsafe { buf[1].assume_init() };
let ids = buf[2..].to_vec();
let ids = unsafe { transmute::<Vec<_>, Vec<u32>>(ids) };
return Ok((ids, Some(prog_cnt - buf_len)));
}
Err(e)
}
}
};
#[cfg(not(feature = "linux-4.16"))]
return {
let _ = buf_len;
Err(std::io::ErrorKind::Unsupported.into())
};
}
pub fn with_ftrace_filter(&self, filter: &CStr) -> Result<()> {
let filter_addr = filter.as_ptr() as u64;
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_SET_FILTER as u64,
filter_addr
)?;
Ok(())
}
pub fn switch_to<E>(&self, event: E) -> Result<()>
where
E: TryInto<Event, Error = io::Error>,
{
#[cfg(feature = "linux-4.17")]
{
let Event(event_cfg): Event = event.try_into()?;
let attr = unsafe { &mut *self.attr.get() };
attr.type_ = event_cfg.ty;
attr.config = event_cfg.config;
attr.__bindgen_anon_3.config1 = event_cfg.config1;
attr.__bindgen_anon_4.config2 = event_cfg.config2;
#[cfg(feature = "linux-6.3")]
(attr.config3 = event_cfg.config3);
attr.bp_type = event_cfg.bp_type;
let attr_addr = ptr::from_mut(attr) as u64;
syscall!(
unsafe,
ioctl_arg,
&self.perf,
b::PERF_IOC_OP_MODIFY_ATTRS as u64,
attr_addr
)?;
Ok(())
}
#[cfg(not(feature = "linux-4.17"))]
return {
let _ = event;
Err(std::io::ErrorKind::Unsupported.into())
};
}
}