v0.24.7
This commit is contained in:
1 parent
5e23e9af02
commit
3883a21bc4
56 files changed
+1640
-403
No files matched your search
@@ -1,180 +0,0 @@
|
||||
use super::{now_ms, platform_name, NetworkInterfaceView, View};
|
||||
use std::{collections::BTreeMap, process::Command};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct Counters {
|
||||
cpu_total: Option<u64>,
|
||||
cpu_idle: Option<u64>,
|
||||
process_cpu_micros: Option<u64>,
|
||||
}
|
||||
|
||||
fn command_output(program: &str, args: &[&str]) -> Option<String> {
|
||||
let output = Command::new(program).args(args).output().ok()?;
|
||||
output.status.success().then(|| String::from_utf8_lossy(&output.stdout).trim().to_owned())
|
||||
}
|
||||
|
||||
fn sysctl(name: &str) -> Option<String> {
|
||||
command_output("sysctl", &["-n", name])
|
||||
}
|
||||
|
||||
fn sysctl_u64(names: &[&str]) -> Option<u64> {
|
||||
names.iter().find_map(|name| sysctl(name)?.split_whitespace().next()?.parse().ok())
|
||||
}
|
||||
|
||||
fn cpu_times() -> Option<(u64, u64)> {
|
||||
let values: Vec<u64> = sysctl("kern.cp_time")?
|
||||
.split_whitespace()
|
||||
.filter_map(|value| value.parse().ok())
|
||||
.collect();
|
||||
if values.len() < 5 {
|
||||
return None;
|
||||
}
|
||||
Some((values.iter().sum(), values[4]))
|
||||
}
|
||||
|
||||
fn process_cpu_micros() -> Option<u64> {
|
||||
let mut usage = std::mem::MaybeUninit::<libc::rusage>::zeroed();
|
||||
// SAFETY: getrusage initializes the supplied rusage structure on success.
|
||||
let rc = unsafe { libc::getrusage(libc::RUSAGE_SELF, usage.as_mut_ptr()) };
|
||||
if rc != 0 {
|
||||
return None;
|
||||
}
|
||||
// SAFETY: rc == 0 guarantees the structure was initialized by libc.
|
||||
let usage = unsafe { usage.assume_init() };
|
||||
let user = u64::try_from(usage.ru_utime.tv_sec).ok()?.saturating_mul(1_000_000)
|
||||
+ u64::try_from(usage.ru_utime.tv_usec).ok()?;
|
||||
let system = u64::try_from(usage.ru_stime.tv_sec).ok()?.saturating_mul(1_000_000)
|
||||
+ u64::try_from(usage.ru_stime.tv_usec).ok()?;
|
||||
Some(user.saturating_add(system))
|
||||
}
|
||||
|
||||
fn load_average() -> Option<[f64; 3]> {
|
||||
let raw = sysctl("vm.loadavg")?;
|
||||
let values: Vec<f64> = raw
|
||||
.trim_matches(|c: char| c == '{' || c == '}' || c.is_whitespace())
|
||||
.split_whitespace()
|
||||
.filter_map(|value| value.parse().ok())
|
||||
.collect();
|
||||
(values.len() >= 3).then(|| [values[0], values[1], values[2]])
|
||||
}
|
||||
|
||||
fn process_rss_bytes() -> Option<u64> {
|
||||
let pid = std::process::id().to_string();
|
||||
command_output("ps", &["-o", "rss=", "-p", &pid])?
|
||||
.split_whitespace()
|
||||
.next()?
|
||||
.parse::<u64>()
|
||||
.ok()
|
||||
.map(|kib| kib.saturating_mul(1024))
|
||||
}
|
||||
|
||||
fn network_interfaces() -> Vec<NetworkInterfaceView> {
|
||||
let Some(text) = command_output("netstat", &["-ibn"]) else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut lines = text.lines();
|
||||
let Some(header) = lines.next() else {
|
||||
return Vec::new();
|
||||
};
|
||||
let columns: Vec<_> = header.split_whitespace().collect();
|
||||
let name_index = columns.iter().position(|value| value.eq_ignore_ascii_case("Name"));
|
||||
let ibytes_index = columns.iter().position(|value| value.eq_ignore_ascii_case("Ibytes"));
|
||||
let obytes_index = columns.iter().position(|value| value.eq_ignore_ascii_case("Obytes"));
|
||||
let (Some(name_index), Some(ibytes_index), Some(obytes_index)) =
|
||||
(name_index, ibytes_index, obytes_index)
|
||||
else {
|
||||
return Vec::new();
|
||||
};
|
||||
let mut interfaces: BTreeMap<String, (u64, u64)> = BTreeMap::new();
|
||||
for line in lines {
|
||||
let fields: Vec<_> = line.split_whitespace().collect();
|
||||
if fields.len() <= name_index || fields.len() <= ibytes_index || fields.len() <= obytes_index {
|
||||
continue;
|
||||
}
|
||||
let name = fields[name_index].trim_end_matches('*');
|
||||
if name.is_empty() || name == "lo0" {
|
||||
continue;
|
||||
}
|
||||
let (Ok(rx), Ok(tx)) = (fields[ibytes_index].parse::<u64>(), fields[obytes_index].parse::<u64>()) else {
|
||||
continue;
|
||||
};
|
||||
let entry = interfaces.entry(name.to_owned()).or_default();
|
||||
entry.0 = entry.0.max(rx);
|
||||
entry.1 = entry.1.max(tx);
|
||||
}
|
||||
interfaces
|
||||
.into_iter()
|
||||
.take(64)
|
||||
.map(|(name, (rx_bytes, tx_bytes))| NetworkInterfaceView {
|
||||
name,
|
||||
rx_bytes,
|
||||
tx_bytes,
|
||||
rx_bytes_per_second: None,
|
||||
tx_bytes_per_second: None,
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn sample(previous: Option<(View, Counters)>) -> (View, Counters) {
|
||||
let (cpu_total, cpu_idle) = cpu_times().map_or((None, None), |(total, idle)| (Some(total), Some(idle)));
|
||||
let counters = Counters {
|
||||
cpu_total,
|
||||
cpu_idle,
|
||||
process_cpu_micros: process_cpu_micros(),
|
||||
};
|
||||
let mut view = View {
|
||||
source: "bsd_native",
|
||||
platform: platform_name(),
|
||||
sampled_at_ms: now_ms(),
|
||||
logical_cpus: sysctl_u64(&["hw.ncpu", "hw.ncpuonline"])
|
||||
.and_then(|value| usize::try_from(value).ok())
|
||||
.or_else(|| std::thread::available_parallelism().ok().map(usize::from)),
|
||||
memory_total_bytes: sysctl_u64(&["hw.physmem64", "hw.physmem"]),
|
||||
process_rss_bytes: process_rss_bytes(),
|
||||
load_average: load_average(),
|
||||
network_interfaces: network_interfaces(),
|
||||
..Default::default()
|
||||
};
|
||||
if !view.network_interfaces.is_empty() {
|
||||
view.network_rx_bytes = Some(view.network_interfaces.iter().map(|item| item.rx_bytes).sum());
|
||||
view.network_tx_bytes = Some(view.network_interfaces.iter().map(|item| item.tx_bytes).sum());
|
||||
}
|
||||
if let Some((old, old_counters)) = previous {
|
||||
let system_total = counters
|
||||
.cpu_total
|
||||
.zip(old_counters.cpu_total)
|
||||
.and_then(|(new, old)| new.checked_sub(old))
|
||||
.filter(|value| *value > 0);
|
||||
if let Some(total) = system_total {
|
||||
view.cpu_percent = counters
|
||||
.cpu_idle
|
||||
.zip(old_counters.cpu_idle)
|
||||
.and_then(|(new, old)| new.checked_sub(old))
|
||||
.map(|idle| (100.0 * (1.0 - idle as f64 / total as f64)).clamp(0.0, 100.0));
|
||||
}
|
||||
let seconds = view.sampled_at_ms.saturating_sub(old.sampled_at_ms) as f64 / 1000.0;
|
||||
if seconds > 0.0 {
|
||||
view.process_cpu_percent = counters
|
||||
.process_cpu_micros
|
||||
.zip(old_counters.process_cpu_micros)
|
||||
.and_then(|(new, old)| new.checked_sub(old))
|
||||
.map(|micros| micros as f64 / (seconds * 10_000.0));
|
||||
view.network_rx_bytes_per_second = view
|
||||
.network_rx_bytes
|
||||
.zip(old.network_rx_bytes)
|
||||
.map(|(new, old)| new.saturating_sub(old) as f64 / seconds);
|
||||
view.network_tx_bytes_per_second = view
|
||||
.network_tx_bytes
|
||||
.zip(old.network_tx_bytes)
|
||||
.map(|(new, old)| new.saturating_sub(old) as f64 / seconds);
|
||||
for interface in &mut view.network_interfaces {
|
||||
if let Some(previous) = old.network_interfaces.iter().find(|old| old.name == interface.name) {
|
||||
interface.rx_bytes_per_second = Some(interface.rx_bytes.saturating_sub(previous.rx_bytes) as f64 / seconds);
|
||||
interface.tx_bytes_per_second = Some(interface.tx_bytes.saturating_sub(previous.tx_bytes) as f64 / seconds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Keep unknown/unsupported BSD metrics as None instead of substituting a different semantic.
|
||||
(view, counters)
|
||||
}
|
||||
Reference in new issue
Block a user