|
| 1 | +use super::Metrics; |
| 2 | + |
| 3 | +fn getrusage(who: libc::c_int) -> Option<libc::rusage> { |
| 4 | + let mut usage = std::mem::MaybeUninit::zeroed(); |
| 5 | + // SAFETY: libc call; usage is valid pointer to rusage struct |
| 6 | + if unsafe { libc::getrusage(who, usage.as_mut_ptr()) } == 0 { |
| 7 | + // SAFETY: libc call was success, struct must be initialized |
| 8 | + Some(unsafe { usage.assume_init() }) |
| 9 | + } else { |
| 10 | + None |
| 11 | + } |
| 12 | +} |
| 13 | + |
| 14 | +fn getrlimit(resource: libc::c_int) -> Option<libc::rlimit> { |
| 15 | + let mut limit = std::mem::MaybeUninit::zeroed(); |
| 16 | + // SAFETY: libc call; limit is valid pointer to rlimit struct |
| 17 | + if unsafe { libc::getrlimit(resource, limit.as_mut_ptr()) } == 0 { |
| 18 | + // SAFETY: libc call was success, struct must be initialized |
| 19 | + Some(unsafe { limit.assume_init() }) |
| 20 | + } else { |
| 21 | + None |
| 22 | + } |
| 23 | +} |
| 24 | + |
| 25 | +fn translate_rlim(rlim: libc::rlim_t) -> u64 { |
| 26 | + if rlim == libc::RLIM_INFINITY { |
| 27 | + 0 |
| 28 | + } else { |
| 29 | + rlim as u64 |
| 30 | + } |
| 31 | +} |
| 32 | + |
| 33 | +fn kinfo_getproc(pid: libc::pid_t) -> Option<libc::kinfo_proc> { |
| 34 | + // References: |
| 35 | + // kinfo_getproc() code from FreeBSD: https://github.com/freebsd/freebsd-src/blob/b22be3bbb2de75157c97d8baa01ce6cd654caddf/lib/libutil/kinfo_getproc.c |
| 36 | + // code from deno doing similar stuff: https://github.com/denoland/deno/blob/20ae8db50d7d48ad020b83ebe78dc0e9e9eab3b2/runtime/ops/os/mod.rs#L415 |
| 37 | + let mib = [libc::CTL_KERN, libc::KERN_PROC, libc::KERN_PROC_PID, pid]; |
| 38 | + |
| 39 | + let mut kinfo_proc = std::mem::MaybeUninit::zeroed(); |
| 40 | + let kinfo_proc_size = std::mem::size_of_val(&kinfo_proc) as libc::size_t; |
| 41 | + let mut data_size = kinfo_proc_size; |
| 42 | + |
| 43 | + // SAFETY: libc call; mib is statically initialized, kinfo_proc is valid pointer |
| 44 | + // to kinfo_proc and data_size holds its size |
| 45 | + if unsafe { |
| 46 | + libc::sysctl( |
| 47 | + mib.as_ptr(), |
| 48 | + mib.len() as _, |
| 49 | + kinfo_proc.as_mut_ptr() as *mut libc::c_void, |
| 50 | + &mut data_size, |
| 51 | + std::ptr::null(), |
| 52 | + 0, |
| 53 | + ) |
| 54 | + } == 0 |
| 55 | + && data_size == kinfo_proc_size |
| 56 | + { |
| 57 | + // SAFETY: libc call was success and check for struct size passed, struct must be initialized |
| 58 | + Some(unsafe { kinfo_proc.assume_init() }) |
| 59 | + } else { |
| 60 | + None |
| 61 | + } |
| 62 | +} |
| 63 | + |
| 64 | +pub fn collect() -> Metrics { |
| 65 | + let mut metrics = Metrics::default(); |
| 66 | + |
| 67 | + if let Some(usage) = getrusage(libc::RUSAGE_SELF) { |
| 68 | + metrics.cpu_seconds_total = Some( |
| 69 | + (usage.ru_utime.tv_sec + usage.ru_stime.tv_sec) as f64 |
| 70 | + + (usage.ru_utime.tv_usec + usage.ru_stime.tv_usec) as f64 / 1000000.0, |
| 71 | + ); |
| 72 | + } |
| 73 | + |
| 74 | + if let Some(limit_as) = getrlimit(libc::RLIMIT_AS) { |
| 75 | + metrics.virtual_memory_max_bytes = Some(translate_rlim(limit_as.rlim_cur)); |
| 76 | + } |
| 77 | + |
| 78 | + if let Some(limit_as) = getrlimit(libc::RLIMIT_NOFILE) { |
| 79 | + metrics.max_fds = Some(translate_rlim(limit_as.rlim_cur)); |
| 80 | + } |
| 81 | + |
| 82 | + // SAFETY: libc call |
| 83 | + let pid = unsafe { libc::getpid() }; |
| 84 | + |
| 85 | + if let Some(kinfo_proc) = kinfo_getproc(pid) { |
| 86 | + // struct kinfo_proc layout for reference |
| 87 | + // libc crate: https://docs.rs/libc/latest/x86_64-unknown-freebsd/libc/struct.kinfo_proc.html |
| 88 | + // FreeBSD: https://github.com/freebsd/freebsd-src/blob/b22be3bbb2de75157c97d8baa01ce6cd654caddf/lib/libutil/kinfo_getfile.c |
| 89 | + |
| 90 | + // SAFETY: libc call |
| 91 | + let pagesize = unsafe { libc::sysconf(libc::_SC_PAGESIZE) } as u64; |
| 92 | + metrics.virtual_memory_bytes = Some(kinfo_proc.ki_size as u64); |
| 93 | + metrics.resident_memory_bytes = Some(kinfo_proc.ki_rssize as u64 * pagesize); |
| 94 | + use std::convert::TryInto as _; |
| 95 | + metrics.start_time_seconds = kinfo_proc.ki_start.tv_sec.try_into().ok(); |
| 96 | + metrics.threads = kinfo_proc.ki_numthreads.try_into().ok(); |
| 97 | + |
| 98 | + // note that we can't access pointers in kinfo_proc as these point to kernel space |
| 99 | + } |
| 100 | + |
| 101 | + // Alternative to this would be implementing kinfo_getfile() like interface, see |
| 102 | + // https://github.com/freebsd/freebsd-src/blob/b22be3bbb2de75157c97d8baa01ce6cd654caddf/lib/libutil/kinfo_getfile.c |
| 103 | + // it can be done similar to kinfo_getproc() implementation above, but is more cumbersome |
| 104 | + // because we have to parse structures of varying size frow raw memory. As long as |
| 105 | + // it's common to read /proc on Linux, it shuld be as ok to read /dev/fs (which |
| 106 | + // is roughly the same as /proc/self/fd) on FreeBSD. |
| 107 | + metrics.open_fds = std::fs::read_dir("/dev/fd") |
| 108 | + .ok() |
| 109 | + .map(|read_dir| read_dir.count() as u64); |
| 110 | + |
| 111 | + metrics |
| 112 | +} |
0 commit comments