use std::{io, path, ptr, time, fs, mem, ffi, slice};
use std::os::unix::io::AsRawFd;
use std::os::unix::ffi::OsStrExt;
use std::mem::size_of;
use libc::{c_void, c_int, c_uint, c_ulong, c_uchar, ioctl, sysctl, timeval, statfs, ifaddrs, getifaddrs, if_data, freeifaddrs};
use crate::data::*;
use super::common::*;
use super::unix;
use super::bsd;
pub struct PlatformImpl;
macro_rules! sysctl {
($mib:expr, $dataptr:expr, $size:expr, $shouldcheck:expr) => {
{
let mib = &$mib;
let mut size = $size;
if unsafe { sysctl(&mib[0], mib.len() as u32,
$dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck {
return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed"))
}
size
}
};
($mib:expr, $dataptr:expr, $size:expr) => {
sysctl!($mib, $dataptr, $size, true)
}
}
lazy_static! {
static ref APM_IOC_GETPOWER: c_ulong = 0x40000000u64 | ((size_of::<apm_power_info>() & 0x1fff) << 16) as u64 | (0x41 << 8) | 3;
static ref HW_NCPU: [c_int; 2] = [6, 3];
static ref KERN_CPTIME2: [c_int; 3] = [1, 71, 0];
static ref KERN_BOOTTIME: [c_int; 2] = [1, 21];
static ref VM_UVMEXP: [c_int; 2] = [2, 4];
static ref VFS_BCACHESTAT: [c_int; 3] = [10, 0, 3];
}
#[link(name = "c")]
extern "C" {
fn getmntinfo(mntbufp: *mut *mut statfs, flags: c_int) -> c_int;
}
impl Platform for PlatformImpl {
#[inline(always)]
fn new() -> Self {
PlatformImpl
}
fn cpu_load(&self) -> io::Result<DelayedMeasurement<Vec<CPULoad>>> {
let loads = measure_cpu()?;
Ok(DelayedMeasurement::new(
Box::new(move || Ok(loads.iter()
.zip(measure_cpu()?.iter())
.map(|(prev, now)| (*now - prev).to_cpuload())
.collect::<Vec<_>>()))))
}
fn load_average(&self) -> io::Result<LoadAverage> {
unix::load_average()
}
fn memory(&self) -> io::Result<Memory> {
PlatformMemory::new().map(|pm| pm.to_memory())
}
fn swap(&self) -> io::Result<Swap> {
PlatformMemory::new().map(|pm| pm.to_swap())
}
fn memory_and_swap(&self) -> io::Result<(Memory, Swap)> {
let pm = PlatformMemory::new()?;
Ok((pm.clone().to_memory(), pm.to_swap()))
}
fn boot_time(&self) -> io::Result<OffsetDateTime> {
let mut data: timeval = unsafe { mem::zeroed() };
sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::<timeval>());
let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec.into()).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64);
Ok(ts)
}
fn battery_life(&self) -> io::Result<BatteryLife> {
let f = fs::File::open("/dev/apm")?;
let mut info = apm_power_info::default();
if unsafe { ioctl(f.as_raw_fd(), *APM_IOC_GETPOWER, &mut info) } == -1 {
return Err(io::Error::new(io::ErrorKind::Other, "ioctl() failed"))
}
if info.battery_state == 0xff { return Err(io::Error::new(io::ErrorKind::Other, "Battery state unknown"))
}
if info.battery_state == 4 { return Err(io::Error::new(io::ErrorKind::Other, "Battery absent"))
}
Ok(BatteryLife {
remaining_capacity: info.battery_life as f32,
remaining_time: time::Duration::from_secs(info.minutes_left as u64),
})
}
fn on_ac_power(&self) -> io::Result<bool> {
let f = fs::File::open("/dev/apm")?;
let mut info = apm_power_info::default();
if unsafe { ioctl(f.as_raw_fd(), *APM_IOC_GETPOWER, &mut info) } == -1 {
return Err(io::Error::new(io::ErrorKind::Other, "ioctl() failed"))
}
Ok(info.ac_state == 0x01) }
fn mounts(&self) -> io::Result<Vec<Filesystem>> {
let mut mptr: *mut statfs = ptr::null_mut();
let len = unsafe { getmntinfo(&mut mptr, 1 as i32) };
if len < 1 {
return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed"))
}
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
Ok(mounts.iter().map(|m| statfs_to_fs(&m)).collect::<Vec<_>>())
}
fn mount_at<P: AsRef<path::Path>>(&self, path: P) -> io::Result<Filesystem> {
let path = ffi::CString::new(path.as_ref().as_os_str().as_bytes())?;
let mut sfs: statfs = unsafe { mem::zeroed() };
if unsafe { statfs(path.as_ptr() as *const _, &mut sfs) } != 0 {
return Err(io::Error::new(io::ErrorKind::Other, "statfs() failed"));
}
Ok(statfs_to_fs(&sfs))
}
fn block_device_statistics(&self) -> io::Result<BTreeMap<String, BlockDeviceStats>> {
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
}
fn networks(&self) -> io::Result<BTreeMap<String, Network>> {
unix::networks()
}
fn network_stats(&self, interface: &str) -> io::Result<NetworkStats> {
let mut rx_bytes: u64 = 0;
let mut tx_bytes: u64 = 0;
let mut rx_packets: u64 = 0;
let mut tx_packets: u64 = 0;
let mut rx_errors: u64 = 0;
let mut tx_errors: u64 = 0;
let mut ifap: *mut ifaddrs = std::ptr::null_mut();
let mut ifa: *mut ifaddrs;
let mut data: *mut if_data;
unsafe {
getifaddrs(&mut ifap);
ifa = ifap;
while !ifa.is_null() {
let c_str: &std::ffi::CStr = std::ffi::CStr::from_ptr((*ifa).ifa_name);
let str_net: &str = match c_str.to_str() {
Ok(v) => v,
Err(_) => return Err(io::Error::new(io::ErrorKind::Other, "C string cannot be converted"))
};
if interface == str_net {
data = (*ifa).ifa_data as *mut if_data;
if !data.is_null() {
rx_bytes += (*data).ifi_ibytes;
tx_bytes += (*data).ifi_obytes;
rx_packets += (*data).ifi_ipackets;
tx_packets += (*data).ifi_opackets;
rx_errors += (*data).ifi_ierrors;
tx_errors += (*data).ifi_oerrors;
}
}
ifa = (*ifa).ifa_next;
}
freeifaddrs(ifap);
}
Ok(NetworkStats {
rx_bytes: ByteSize::b(rx_bytes),
tx_bytes: ByteSize::b(tx_bytes),
rx_packets,
tx_packets,
rx_errors,
tx_errors,
})
}
fn cpu_temp(&self) -> io::Result<f32> {
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
}
fn socket_stats(&self) -> io::Result<SocketStats> {
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
}
}
fn measure_cpu() -> io::Result<Vec<CpuTime>> {
let mut cpus: usize = 0;
sysctl!(HW_NCPU, &mut cpus, mem::size_of::<usize>());
let mut data: Vec<sysctl_cpu> = Vec::with_capacity(cpus);
unsafe { data.set_len(cpus) };
for i in 0..cpus {
let mut mib = KERN_CPTIME2.clone();
mib[2] = i as i32;
sysctl!(mib, &mut data[i], mem::size_of::<sysctl_cpu>());
}
Ok(data.into_iter().map(|cpu| cpu.into()).collect())
}
impl PlatformMemory {
fn new() -> io::Result<Self> {
let mut uvm_info = uvmexp::default();
sysctl!(VM_UVMEXP, &mut uvm_info, mem::size_of::<uvmexp>());
let mut bcache_info = bcachestats::default();
sysctl!(
VFS_BCACHESTAT,
&mut bcache_info,
mem::size_of::<bcachestats>()
);
Ok(Self {
total: ByteSize::kib((uvm_info.npages << *bsd::PAGESHIFT) as u64),
active: ByteSize::kib((uvm_info.active << *bsd::PAGESHIFT) as u64),
inactive: ByteSize::kib((uvm_info.inactive << *bsd::PAGESHIFT) as u64),
wired: ByteSize::kib((uvm_info.wired << *bsd::PAGESHIFT) as u64),
cache: ByteSize::kib((bcache_info.numbufpages << *bsd::PAGESHIFT) as u64),
free: ByteSize::kib((uvm_info.free << *bsd::PAGESHIFT) as u64),
paging: ByteSize::kib((uvm_info.paging << *bsd::PAGESHIFT) as u64),
sw: ByteSize::kib((uvm_info.swpages << *bsd::PAGESHIFT) as u64),
swinuse: ByteSize::kib((uvm_info.swpginuse << *bsd::PAGESHIFT) as u64),
swonly: ByteSize::kib((uvm_info.swpgonly << *bsd::PAGESHIFT) as u64),
})
}
fn to_memory(self) -> Memory {
Memory {
total: self.total,
free: self.inactive + self.cache + self.free + self.paging,
platform_memory: self,
}
}
fn to_swap(self) -> Swap {
Swap {
total: self.sw,
free: saturating_sub_bytes(self.sw, self.swinuse),
platform_swap: self,
}
}
}
fn statfs_to_fs(fs: &statfs) -> Filesystem {
Filesystem {
files: (fs.f_files as usize).saturating_sub(fs.f_ffree as usize),
files_total: fs.f_files as usize,
files_avail: fs.f_ffree as usize,
free: ByteSize::b(fs.f_bfree * fs.f_bsize as u64),
avail: ByteSize::b(fs.f_bavail as u64 * fs.f_bsize as u64),
total: ByteSize::b(fs.f_blocks * fs.f_bsize as u64),
name_max: fs.f_namemax as usize,
fs_type: unsafe { ffi::CStr::from_ptr(&fs.f_fstypename[0]).to_string_lossy().into_owned() },
fs_mounted_from: unsafe { ffi::CStr::from_ptr(&fs.f_mntfromname[0]).to_string_lossy().into_owned() },
fs_mounted_on: unsafe { ffi::CStr::from_ptr(&fs.f_mntonname[0]).to_string_lossy().into_owned() },
}
}
#[repr(C)]
#[derive(Debug, Clone, Copy)]
pub struct sysctl_cpu {
user: usize,
nice: usize,
system: usize,
spin: usize,
interrupt: usize,
idle: usize,
}
impl From<sysctl_cpu> for CpuTime {
fn from(cpu: sysctl_cpu) -> CpuTime {
CpuTime {
user: cpu.user,
nice: cpu.nice,
system: cpu.system,
interrupt: cpu.interrupt,
idle: cpu.idle,
other: 0,
}
}
}
#[derive(Default, Debug)]
#[repr(C)]
struct apm_power_info {
battery_state: c_uchar,
ac_state: c_uchar,
battery_life: c_uchar,
spare1: c_uchar,
minutes_left: c_uint,
spare2: [c_uint; 6],
}
#[derive(Default, Debug)]
#[repr(C)]
struct bcachestats {
numbufs: i64,
numbufpages: i64,
numdirtypages: i64,
numcleanpages: i64,
pendingwrites: i64,
pendingreads: i64,
numwrites: i64,
numreads: i64,
cachehits: i64,
busymapped: i64,
dmapages: i64,
highpages: i64,
delwribufs: i64,
kvaslots: i64,
kvaslots_avail: i64,
highflips: i64,
highflops: i64,
dmaflips: i64,
}
#[derive(Default, Debug)]
#[repr(C)]
struct uvmexp {
pagesize: c_int,
pagemask: c_int,
pageshift: c_int,
npages: c_int,
free: c_int,
active: c_int,
inactive: c_int,
paging: c_int,
wired: c_int,
zeropages: c_int,
reserve_pagedaemon: c_int,
reserve_kernel: c_int,
anonpages: c_int,
vnodepages: c_int,
vtextpages: c_int,
freemin: c_int,
freetarg: c_int,
inactarg: c_int,
wiredmax: c_int,
anonmin: c_int,
vtextmin: c_int,
vnodemin: c_int,
anonminpct: c_int,
vtextminpct: c_int,
vnodeminpct: c_int,
nswapdev: c_int,
swpages: c_int,
swpginuse: c_int,
swpgonly: c_int,
nswget: c_int,
nanon: c_int,
nanonneeded: c_int,
nfreeanon: c_int,
faults: c_int,
traps: c_int,
intrs: c_int,
swtch: c_int,
softs: c_int,
syscalls: c_int,
pageins: c_int,
obsolete_swapins: c_int,
obsolete_swapouts: c_int,
pgswapin: c_int,
pgswapout: c_int,
forks: c_int,
forks_ppwait: c_int,
forks_sharevm: c_int,
pga_zerohit: c_int,
pga_zeromiss: c_int,
zeroaborts: c_int,
fltnoram: c_int,
fltnoanon: c_int,
fltnoamap: c_int,
fltpgwait: c_int,
fltpgrele: c_int,
fltrelck: c_int,
fltrelckok: c_int,
fltanget: c_int,
fltanretry: c_int,
fltamcopy: c_int,
fltnamap: c_int,
fltnomap: c_int,
fltlget: c_int,
fltget: c_int,
flt_anon: c_int,
flt_acow: c_int,
flt_obj: c_int,
flt_prcopy: c_int,
flt_przero: c_int,
pdwoke: c_int,
pdrevs: c_int,
pdswout: c_int,
pdfreed: c_int,
pdscans: c_int,
pdanscan: c_int,
pdobscan: c_int,
pdreact: c_int,
pdbusy: c_int,
pdpageouts: c_int,
pdpending: c_int,
pddeact: c_int,
pdreanon: c_int,
pdrevnode: c_int,
pdrevtext: c_int,
fpswtch: c_int,
kmapent: c_int,
}