1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use std::convert::TryInto as _;
use super::Metrics;
fn getrusage(who: libc::c_int) -> Option<libc::rusage> {
let mut usage = std::mem::MaybeUninit::zeroed();
// SAFETY: libc call; usage is valid pointer to rusage struct
if unsafe { libc::getrusage(who, usage.as_mut_ptr()) } == 0 {
// SAFETY: libc call was success, struct must be initialized
Some(unsafe { usage.assume_init() })
} else {
None
}
}
fn getrlimit(resource: libc::c_int) -> Option<libc::rlimit> {
let mut limit = std::mem::MaybeUninit::zeroed();
// SAFETY: libc call; limit is valid pointer to rlimit struct
if unsafe { libc::getrlimit(resource, limit.as_mut_ptr()) } == 0 {
// SAFETY: libc call was success, struct must be initialized
Some(unsafe { limit.assume_init() })
} else {
None
}
}
fn translate_rlim(rlim: libc::rlim_t) -> u64 {
if rlim == libc::RLIM_INFINITY {
0
} else {
rlim as u64
}
}
fn kinfo_getproc(pid: libc::pid_t) -> Option<libc::kinfo_proc> {
let mut kinfo_proc = std::mem::MaybeUninit::zeroed();
let kinfo_proc_size = std::mem::size_of_val(&kinfo_proc) as libc::size_t;
let mut data_size = kinfo_proc_size;
// code from deno doing similar stuff: https://github.com/denoland/deno/blob/20ae8db50d7d48ad020b83ebe78dc0e9e9eab3b2/runtime/ops/os/mod.rs#L415
let mib = [
libc::CTL_KERN,
libc::KERN_PROC,
libc::KERN_PROC_PID,
pid,
// this is required because MIB is array of ints, and is safe
// as long size of kinfo_proc structure doesn't exceed 2GB
kinfo_proc_size.try_into().unwrap(),
1,
];
// SAFETY: libc call; mib is statically initialized, kinfo_proc is valid pointer
// to kinfo_proc and data_size holds its size
if unsafe {
libc::sysctl(
mib.as_ptr(),
mib.len() as _,
kinfo_proc.as_mut_ptr() as *mut libc::c_void,
&mut data_size,
std::ptr::null_mut(),
0,
)
} == 0
&& data_size == kinfo_proc_size
{
// SAFETY: libc call was success and check for struct size passed, struct must be initialized
Some(unsafe { kinfo_proc.assume_init() })
} else {
None
}
}
pub fn collect() -> Metrics {
let mut metrics = Metrics::default();
// TODO: this is based on freebsd.rs, but lacks
// - virtual_memory_bytes (kinfo_proc::p_vm_map_size contains zero)
// - virtual_memory_max_bytes (openbsd lacks RLIMIT_AS)
// - threads (no corresponding field in kinfo_proc(
// - open_fds (no idea where to get it from)
if let Some(usage) = getrusage(libc::RUSAGE_SELF) {
metrics.cpu_seconds_total = Some(
(usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) as f64
+ (usage.ru_utime.tv_usec + usage.ru_stime.tv_usec) as f64 / 1000000.0,
);
}
if let Some(limit_as) = getrlimit(libc::RLIMIT_NOFILE) {
metrics.max_fds = Some(translate_rlim(limit_as.rlim_cur));
}
// SAFETY: libc call
let pid = unsafe { libc::getpid() };
if let Some(kinfo_proc) = kinfo_getproc(pid) {
// reference:
// https://github.com/openbsd/src/blob/782feb691bc15d1abd5f5c66fe3c0d336903a461/sys/sys/sysctl.h#L370
// SAFETY: libc call
let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64;
metrics.resident_memory_bytes = Some(kinfo_proc.p_vm_rssize as u64 * pagesize);
metrics.start_time_seconds = Some(kinfo_proc.p_ustart_sec);
}
metrics
}