use std::fs::File;
use std::io::Read;
use std::os::fd::{AsRawFd, FromRawFd};
#[repr(C)]
#[derive(Default)]
struct PerfEventAttr {
type_: u32,
size: u32,
config: u64,
sample_period: u64,
sample_type: u64,
read_format: u64,
flags: u64,
wakeup_events: u32,
bp_type: u32,
config1: u64,
}
const _: () = assert!(std::mem::size_of::<PerfEventAttr>() == 64);
const PERF_TYPE_HARDWARE: u32 = 0;
const PERF_COUNT_HW_CPU_CYCLES: u64 = 0;
const PERF_COUNT_HW_INSTRUCTIONS: u64 = 1;
const PERF_COUNT_HW_BRANCH_MISSES: u64 = 5;
const FLAG_DISABLED: u64 = 1 << 0;
const FLAG_EXCLUDE_KERNEL: u64 = 1 << 5;
const FLAG_EXCLUDE_HV: u64 = 1 << 6;
const IOC_ENABLE: libc::c_ulong = 0x2400;
const IOC_DISABLE: libc::c_ulong = 0x2401;
const IOC_RESET: libc::c_ulong = 0x2403;
pub struct Counter(File);
impl Counter {
pub fn instructions() -> Self {
Self::open(PERF_COUNT_HW_INSTRUCTIONS)
}
pub fn cycles() -> Self {
Self::open(PERF_COUNT_HW_CPU_CYCLES)
}
pub fn branch_misses() -> Self {
Self::open(PERF_COUNT_HW_BRANCH_MISSES)
}
fn open(config: u64) -> Self {
let attr = PerfEventAttr {
type_: PERF_TYPE_HARDWARE,
size: std::mem::size_of::<PerfEventAttr>() as u32,
config,
flags: FLAG_DISABLED | FLAG_EXCLUDE_KERNEL | FLAG_EXCLUDE_HV,
..Default::default()
};
let fd = unsafe { libc::syscall(libc::SYS_perf_event_open, &attr as *const _, 0, -1, -1, 0) };
assert!(fd >= 0, "perf_event_open: {}", std::io::Error::last_os_error());
Counter(unsafe { File::from_raw_fd(fd as libc::c_int) })
}
pub fn measure<T>(&self, f: impl FnOnce() -> T) -> (T, u64) {
let fd = self.0.as_raw_fd();
unsafe {
libc::ioctl(fd, IOC_RESET, 0);
libc::ioctl(fd, IOC_ENABLE, 0);
}
let out = f();
unsafe { libc::ioctl(fd, IOC_DISABLE, 0) };
let mut buf = [0u8; 8];
(&self.0).read_exact(&mut buf).expect("read the perf counter");
(out, u64::from_le_bytes(buf))
}
}
pub fn voluntary_ctx_switches() -> i64 {
unsafe {
let mut ru: libc::rusage = std::mem::zeroed();
libc::getrusage(libc::RUSAGE_THREAD, &mut ru);
ru.ru_nvcsw
}
}
fn status_bytes(field: &str) -> u64 {
std::fs::read_to_string("/proc/self/status")
.ok()
.and_then(|s| {
s.lines()
.find_map(|l| l.strip_prefix(field)?.strip_prefix(':'))
.and_then(|v| v.split_whitespace().next()?.parse::<u64>().ok())
})
.map_or(0, |kb| kb * 1024)
}
pub fn rss_bytes() -> u64 {
unsafe { libc::malloc_trim(0) };
status_bytes("VmRSS")
}
pub fn peak_rss_bytes() -> u64 {
status_bytes("VmHWM")
}
pub fn reset_peak_rss() {
let _ = std::fs::write("/proc/self/clear_refs", "5");
}
pub fn bench_passes() -> usize {
crate::env::env_num("GNITZ_BENCH_PASSES", 1)
}