use core::ffi::{c_char, c_void};
use std::sync::OnceLock;
mod libsystem_ffi {
use core::ffi::{c_char, c_void};
#[allow(unsafe_code)]
unsafe extern "C" {
#[allow(dead_code)]
pub(super) safe fn getpid() -> i32;
pub(super) unsafe fn ledger(
cmd: i32,
arg1: *mut c_void,
arg2: *mut c_void,
arg3: *mut c_void,
) -> i32;
pub(super) unsafe fn sysctlbyname(
name: *const c_char,
oldp: *mut c_void,
oldlenp: *mut usize,
newp: *mut c_void,
newlen: usize,
) -> i32;
pub(super) unsafe fn proc_listpids(
type_: u32,
typeinfo: u32,
buffer: *mut c_void,
buffersize: i32,
) -> i32;
pub(super) unsafe fn proc_pidpath(pid: i32, buffer: *mut c_void, buffersize: u32) -> i32;
}
}
const LEDGER_INFO: i32 = 0;
const LEDGER_TEMPLATE_INFO: i32 = 2;
const LEDGER_ENTRY_INFO_V2: i32 = 4;
const PROC_ALL_PIDS: u32 = 1;
const PROC_PIDPATHINFO_MAXSIZE: usize = 4096;
#[repr(C)]
#[allow(clippy::struct_field_names)] struct LedgerTemplateInfo {
lti_name: [c_char; 32],
lti_group: [c_char; 32],
lti_units: [c_char; 32],
}
#[repr(C)]
#[allow(clippy::struct_field_names)] struct LedgerEntryInfo {
lei_balance: i64,
lei_credit: i64,
lei_debit: i64,
lei_limit: u64,
lei_refill_period: u64,
lei_last_refill: u64,
lei_lifetime_max: i64,
lei_reserved: [u64; 4],
}
pub(super) struct MetalQueryResult {
pub current_usage: u64,
pub dedicated_video_memory: u64,
pub recommended_max_working_set: u64,
pub adapter_name: String,
}
static GRAPHICS_FOOTPRINT_INDEX: OnceLock<i32> = OnceLock::new();
const LEDGER_TEMPLATE_BUF_CAP: usize = 128;
static METAL_DEVICE: OnceLock<
Option<objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLDevice>>>,
> = OnceLock::new();
const _: fn() = || {
const fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<
objc2::rc::Retained<objc2::runtime::ProtocolObject<dyn objc2_metal::MTLDevice>>,
>();
};
fn recommended_max_working_set_size() -> Option<u64> {
use objc2_metal::MTLDevice;
METAL_DEVICE
.get_or_init(|| objc2_metal::MTLCreateSystemDefaultDevice())
.as_ref()
.map(|d| d.recommendedMaxWorkingSetSize())
}
#[allow(unsafe_code)]
fn read_sysctl_u64(name: &[u8]) -> Option<u64> {
let mut value: u64 = 0;
let mut len: usize = size_of::<u64>();
#[allow(clippy::as_conversions, clippy::ptr_as_ptr)]
let out_ptr = (&raw mut value).cast::<c_void>();
let rc = unsafe {
libsystem_ffi::sysctlbyname(
name.as_ptr().cast::<c_char>(),
out_ptr,
&raw mut len,
core::ptr::null_mut(),
0,
)
};
if rc == 0 && len == size_of::<u64>() {
Some(value)
} else {
None
}
}
#[allow(unsafe_code)]
fn read_sysctl_string(name: &[u8]) -> Option<String> {
let mut len: usize = 0;
let rc = unsafe {
libsystem_ffi::sysctlbyname(
name.as_ptr().cast::<c_char>(),
core::ptr::null_mut(),
&raw mut len,
core::ptr::null_mut(),
0,
)
};
if rc != 0 || len == 0 {
return None;
}
let mut buf: Vec<u8> = vec![0_u8; len];
let rc2 = unsafe {
libsystem_ffi::sysctlbyname(
name.as_ptr().cast::<c_char>(),
buf.as_mut_ptr().cast::<c_void>(),
&raw mut len,
core::ptr::null_mut(),
0,
)
};
if rc2 != 0 {
return None;
}
while buf.last() == Some(&0) {
buf.pop();
}
String::from_utf8(buf).ok()
}
#[allow(unsafe_code)]
fn resolve_graphics_footprint_index() -> Option<i32> {
let mut buf: Vec<LedgerTemplateInfo> = (0..LEDGER_TEMPLATE_BUF_CAP)
.map(|_| unsafe { core::mem::zeroed::<LedgerTemplateInfo>() })
.collect();
#[allow(
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap
)]
let mut count: i32 = LEDGER_TEMPLATE_BUF_CAP as i32;
let rc = unsafe {
libsystem_ffi::ledger(
LEDGER_TEMPLATE_INFO,
buf.as_mut_ptr().cast::<c_void>(),
(&raw mut count).cast::<c_void>(),
core::ptr::null_mut(),
)
};
if rc != 0 || count <= 0 {
return None;
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let returned = (count as usize).min(LEDGER_TEMPLATE_BUF_CAP);
let target = b"graphics_footprint";
for (i, row) in buf.iter().take(returned).enumerate() {
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let name_bytes: Vec<u8> = row
.lti_name
.iter()
.take_while(|&&c| c != 0)
.map(|&c| c as u8)
.collect();
if name_bytes == target {
#[allow(
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap
)]
return Some(i as i32);
}
}
None
}
#[allow(unsafe_code)]
fn read_graphics_footprint(pid: i32) -> Option<u64> {
let idx =
*GRAPHICS_FOOTPRINT_INDEX.get_or_init(|| resolve_graphics_footprint_index().unwrap_or(-1));
if idx < 0 {
return None;
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let idx_usize = idx as usize;
let mut buf: Vec<LedgerEntryInfo> = (0..LEDGER_TEMPLATE_BUF_CAP)
.map(|_| unsafe { core::mem::zeroed::<LedgerEntryInfo>() })
.collect();
#[allow(
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap
)]
let mut count: i32 = LEDGER_TEMPLATE_BUF_CAP as i32;
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let pid_as_ptr = pid as usize as *mut c_void;
let rc = unsafe {
libsystem_ffi::ledger(
LEDGER_ENTRY_INFO_V2,
pid_as_ptr,
buf.as_mut_ptr().cast::<c_void>(),
(&raw mut count).cast::<c_void>(),
)
};
if rc != 0 || count <= 0 {
return None;
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let returned = (count as usize).min(LEDGER_TEMPLATE_BUF_CAP);
if idx_usize >= returned {
return None;
}
let balance = buf.get(idx_usize)?.lei_balance;
if balance < 0 {
None
} else {
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
Some(balance as u64)
}
}
pub(super) fn query(idx: u32) -> Option<MetalQueryResult> {
if idx != 0 {
return None;
}
let self_pid = process_self_pid();
let current_usage = read_graphics_footprint(self_pid).unwrap_or(0);
let dedicated_video_memory = read_sysctl_u64(b"hw.memsize\0")?;
let adapter_name =
read_sysctl_string(b"machdep.cpu.brand_string\0").unwrap_or_else(|| "Apple GPU".into());
let recommended_max_working_set =
recommended_max_working_set_size().unwrap_or(dedicated_video_memory);
Some(MetalQueryResult {
current_usage,
dedicated_video_memory,
recommended_max_working_set,
adapter_name,
})
}
pub(super) fn device_count() -> Option<u32> {
let brand = read_sysctl_string(b"machdep.cpu.brand_string\0")?;
if brand.contains("Apple") {
Some(1)
} else {
None
}
}
fn process_self_pid() -> i32 {
#[allow(clippy::as_conversions, clippy::cast_possible_wrap)]
{
std::process::id() as i32
}
}
pub(super) fn process_gpu_info(device_index: u32) -> Option<crate::ProcessGpuInfo> {
if device_index != 0 {
return None;
}
let self_pid = process_self_pid();
let used_bytes = read_graphics_footprint(self_pid)?;
Some(crate::ProcessGpuInfo {
used_bytes,
is_per_process: true,
source: crate::GpuQuerySource::Metal,
})
}
#[allow(unsafe_code)]
pub(super) fn list_compute_processes(device_index: u32) -> Option<Vec<crate::GpuProcessEntry>> {
if device_index != 0 {
return None;
}
let size_bytes =
unsafe { libsystem_ffi::proc_listpids(PROC_ALL_PIDS, 0, core::ptr::null_mut(), 0) };
if size_bytes <= 0 {
return None;
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let size_usize = size_bytes as usize;
let pid_count = size_usize / size_of::<i32>();
if pid_count == 0 {
return None;
}
let mut pids: Vec<i32> = vec![0_i32; pid_count];
#[allow(
clippy::as_conversions,
clippy::cast_possible_truncation,
clippy::cast_possible_wrap
)]
let cap_bytes = (pid_count * size_of::<i32>()) as i32;
let written_bytes = unsafe {
libsystem_ffi::proc_listpids(
PROC_ALL_PIDS,
0,
pids.as_mut_ptr().cast::<c_void>(),
cap_bytes,
)
};
if written_bytes <= 0 {
return None;
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let written_usize = (written_bytes as usize).min(size_usize);
let written_pids = written_usize / size_of::<i32>();
let mut out: Vec<crate::GpuProcessEntry> = Vec::new();
for &pid in pids.iter().take(written_pids) {
if pid <= 0 {
continue;
}
let Some(used) = read_graphics_footprint(pid) else {
continue;
};
if used == 0 {
continue;
}
let name = read_proc_pidpath_basename(pid);
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let pid_u32 = pid as u32;
out.push(crate::GpuProcessEntry {
pid: pid_u32,
name,
used_bytes: used,
source: crate::GpuQuerySource::Metal,
});
}
Some(out)
}
#[allow(unsafe_code)]
fn read_proc_pidpath_basename(pid: i32) -> Option<String> {
let mut buf: [u8; PROC_PIDPATHINFO_MAXSIZE] = [0; PROC_PIDPATHINFO_MAXSIZE];
#[allow(clippy::as_conversions, clippy::cast_possible_truncation)]
let cap_u32 = PROC_PIDPATHINFO_MAXSIZE as u32;
let len =
unsafe { libsystem_ffi::proc_pidpath(pid, buf.as_mut_ptr().cast::<c_void>(), cap_u32) };
if len <= 0 {
return None;
}
#[allow(clippy::as_conversions, clippy::cast_sign_loss)]
let len_usize = (len as usize).min(PROC_PIDPATHINFO_MAXSIZE);
let path_bytes = buf.get(..len_usize)?;
let path_str = core::str::from_utf8(path_bytes).ok()?.to_owned();
let basename = path_str.rsplit('/').next().unwrap_or(&path_str);
if basename.is_empty() {
None
} else {
Some(basename.to_owned())
}
}