Skip to content

Commit 2a12cb8

Browse files
committed
CPU support for illumos, macOS, and NetBSD.
1 parent 61757f0 commit 2a12cb8

File tree

4 files changed

+190
-37
lines changed

4 files changed

+190
-37
lines changed

Cargo.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,12 @@ nom = "7.0"
2424
version = "0.3"
2525
features = ["fileapi", "sysinfoapi", "minwindef", "winbase", "winerror", "ws2def", "ws2ipdef", "pdh"]
2626

27+
[target.'cfg(target_vendor = "apple")'.dependencies]
28+
mach2 = "0.6"
29+
30+
[target.'cfg(any(target_os = "illumos", target_os = "solaris"))'.dependencies]
31+
kstat-rs = "0.2"
32+
2733
[package.metadata.docs.rs]
2834
targets = [
2935
"x86_64-unknown-freebsd",

src/platform/illumos.rs

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,40 @@ use std::{io, path};
55

66
pub struct PlatformImpl;
77

8+
fn named_u64(named: &[kstat_rs::Named], key: &str) -> usize {
9+
for n in named {
10+
if n.name == key {
11+
if let kstat_rs::NamedData::UInt64(v) = n.value {
12+
return v as usize;
13+
}
14+
}
15+
}
16+
0
17+
}
18+
19+
fn measure_cpu() -> io::Result<Vec<CpuTime>> {
20+
let ctl = kstat_rs::Ctl::new()
21+
.map_err(|e| io::Error::other(e.to_string()))?;
22+
let mut result = Vec::new();
23+
for mut ks in ctl.filter(Some("cpu"), None, Some("sys")) {
24+
let instance = ks.ks_instance;
25+
let data = ctl.read(&mut ks)
26+
.map_err(|e| io::Error::other(e.to_string()))?;
27+
if let kstat_rs::Data::Named(named) = data {
28+
result.push((instance, CpuTime {
29+
user: named_u64(&named, "cpu_ticks_user"),
30+
nice: 0,
31+
system: named_u64(&named, "cpu_ticks_kernel"),
32+
interrupt: 0,
33+
idle: named_u64(&named, "cpu_ticks_idle"),
34+
other: named_u64(&named, "cpu_ticks_wait"),
35+
}));
36+
}
37+
}
38+
result.sort_by_key(|(instance, _)| *instance);
39+
Ok(result.into_iter().map(|(_, cpu)| cpu).collect())
40+
}
41+
842
/// An implementation of `Platform` for illumos.
943
/// See `Platform` for documentation.
1044
impl Platform for PlatformImpl {
@@ -14,58 +48,80 @@ impl Platform for PlatformImpl {
1448
}
1549

1650
fn cpu_load(&self) -> io::Result<DelayedMeasurement<Vec<CPULoad>>> {
17-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
51+
let loads = measure_cpu()?;
52+
Ok(DelayedMeasurement::new(
53+
Box::new(move || Ok(loads.iter()
54+
.zip(measure_cpu()?.iter())
55+
.map(|(prev, now)| (*now - prev).to_cpuload())
56+
.collect::<Vec<_>>()))))
1857
}
1958

2059
fn load_average(&self) -> io::Result<LoadAverage> {
2160
unix::load_average()
2261
}
2362

2463
fn memory(&self) -> io::Result<Memory> {
25-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
64+
Err(io::Error::other("Not supported"))
2665
}
2766

2867
fn swap(&self) -> io::Result<Swap> {
29-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
68+
Err(io::Error::other("Not supported"))
3069
}
3170

3271
fn boot_time(&self) -> io::Result<OffsetDateTime> {
33-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
72+
let ctl = kstat_rs::Ctl::new()
73+
.map_err(|e| io::Error::other(e.to_string()))?;
74+
let mut ks = ctl.filter(Some("unix"), Some(0), Some("system_misc"))
75+
.next()
76+
.ok_or_else(|| io::Error::other("kstat unix:0:system_misc not found"))?;
77+
let data = ctl.read(&mut ks)
78+
.map_err(|e| io::Error::other(e.to_string()))?;
79+
if let kstat_rs::Data::Named(named) = data {
80+
for n in &named {
81+
if n.name == "boot_time" {
82+
if let kstat_rs::NamedData::UInt32(v) = n.value {
83+
return OffsetDateTime::from_unix_timestamp(v as i64)
84+
.map_err(|e| io::Error::other(e.to_string()));
85+
}
86+
}
87+
}
88+
}
89+
Err(io::Error::other("boot_time not found in kstat"))
3490
}
3591

3692
fn battery_life(&self) -> io::Result<BatteryLife> {
37-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
93+
Err(io::Error::other("Not supported"))
3894
}
3995

4096
fn on_ac_power(&self) -> io::Result<bool> {
41-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
97+
Err(io::Error::other("Not supported"))
4298
}
4399

44100
fn mounts(&self) -> io::Result<Vec<Filesystem>> {
45-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
101+
Err(io::Error::other("Not supported"))
46102
}
47103

48104
fn mount_at<P: AsRef<path::Path>>(&self, _: P) -> io::Result<Filesystem> {
49-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
105+
Err(io::Error::other("Not supported"))
50106
}
51107

52108
fn block_device_statistics(&self) -> io::Result<BTreeMap<String, BlockDeviceStats>> {
53-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
109+
Err(io::Error::other("Not supported"))
54110
}
55111

56112
fn networks(&self) -> io::Result<BTreeMap<String, Network>> {
57113
unix::networks()
58114
}
59115

60-
fn network_stats(&self, interface: &str) -> io::Result<NetworkStats> {
61-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
116+
fn network_stats(&self, _interface: &str) -> io::Result<NetworkStats> {
117+
Err(io::Error::other("Not supported"))
62118
}
63119

64120
fn cpu_temp(&self) -> io::Result<f32> {
65-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
121+
Err(io::Error::other("Not supported"))
66122
}
67123

68124
fn socket_stats(&self) -> io::Result<SocketStats> {
69-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
125+
Err(io::Error::other("Not supported"))
70126
}
71127
}

src/platform/macos.rs

Lines changed: 73 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use std::{io, ptr, mem::{self, MaybeUninit}, ffi, slice};
22
use libc::{
3-
c_int, c_void, host_statistics64, mach_host_self, size_t, statfs, sysconf, sysctl,
4-
sysctlnametomib, timeval, vm_statistics64, xsw_usage, CTL_VM, HOST_VM_INFO64,
5-
HOST_VM_INFO64_COUNT, KERN_SUCCESS, VM_SWAPUSAGE, _SC_PHYS_PAGES,
3+
c_int, c_void, host_processor_info, host_statistics64, mach_msg_type_number_t,
4+
natural_t, processor_cpu_load_info, processor_info_array_t, size_t, statfs,
5+
sysconf, sysctl, sysctlnametomib, timeval, vm_address_t, vm_deallocate, vm_size_t,
6+
vm_statistics64, xsw_usage, CTL_VM, CPU_STATE_IDLE, CPU_STATE_NICE, CPU_STATE_SYSTEM,
7+
CPU_STATE_USER, HOST_VM_INFO64, HOST_VM_INFO64_COUNT, KERN_SUCCESS,
8+
PROCESSOR_CPU_LOAD_INFO, VM_SWAPUSAGE, _SC_PHYS_PAGES,
69
};
10+
use mach2::mach_init::mach_host_self;
11+
use mach2::traps::mach_task_self;
712
use crate::data::*;
813
use super::common::*;
914
use super::unix;
@@ -30,7 +35,7 @@ macro_rules! sysctl {
3035
let mut size = $size;
3136
if unsafe { sysctl(&mib[0] as *const _ as *mut _, mib.len() as u32,
3237
$dataptr as *mut _ as *mut c_void, &mut size, ptr::null_mut(), 0) } != 0 && $shouldcheck {
33-
return Err(io::Error::new(io::ErrorKind::Other, "sysctl() failed"))
38+
return Err(io::Error::other("sysctl() failed"))
3439
}
3540
size
3641
}
@@ -53,7 +58,12 @@ impl Platform for PlatformImpl {
5358
}
5459

5560
fn cpu_load(&self) -> io::Result<DelayedMeasurement<Vec<CPULoad>>> {
56-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
61+
let loads = measure_cpu()?;
62+
Ok(DelayedMeasurement::new(
63+
Box::new(move || Ok(loads.iter()
64+
.zip(measure_cpu()?.iter())
65+
.map(|(prev, now)| (*now - prev).to_cpuload())
66+
.collect::<Vec<_>>()))))
5767
}
5868

5969
fn load_average(&self) -> io::Result<LoadAverage> {
@@ -64,10 +74,7 @@ impl Platform for PlatformImpl {
6474
// Get Total Memory
6575
let total = match unsafe { sysconf(_SC_PHYS_PAGES) } {
6676
-1 => {
67-
return Err(io::Error::new(
68-
io::ErrorKind::Other,
69-
"sysconf(_SC_PHYS_PAGES) failed",
70-
))
77+
return Err(io::Error::other("sysconf(_SC_PHYS_PAGES) failed"))
7178
}
7279
n => n as u64,
7380
};
@@ -87,10 +94,7 @@ impl Platform for PlatformImpl {
8794
};
8895

8996
if ret != KERN_SUCCESS {
90-
return Err(io::Error::new(
91-
io::ErrorKind::Other,
92-
"host_statistics64() failed",
93-
));
97+
return Err(io::Error::other("host_statistics64() failed"));
9498
}
9599
let stat = unsafe { stat.assume_init() };
96100

@@ -107,7 +111,7 @@ impl Platform for PlatformImpl {
107111
external: ByteSize::kib((stat.external_page_count as u64) << *bsd::PAGESHIFT),
108112
internal: ByteSize::kib((stat.internal_page_count as u64) << *bsd::PAGESHIFT),
109113
uncompressed_in_compressor: ByteSize::kib(
110-
(stat.total_uncompressed_pages_in_compressor as u64) << *bsd::PAGESHIFT,
114+
stat.total_uncompressed_pages_in_compressor << *bsd::PAGESHIFT,
111115
),
112116
};
113117

@@ -143,49 +147,96 @@ impl Platform for PlatformImpl {
143147
fn boot_time(&self) -> io::Result<OffsetDateTime> {
144148
let mut data: timeval = unsafe { mem::zeroed() };
145149
sysctl!(KERN_BOOTTIME, &mut data, mem::size_of::<timeval>());
146-
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);
150+
let ts = OffsetDateTime::from_unix_timestamp(data.tv_sec).expect("unix timestamp should be within range") + Duration::from_nanos(data.tv_usec as u64);
147151
Ok(ts)
148152
}
149153

150154
fn battery_life(&self) -> io::Result<BatteryLife> {
151-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
155+
Err(io::Error::other("Not supported"))
152156
}
153157

154158
fn on_ac_power(&self) -> io::Result<bool> {
155-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
159+
Err(io::Error::other("Not supported"))
156160
}
157161

158162
fn mounts(&self) -> io::Result<Vec<Filesystem>> {
159163
let mut mptr: *mut statfs = ptr::null_mut();
160164
let len = unsafe { getmntinfo(&mut mptr, 2_i32) };
161165
if len < 1 {
162-
return Err(io::Error::new(io::ErrorKind::Other, "getmntinfo() failed"))
166+
return Err(io::Error::other("getmntinfo() failed"))
163167
}
164168
let mounts = unsafe { slice::from_raw_parts(mptr, len as usize) };
165169
Ok(mounts.iter().map(statfs_to_fs).collect::<Vec<_>>())
166170
}
167171

168172
fn block_device_statistics(&self) -> io::Result<BTreeMap<String, BlockDeviceStats>> {
169-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
173+
Err(io::Error::other("Not supported"))
170174
}
171175

172176
fn networks(&self) -> io::Result<BTreeMap<String, Network>> {
173177
unix::networks()
174178
}
175179

176180
fn network_stats(&self, _interface: &str) -> io::Result<NetworkStats> {
177-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
181+
Err(io::Error::other("Not supported"))
178182
}
179183

180184
fn cpu_temp(&self) -> io::Result<f32> {
181-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
185+
Err(io::Error::other("Not supported"))
182186
}
183187

184188
fn socket_stats(&self) -> io::Result<SocketStats> {
185-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
189+
Err(io::Error::other("Not supported"))
186190
}
187191
}
188192

193+
fn measure_cpu() -> io::Result<Vec<CpuTime>> {
194+
let mut num_cpus: natural_t = 0;
195+
let mut info: processor_info_array_t = ptr::null_mut();
196+
let mut info_count: mach_msg_type_number_t = 0;
197+
198+
let ret = unsafe {
199+
host_processor_info(
200+
mach_host_self(),
201+
PROCESSOR_CPU_LOAD_INFO,
202+
&mut num_cpus,
203+
&mut info,
204+
&mut info_count,
205+
)
206+
};
207+
208+
if ret != KERN_SUCCESS {
209+
return Err(io::Error::other("host_processor_info() failed"));
210+
}
211+
212+
let loads = unsafe {
213+
let cpus = slice::from_raw_parts(
214+
info as *const processor_cpu_load_info,
215+
num_cpus as usize,
216+
);
217+
cpus.iter()
218+
.map(|cpu| CpuTime {
219+
user: cpu.cpu_ticks[CPU_STATE_USER as usize] as usize,
220+
nice: cpu.cpu_ticks[CPU_STATE_NICE as usize] as usize,
221+
system: cpu.cpu_ticks[CPU_STATE_SYSTEM as usize] as usize,
222+
interrupt: 0,
223+
idle: cpu.cpu_ticks[CPU_STATE_IDLE as usize] as usize,
224+
other: 0,
225+
})
226+
.collect::<Vec<_>>()
227+
};
228+
229+
unsafe {
230+
vm_deallocate(
231+
mach_task_self(),
232+
info as vm_address_t,
233+
info_count as vm_size_t * mem::size_of::<natural_t>() as vm_size_t,
234+
);
235+
}
236+
237+
Ok(loads)
238+
}
239+
189240
fn statfs_to_fs(x: &statfs) -> Filesystem {
190241
Filesystem {
191242
files: (x.f_files as usize).saturating_sub(x.f_ffree as usize),

src/platform/netbsd.rs

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
use super::common::*;
33
use super::unix;
44
use crate::data::*;
5-
use libc::{c_int, c_void, sysctl, CTL_VM};
5+
use libc::{c_int, c_void, sysctl, CTL_HW, CTL_KERN, CTL_VM, HW_NCPU, KERN_CP_TIME};
66
use std::{io, mem, path, ptr};
77

88
pub struct PlatformImpl;
@@ -44,7 +44,12 @@ impl Platform for PlatformImpl {
4444
}
4545

4646
fn cpu_load(&self) -> io::Result<DelayedMeasurement<Vec<CPULoad>>> {
47-
Err(io::Error::new(io::ErrorKind::Other, "Not supported"))
47+
let loads = measure_cpu()?;
48+
Ok(DelayedMeasurement::new(
49+
Box::new(move || Ok(loads.iter()
50+
.zip(measure_cpu()?.iter())
51+
.map(|(prev, now)| (*now - prev).to_cpuload())
52+
.collect::<Vec<_>>()))))
4853
}
4954

5055
fn load_average(&self) -> io::Result<LoadAverage> {
@@ -147,6 +152,41 @@ impl PlatformMemory {
147152
}
148153
}
149154

155+
fn measure_cpu() -> io::Result<Vec<CpuTime>> {
156+
let mut ncpu: usize = 0;
157+
sysctl!([CTL_HW, HW_NCPU], &mut ncpu, mem::size_of::<usize>());
158+
let mut data: Vec<cp_time> = Vec::with_capacity(ncpu);
159+
unsafe { data.set_len(ncpu) };
160+
for i in 0..ncpu {
161+
let mib = [CTL_KERN, KERN_CP_TIME, i as c_int];
162+
sysctl!(mib, &mut data[i], mem::size_of::<cp_time>());
163+
}
164+
Ok(data.into_iter().map(|cpu| cpu.into()).collect())
165+
}
166+
167+
#[repr(C)]
168+
#[derive(Debug, Clone, Copy)]
169+
struct cp_time {
170+
user: u64,
171+
nice: u64,
172+
system: u64,
173+
interrupt: u64,
174+
idle: u64,
175+
}
176+
177+
impl From<cp_time> for CpuTime {
178+
fn from(cpu: cp_time) -> CpuTime {
179+
CpuTime {
180+
user: cpu.user as usize,
181+
nice: cpu.nice as usize,
182+
system: cpu.system as usize,
183+
interrupt: cpu.interrupt as usize,
184+
idle: cpu.idle as usize,
185+
other: 0,
186+
}
187+
}
188+
}
189+
150190
// https://github.com/NetBSD/src/blob/038135cba4b80f5c8d1e32fbc5b73c91c2f276d9/sys/uvm/uvm_extern.h#L420-L515
151191
#[repr(C)]
152192
#[derive(Debug, Default)]

0 commit comments

Comments
 (0)