use std::fmt;
use std::io;
use std::os::fd::RawFd;
const TASK_VM_INFO: libc::task_flavor_t = 22;
unsafe extern "C" {
static mach_task_self_: libc::mach_port_t;
}
#[derive(Debug)]
pub enum SysError {
BarrierFsync(io::Error),
FullFsync(io::Error),
TaskVmInfo(libc::kern_return_t),
PageSize(io::Error),
RangeOverflow,
Mincore(io::Error),
PerformanceCoreCount(io::Error),
InvalidPerformanceCoreCount,
ProductVersion(io::Error),
InvalidProductVersion,
RequestQos(io::Error),
ObserveQos(io::Error),
UnrequestableQos,
}
impl fmt::Display for SysError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::BarrierFsync(error) => write!(formatter, "F_BARRIERFSYNC failed: {error}"),
Self::FullFsync(error) => write!(formatter, "F_FULLFSYNC failed: {error}"),
Self::TaskVmInfo(code) => {
write!(
formatter,
"task_info(TASK_VM_INFO) failed with Mach code {code}"
)
}
Self::PageSize(error) => write!(formatter, "could not determine VM page size: {error}"),
Self::RangeOverflow => formatter.write_str("memory range overflows the address space"),
Self::Mincore(error) => write!(formatter, "mincore failed: {error}"),
Self::PerformanceCoreCount(error) => {
write!(
formatter,
"could not read physical performance-core count: {error}"
)
}
Self::InvalidPerformanceCoreCount => {
formatter.write_str("Darwin returned an invalid physical performance-core count")
}
Self::ProductVersion(error) => {
write!(formatter, "kern.osproductversion failed: {error}")
}
Self::InvalidProductVersion => {
formatter.write_str("Darwin returned an invalid product version")
}
Self::RequestQos(error) => write!(formatter, "could not request a QoS class: {error}"),
Self::ObserveQos(error) => {
write!(formatter, "could not read the thread QoS class: {error}")
}
Self::UnrequestableQos => {
formatter.write_str("Darwin provides no way to request this QoS class")
}
}
}
}
impl std::error::Error for SysError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::BarrierFsync(error)
| Self::FullFsync(error)
| Self::PageSize(error)
| Self::Mincore(error)
| Self::PerformanceCoreCount(error)
| Self::ProductVersion(error)
| Self::RequestQos(error)
| Self::ObserveQos(error) => Some(error),
Self::TaskVmInfo(_)
| Self::RangeOverflow
| Self::InvalidPerformanceCoreCount
| Self::InvalidProductVersion
| Self::UnrequestableQos => None,
}
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct TaskMemoryInfo {
pub resident_size: u64,
pub phys_footprint: u64,
}
#[repr(C)]
#[derive(Default)]
struct TaskVmInfoRev1 {
virtual_size: libc::mach_vm_size_t,
region_count: libc::integer_t,
page_size: libc::integer_t,
resident_size: libc::mach_vm_size_t,
resident_size_peak: libc::mach_vm_size_t,
device: libc::mach_vm_size_t,
device_peak: libc::mach_vm_size_t,
internal: libc::mach_vm_size_t,
internal_peak: libc::mach_vm_size_t,
external: libc::mach_vm_size_t,
external_peak: libc::mach_vm_size_t,
reusable: libc::mach_vm_size_t,
reusable_peak: libc::mach_vm_size_t,
purgeable_volatile_pmap: libc::mach_vm_size_t,
purgeable_volatile_resident: libc::mach_vm_size_t,
purgeable_volatile_virtual: libc::mach_vm_size_t,
compressed: libc::mach_vm_size_t,
compressed_peak: libc::mach_vm_size_t,
compressed_lifetime: libc::mach_vm_size_t,
phys_footprint: libc::mach_vm_size_t,
}
pub fn barrier_fsync(fd: RawFd) -> Result<(), SysError> {
let result = unsafe {
libc::fcntl(fd, libc::F_BARRIERFSYNC)
};
if result == -1 {
Err(SysError::BarrierFsync(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn full_fsync(fd: RawFd) -> Result<(), SysError> {
let result = unsafe {
libc::fcntl(fd, libc::F_FULLFSYNC)
};
if result == -1 {
Err(SysError::FullFsync(io::Error::last_os_error()))
} else {
Ok(())
}
}
pub fn task_memory_info() -> Result<TaskMemoryInfo, SysError> {
let mut info = TaskVmInfoRev1::default();
let count_value = std::mem::size_of::<TaskVmInfoRev1>()
.checked_div(std::mem::size_of::<libc::natural_t>())
.ok_or(SysError::RangeOverflow)?;
let mut count =
libc::mach_msg_type_number_t::try_from(count_value).map_err(|_| SysError::RangeOverflow)?;
let task = unsafe {
mach_task_self_
};
let result = unsafe {
libc::task_info(
task,
TASK_VM_INFO,
(&raw mut info).cast::<libc::integer_t>(),
&raw mut count,
)
};
if result != libc::KERN_SUCCESS {
return Err(SysError::TaskVmInfo(result));
}
Ok(TaskMemoryInfo {
resident_size: info.resident_size,
phys_footprint: info.phys_footprint,
})
}
pub fn phys_footprint() -> Result<u64, SysError> {
task_memory_info().map(|info| info.phys_footprint)
}
pub fn physical_performance_core_count() -> Result<usize, SysError> {
let mut count = 0_u32;
let mut length = std::mem::size_of::<u32>();
let result = unsafe {
libc::sysctlbyname(
c"hw.perflevel0.physicalcpu".as_ptr(),
(&raw mut count).cast::<libc::c_void>(),
&raw mut length,
std::ptr::null_mut(),
0,
)
};
if result == -1 {
return Err(SysError::PerformanceCoreCount(io::Error::last_os_error()));
}
if count == 0 || length != std::mem::size_of::<u32>() {
return Err(SysError::InvalidPerformanceCoreCount);
}
usize::try_from(count).map_err(|_| SysError::InvalidPerformanceCoreCount)
}
pub fn os_product_version() -> Result<(u32, u32), SysError> {
let mut length = 0;
let status = unsafe {
libc::sysctlbyname(
c"kern.osproductversion".as_ptr(),
std::ptr::null_mut(),
&raw mut length,
std::ptr::null_mut(),
0,
)
};
if status == -1 {
return Err(SysError::ProductVersion(io::Error::last_os_error()));
}
let mut buffer = [0_u8; 64];
if length == 0 || length > buffer.len() {
return Err(SysError::InvalidProductVersion);
}
let status = unsafe {
libc::sysctlbyname(
c"kern.osproductversion".as_ptr(),
buffer.as_mut_ptr().cast(),
&raw mut length,
std::ptr::null_mut(),
0,
)
};
if status == -1 {
return Err(SysError::ProductVersion(io::Error::last_os_error()));
}
let bytes = buffer
.get(..length)
.ok_or(SysError::InvalidProductVersion)?;
let text = std::ffi::CStr::from_bytes_with_nul(bytes)
.map_err(|_| SysError::InvalidProductVersion)?
.to_str()
.map_err(|_| SysError::InvalidProductVersion)?;
parse_product_version(text).ok_or(SysError::InvalidProductVersion)
}
pub(crate) fn parse_product_version(text: &str) -> Option<(u32, u32)> {
fn component(text: &str) -> Option<u32> {
if text.is_empty() || !text.bytes().all(|byte| byte.is_ascii_digit()) {
return None;
}
text.parse().ok()
}
let mut parts = text.split('.');
let major = component(parts.next()?)?;
let minor = match parts.next() {
Some(text) => component(text)?,
None => 0,
};
Some((major, minor))
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum QosClass {
UserInteractive,
UserInitiated,
Default,
Utility,
Background,
Unspecified,
}
impl QosClass {
const fn requestable(self) -> Option<libc::qos_class_t> {
match self {
Self::UserInteractive => Some(libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE),
Self::UserInitiated => Some(libc::qos_class_t::QOS_CLASS_USER_INITIATED),
Self::Default => Some(libc::qos_class_t::QOS_CLASS_DEFAULT),
Self::Utility => Some(libc::qos_class_t::QOS_CLASS_UTILITY),
Self::Background => Some(libc::qos_class_t::QOS_CLASS_BACKGROUND),
Self::Unspecified => None,
}
}
}
pub fn request_qos(class: QosClass) -> Result<(), SysError> {
let Some(requested) = class.requestable() else {
return Err(SysError::UnrequestableQos);
};
let status = unsafe { libc::pthread_set_qos_class_self_np(requested, 0) };
if status != 0 {
return Err(SysError::RequestQos(io::Error::from_raw_os_error(status)));
}
Ok(())
}
pub fn observed_qos() -> Result<(QosClass, i32), SysError> {
let mut class = libc::qos_class_t::QOS_CLASS_UNSPECIFIED;
let mut priority = 0_i32;
let status = unsafe {
libc::pthread_get_qos_class_np(libc::pthread_self(), &raw mut class, &raw mut priority)
};
if status != 0 {
return Err(SysError::ObserveQos(io::Error::from_raw_os_error(status)));
}
let class = match class {
libc::qos_class_t::QOS_CLASS_USER_INTERACTIVE => QosClass::UserInteractive,
libc::qos_class_t::QOS_CLASS_USER_INITIATED => QosClass::UserInitiated,
libc::qos_class_t::QOS_CLASS_DEFAULT => QosClass::Default,
libc::qos_class_t::QOS_CLASS_UTILITY => QosClass::Utility,
libc::qos_class_t::QOS_CLASS_BACKGROUND => QosClass::Background,
libc::qos_class_t::QOS_CLASS_UNSPECIFIED => QosClass::Unspecified,
};
Ok((class, priority))
}
pub fn mincore_resident(range: &[u8]) -> Result<usize, SysError> {
if range.is_empty() {
return Ok(0);
}
let page_size_raw = unsafe {
libc::sysconf(libc::_SC_PAGESIZE)
};
if page_size_raw <= 0 {
return Err(SysError::PageSize(io::Error::last_os_error()));
}
let page_size = usize::try_from(page_size_raw).map_err(|_| SysError::RangeOverflow)?;
let start = range.as_ptr() as usize;
let end = start
.checked_add(range.len())
.ok_or(SysError::RangeOverflow)?;
let aligned_start = start / page_size * page_size;
let aligned_end = end
.checked_add(page_size - 1)
.ok_or(SysError::RangeOverflow)?
/ page_size
* page_size;
let aligned_len = aligned_end
.checked_sub(aligned_start)
.ok_or(SysError::RangeOverflow)?;
let page_count = aligned_len / page_size;
let mut residency = vec![0 as libc::c_char; page_count];
let result = unsafe {
libc::mincore(
aligned_start as *const libc::c_void,
aligned_len,
residency.as_mut_ptr(),
)
};
if result == -1 {
return Err(SysError::Mincore(io::Error::last_os_error()));
}
Ok(residency
.into_iter()
.filter(|status| *status & 1 == 1)
.count())
}
#[cfg(test)]
#[allow(
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic,
clippy::unwrap_used
)]
mod tests {
#[test]
fn product_version_parses_major_and_minor() {
for (text, expected) in [("14", (14, 0)), ("13.6", (13, 6)), ("15.1.2", (15, 1))] {
assert_eq!(super::parse_product_version(text), Some(expected));
}
}
#[test]
fn product_version_rejects_empty_and_non_numeric() {
for text in [
"",
"x",
"14.x",
".1",
"14.",
"-1.0",
"+14.0",
"4294967296.0",
] {
assert_eq!(super::parse_product_version(text), None, "{text}");
}
}
#[test]
fn host_product_version_is_at_least_eleven() {
assert!(super::os_product_version().expect("host product version") >= (11, 0));
}
use super::{
QosClass, SysError, barrier_fsync, full_fsync, mincore_resident, observed_qos,
phys_footprint, physical_performance_core_count, request_qos,
};
use std::os::fd::AsRawFd;
use std::slice;
#[test]
fn a_requested_qos_class_is_what_the_thread_then_reports() {
for class in [
QosClass::UserInteractive,
QosClass::UserInitiated,
QosClass::Default,
QosClass::Utility,
QosClass::Background,
] {
let probe = std::thread::spawn(move || {
request_qos(class)?;
observed_qos()
});
let (observed, priority) = probe
.join()
.expect("the probe thread must not panic")
.expect("Darwin accepts every class this enum can request");
assert_eq!(
observed, class,
"requested {class:?} but the thread reports {observed:?}"
);
assert_eq!(priority, 0, "a request at relative priority 0 must stay 0");
}
}
#[test]
fn an_unspecified_qos_class_is_refused_rather_than_silently_ignored() {
let error = request_qos(QosClass::Unspecified).expect_err("must be refused");
assert!(matches!(error, SysError::UnrequestableQos));
}
#[test]
fn barrier_fsync_succeeds_on_regular_file() -> Result<(), Box<dyn std::error::Error>> {
let file = tempfile::tempfile()?;
barrier_fsync(file.as_raw_fd())?;
Ok(())
}
#[test]
fn full_fsync_succeeds_on_regular_file() -> Result<(), Box<dyn std::error::Error>> {
let file = tempfile::tempfile()?;
full_fsync(file.as_raw_fd())?;
Ok(())
}
#[test]
fn barrier_fsync_returns_typed_error_on_closed_fd() -> Result<(), Box<dyn std::error::Error>> {
let file = tempfile::tempfile()?;
let fd = file.as_raw_fd();
drop(file);
let error = barrier_fsync(fd).expect_err("a closed descriptor must be rejected");
assert!(matches!(error, SysError::BarrierFsync(_)));
Ok(())
}
#[test]
fn phys_footprint_is_nonzero_for_current_task() -> Result<(), Box<dyn std::error::Error>> {
assert!(phys_footprint()? > 0);
Ok(())
}
#[test]
fn physical_p_core_count_is_within_logical_cpu_count() -> Result<(), Box<dyn std::error::Error>>
{
let physical = physical_performance_core_count()?;
let logical = std::thread::available_parallelism()?.get();
assert!(
physical >= 1,
"assertion failed: physical P-core count was {physical}"
);
assert!(
physical <= logical,
"physical P-core count {physical} exceeded logical CPU count {logical}"
);
Ok(())
}
#[test]
fn mincore_resident_reports_touched_pages() -> Result<(), Box<dyn std::error::Error>> {
let page_size_raw = unsafe {
libc::sysconf(libc::_SC_PAGESIZE)
};
let page_size = usize::try_from(page_size_raw)?;
let page_count = 256_usize;
let touched_pages = page_count / 2;
let map_len = page_size * page_count;
let touched_len = page_size * touched_pages;
let file = tempfile::tempfile()?;
file.set_len(u64::try_from(map_len)?)?;
let mapping = unsafe {
libc::mmap(
std::ptr::null_mut(),
map_len,
libc::PROT_READ,
libc::MAP_SHARED,
file.as_raw_fd(),
0,
)
};
if mapping == libc::MAP_FAILED {
return Err(std::io::Error::last_os_error().into());
}
let bytes = unsafe {
slice::from_raw_parts(mapping.cast::<u8>(), map_len)
};
for offset in (0..touched_len).step_by(page_size) {
unsafe {
std::ptr::read_volatile(bytes.as_ptr().add(offset));
}
}
let resident = mincore_resident(&bytes[..touched_len])?;
assert!(resident >= touched_pages * 40 / 100);
assert!(resident <= touched_pages);
let unmap_result = unsafe {
libc::munmap(mapping, map_len)
};
if unmap_result != 0 {
return Err(std::io::Error::last_os_error().into());
}
Ok(())
}
#[test]
fn mincore_resident_empty_range_is_zero() -> Result<(), Box<dyn std::error::Error>> {
assert_eq!(mincore_resident(&[])?, 0);
Ok(())
}
#[test]
fn sys_error_preserves_typed_operation() {
let error = SysError::BarrierFsync(std::io::Error::from_raw_os_error(libc::EBADF));
assert!(error.to_string().contains("F_BARRIERFSYNC"));
assert!(std::error::Error::source(&error).is_some());
}
}