use std::ffi::CString;
use std::io;
use std::sync::{Mutex, Once};
#[cfg(target_os = "freebsd")]
use libc::EDOOFUS;
#[cfg(target_os = "freebsd")]
use pmc_sys::{
pmc_allocate, pmc_attach, pmc_detach, pmc_id_t, pmc_init, pmc_mode_PMC_MODE_SC,
pmc_mode_PMC_MODE_SS, pmc_mode_PMC_MODE_TC, pmc_read, pmc_release, pmc_rw, pmc_start, pmc_stop,
};
#[cfg(not(target_os = "freebsd"))]
use super::stubs::*;
use crate::error::{new_error, new_os_error, Error, ErrorKind};
use crate::CPU_ANY;
static PMC_INIT: Once = Once::new();
lazy_static! {
static ref BIG_FAT_LOCK: Mutex<u32> = Mutex::new(42);
}
#[derive(Debug, Default, Clone)]
pub struct CounterBuilder {
cpu: Option<i32>,
pids: Option<Vec<i32>>,
}
impl CounterBuilder {
pub fn set_cpu(self, cpu: i32) -> Self {
Self {
cpu: Some(cpu),
..self
}
}
pub fn attach_to(self, pids: impl Into<Vec<i32>>) -> Self {
Self {
pids: Some(pids.into()),
..self
}
}
pub fn allocate(&self, event_spec: impl Into<String>) -> Result<Counter, Error> {
Counter::new(event_spec, self.cpu, self.pids.clone())
}
}
#[derive(Debug)]
struct AttachHandle {
id: pmc_id_t,
pid: i32,
}
impl Drop for AttachHandle {
fn drop(&mut self) {
if self.pid != 0 {
unsafe { pmc_detach(self.id, self.pid) };
}
}
}
pub struct Running<'a> {
counter: &'a mut Counter,
}
impl<'a> Running<'a> {
pub fn read(&self) -> Result<u64, Error> {
self.counter.read()
}
pub fn set(&mut self, value: u64) -> Result<u64, Error> {
self.counter.set(value)
}
pub fn stop(self) {
drop(self)
}
}
impl<'a> std::fmt::Display for Running<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.counter.fmt(f)
}
}
impl<'a> Drop for Running<'a> {
fn drop(&mut self) {
unsafe { pmc_stop(self.counter.id) };
}
}
#[derive(Debug)]
pub struct Counter {
id: pmc_id_t,
attached: Option<Vec<AttachHandle>>,
}
impl Counter {
fn new(
event_spec: impl Into<String>,
cpu: Option<i32>,
pids: Option<Vec<i32>>,
) -> Result<Self, Error> {
let pmc_mode = if pids.is_none() {
pmc_mode_PMC_MODE_SC
} else {
pmc_mode_PMC_MODE_TC
};
let cpu = cpu.unwrap_or_else(|| {
if pmc_mode == pmc_mode_PMC_MODE_SC || pmc_mode == pmc_mode_PMC_MODE_SS {
0
} else {
CPU_ANY
}
});
let _guard = BIG_FAT_LOCK.lock().unwrap();
init_pmc_once()?;
let c_spec =
CString::new(event_spec.into()).map_err(|_| new_error(ErrorKind::InvalidEventSpec))?;
let mut id = 0;
if unsafe { pmc_allocate(c_spec.as_ptr(), pmc_mode, 0, cpu, &mut id, 0) } != 0 {
return match io::Error::raw_os_error(&io::Error::last_os_error()) {
Some(libc::EINVAL) => Err(new_os_error(ErrorKind::AllocInit)),
_ => Err(new_os_error(ErrorKind::Unknown)),
};
}
let mut c = Counter { id, attached: None };
if let Some(pids) = pids {
let mut handles = vec![];
for pid in pids {
if unsafe { pmc_attach(id, pid) } != 0 {
return match io::Error::raw_os_error(&io::Error::last_os_error()) {
Some(libc::EBUSY) => unreachable!(),
Some(libc::EEXIST) => Err(new_os_error(ErrorKind::AlreadyAttached)),
Some(libc::EPERM) => Err(new_os_error(ErrorKind::Forbidden)),
Some(libc::EINVAL) | Some(libc::ESRCH) => {
Err(new_os_error(ErrorKind::BadTarget))
}
_ => Err(new_os_error(ErrorKind::Unknown)),
};
}
handles.push(AttachHandle { id, pid })
}
c.attached = Some(handles)
}
Ok(c)
}
#[must_use = "counter only runs until handle is dropped"]
pub fn start(&mut self) -> Result<Running<'_>, Error> {
if unsafe { pmc_start(self.id) } != 0 {
return match io::Error::raw_os_error(&io::Error::last_os_error()) {
Some(EDOOFUS) => Err(new_os_error(ErrorKind::LogFileRequired)),
Some(libc::ENXIO) => Err(new_os_error(ErrorKind::BadScope)),
_ => Err(new_os_error(ErrorKind::Unknown)),
};
}
Ok(Running { counter: self })
}
pub fn read(&self) -> Result<u64, Error> {
let mut value: u64 = 0;
if unsafe { pmc_read(self.id, &mut value) } != 0 {
return Err(new_os_error(ErrorKind::Unknown));
}
Ok(value)
}
pub fn set(&mut self, value: u64) -> Result<u64, Error> {
let mut old: u64 = 0;
if unsafe { pmc_rw(self.id, value, &mut old) } != 0 {
let err = io::Error::last_os_error();
return match io::Error::raw_os_error(&err) {
Some(libc::EBUSY) => panic!("{}", err.to_string()),
_ => Err(new_os_error(ErrorKind::Unknown)),
};
}
Ok(old)
}
}
impl std::fmt::Display for Counter {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.read() {
Ok(v) => write!(f, "{}", v),
Err(e) => write!(f, "error: {}", e),
}
}
}
impl Drop for Counter {
fn drop(&mut self) {
let _guard = BIG_FAT_LOCK.lock().unwrap();
self.attached = None;
unsafe {
pmc_release(self.id);
}
}
}
fn init_pmc_once() -> Result<(), Error> {
let mut maybe_err = Ok(());
PMC_INIT.call_once(|| {
if unsafe { pmc_init() } != 0 {
maybe_err = match io::Error::raw_os_error(&io::Error::last_os_error()) {
Some(libc::ENOENT) => Err(new_os_error(ErrorKind::Init)),
Some(libc::ENXIO) => Err(new_os_error(ErrorKind::Unsupported)),
Some(libc::EPROGMISMATCH) => Err(new_os_error(ErrorKind::VersionMismatch)),
_ => Err(new_os_error(ErrorKind::Unknown)),
};
return;
}
});
maybe_err
}