mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-05 17:07:46 +08:00
788 lines
30 KiB
Rust
788 lines
30 KiB
Rust
use std::collections::HashSet;
|
|
use std::sync::Mutex;
|
|
use std::time::{SystemTime, UNIX_EPOCH};
|
|
|
|
use aether_runtime::{MetricKind, MetricSample};
|
|
use sysinfo::{get_current_pid, Networks, Pid, ProcessesToUpdate, System};
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
pub(crate) struct GatewayProcessResourceSnapshot {
|
|
pub(crate) sampled_at_unix_secs: u64,
|
|
pub(crate) system_cpu_usage_basis_points: u64,
|
|
pub(crate) process_cpu_usage_basis_points: u64,
|
|
pub(crate) memory_total_bytes: u64,
|
|
pub(crate) memory_used_bytes: u64,
|
|
pub(crate) memory_available_bytes: u64,
|
|
pub(crate) memory_used_basis_points: u64,
|
|
pub(crate) process_memory_bytes: u64,
|
|
pub(crate) process_virtual_memory_bytes: u64,
|
|
pub(crate) process_memory_basis_points: u64,
|
|
pub(crate) process_uptime_secs: Option<u64>,
|
|
pub(crate) process_threads: u64,
|
|
pub(crate) fd_open_count: u64,
|
|
pub(crate) fd_limit: u64,
|
|
pub(crate) fd_usage_basis_points: u64,
|
|
pub(crate) network_observability_available: u64,
|
|
pub(crate) network_interface_count: u64,
|
|
pub(crate) network_received_bytes_total: u64,
|
|
pub(crate) network_transmitted_bytes_total: u64,
|
|
pub(crate) network_received_packets_total: u64,
|
|
pub(crate) network_transmitted_packets_total: u64,
|
|
pub(crate) network_receive_errors_total: u64,
|
|
pub(crate) network_transmit_errors_total: u64,
|
|
pub(crate) network_receive_dropped_total: u64,
|
|
pub(crate) network_transmit_dropped_total: u64,
|
|
pub(crate) process_socket_fds: u64,
|
|
pub(crate) tcp_state_observability_available: u64,
|
|
pub(crate) host_tcp_connections: u64,
|
|
pub(crate) host_tcp_established_connections: u64,
|
|
pub(crate) host_tcp_listen_connections: u64,
|
|
pub(crate) host_tcp_time_wait_connections: u64,
|
|
pub(crate) host_tcp_syn_sent_connections: u64,
|
|
pub(crate) host_tcp_syn_recv_connections: u64,
|
|
pub(crate) host_tcp_close_wait_connections: u64,
|
|
pub(crate) process_tcp_connections: u64,
|
|
pub(crate) process_tcp_established_connections: u64,
|
|
pub(crate) process_tcp_listen_connections: u64,
|
|
pub(crate) process_tcp_time_wait_connections: u64,
|
|
pub(crate) process_tcp_syn_sent_connections: u64,
|
|
pub(crate) process_tcp_syn_recv_connections: u64,
|
|
pub(crate) process_tcp_close_wait_connections: u64,
|
|
}
|
|
|
|
impl GatewayProcessResourceSnapshot {
|
|
pub(crate) fn to_metric_samples(self) -> Vec<MetricSample> {
|
|
let mut samples = vec![
|
|
MetricSample::new(
|
|
"gateway_process_sampled_at_unix_secs",
|
|
"Unix timestamp of the current gateway process resource sample.",
|
|
MetricKind::Gauge,
|
|
self.sampled_at_unix_secs,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_system_cpu_usage_basis_points",
|
|
"Host CPU usage in basis points of percent, where 10000 means 100 percent.",
|
|
MetricKind::Gauge,
|
|
self.system_cpu_usage_basis_points,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_cpu_usage_basis_points",
|
|
"Gateway process CPU usage in basis points of percent, where 10000 means 100 percent.",
|
|
MetricKind::Gauge,
|
|
self.process_cpu_usage_basis_points,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_system_memory_total_bytes",
|
|
"Total host memory visible to the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.memory_total_bytes,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_system_memory_used_bytes",
|
|
"Used host memory visible to the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.memory_used_bytes,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_system_memory_available_bytes",
|
|
"Available host memory visible to the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.memory_available_bytes,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_system_memory_usage_basis_points",
|
|
"Host memory usage in basis points, where 10000 means 100 percent.",
|
|
MetricKind::Gauge,
|
|
self.memory_used_basis_points,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_memory_bytes",
|
|
"Gateway process resident memory bytes.",
|
|
MetricKind::Gauge,
|
|
self.process_memory_bytes,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_virtual_memory_bytes",
|
|
"Gateway process virtual memory bytes.",
|
|
MetricKind::Gauge,
|
|
self.process_virtual_memory_bytes,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_memory_basis_points",
|
|
"Gateway process resident memory as basis points of host memory.",
|
|
MetricKind::Gauge,
|
|
self.process_memory_basis_points,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_threads",
|
|
"Current number of threads owned by the gateway process where available.",
|
|
MetricKind::Gauge,
|
|
self.process_threads,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_open_fds",
|
|
"Current number of file descriptors opened by the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.fd_open_count,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_fd_limit",
|
|
"Current soft file descriptor limit for the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.fd_limit,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_fd_usage_basis_points",
|
|
"Gateway process file descriptor usage in basis points, where 10000 means 100 percent.",
|
|
MetricKind::Gauge,
|
|
self.fd_usage_basis_points,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_observability_available",
|
|
"Whether host network interface counters are available.",
|
|
MetricKind::Gauge,
|
|
self.network_observability_available,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_interfaces",
|
|
"Number of host network interfaces visible to the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.network_interface_count,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_received_bytes_total",
|
|
"Total host network bytes received across visible interfaces.",
|
|
MetricKind::Counter,
|
|
self.network_received_bytes_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_transmitted_bytes_total",
|
|
"Total host network bytes transmitted across visible interfaces.",
|
|
MetricKind::Counter,
|
|
self.network_transmitted_bytes_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_received_packets_total",
|
|
"Total host network packets received across visible interfaces.",
|
|
MetricKind::Counter,
|
|
self.network_received_packets_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_transmitted_packets_total",
|
|
"Total host network packets transmitted across visible interfaces.",
|
|
MetricKind::Counter,
|
|
self.network_transmitted_packets_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_receive_errors_total",
|
|
"Total host network receive errors across visible interfaces.",
|
|
MetricKind::Counter,
|
|
self.network_receive_errors_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_transmit_errors_total",
|
|
"Total host network transmit errors across visible interfaces.",
|
|
MetricKind::Counter,
|
|
self.network_transmit_errors_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_receive_dropped_total",
|
|
"Total host network receive drops across visible interfaces where available.",
|
|
MetricKind::Counter,
|
|
self.network_receive_dropped_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_network_transmit_dropped_total",
|
|
"Total host network transmit drops across visible interfaces where available.",
|
|
MetricKind::Counter,
|
|
self.network_transmit_dropped_total,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_socket_fds",
|
|
"Current number of socket file descriptors opened by the gateway process.",
|
|
MetricKind::Gauge,
|
|
self.process_socket_fds,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_tcp_state_observability_available",
|
|
"Whether Linux TCP state counters are available from procfs.",
|
|
MetricKind::Gauge,
|
|
self.tcp_state_observability_available,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_connections",
|
|
"Current host TCP connections visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_established_connections",
|
|
"Current host TCP connections in ESTABLISHED state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_established_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_listen_connections",
|
|
"Current host TCP sockets in LISTEN state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_listen_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_time_wait_connections",
|
|
"Current host TCP connections in TIME_WAIT state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_time_wait_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_syn_sent_connections",
|
|
"Current host TCP connections in SYN_SENT state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_syn_sent_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_syn_recv_connections",
|
|
"Current host TCP connections in SYN_RECV state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_syn_recv_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_host_tcp_close_wait_connections",
|
|
"Current host TCP connections in CLOSE_WAIT state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.host_tcp_close_wait_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_connections",
|
|
"Current gateway process TCP connections visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_established_connections",
|
|
"Current gateway process TCP connections in ESTABLISHED state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_established_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_listen_connections",
|
|
"Current gateway process TCP sockets in LISTEN state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_listen_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_time_wait_connections",
|
|
"Current gateway process TCP connections in TIME_WAIT state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_time_wait_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_syn_sent_connections",
|
|
"Current gateway process TCP connections in SYN_SENT state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_syn_sent_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_syn_recv_connections",
|
|
"Current gateway process TCP connections in SYN_RECV state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_syn_recv_connections,
|
|
),
|
|
MetricSample::new(
|
|
"gateway_process_tcp_close_wait_connections",
|
|
"Current gateway process TCP connections in CLOSE_WAIT state visible in procfs.",
|
|
MetricKind::Gauge,
|
|
self.process_tcp_close_wait_connections,
|
|
),
|
|
];
|
|
|
|
if let Some(process_uptime_secs) = self.process_uptime_secs {
|
|
samples.push(MetricSample::new(
|
|
"gateway_process_uptime_seconds",
|
|
"Gateway process uptime in seconds.",
|
|
MetricKind::Gauge,
|
|
process_uptime_secs,
|
|
));
|
|
}
|
|
|
|
samples
|
|
}
|
|
}
|
|
|
|
pub(crate) struct GatewayProcessResourceMonitor {
|
|
system: Mutex<System>,
|
|
networks: Mutex<Networks>,
|
|
current_pid: Option<Pid>,
|
|
}
|
|
|
|
impl GatewayProcessResourceMonitor {
|
|
pub(crate) fn new() -> Self {
|
|
let mut system = System::new_all();
|
|
let mut networks = Networks::new_with_refreshed_list();
|
|
let current_pid = get_current_pid().ok();
|
|
if let Some(pid) = current_pid {
|
|
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
|
}
|
|
system.refresh_cpu_usage();
|
|
system.refresh_memory();
|
|
networks.refresh();
|
|
Self {
|
|
system: Mutex::new(system),
|
|
networks: Mutex::new(networks),
|
|
current_pid,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn snapshot(&self) -> GatewayProcessResourceSnapshot {
|
|
let mut system = match self.system.lock() {
|
|
Ok(guard) => guard,
|
|
Err(poisoned) => poisoned.into_inner(),
|
|
};
|
|
|
|
system.refresh_cpu_usage();
|
|
system.refresh_memory();
|
|
if let Some(pid) = self.current_pid {
|
|
system.refresh_processes(ProcessesToUpdate::Some(&[pid]), true);
|
|
}
|
|
|
|
let memory_total_bytes = system.total_memory();
|
|
let memory_used_bytes = system.used_memory();
|
|
let memory_available_bytes = system.available_memory();
|
|
let (
|
|
process_cpu_usage_basis_points,
|
|
process_memory_bytes,
|
|
process_virtual_memory_bytes,
|
|
process_uptime_secs,
|
|
) = self
|
|
.current_pid
|
|
.and_then(|pid| system.process(pid))
|
|
.map(|process| {
|
|
(
|
|
percent_to_basis_points(process.cpu_usage() as f64),
|
|
process.memory(),
|
|
process.virtual_memory(),
|
|
Some(process.run_time()),
|
|
)
|
|
})
|
|
.unwrap_or((0, 0, 0, None));
|
|
let fd_open_count = open_file_descriptors().unwrap_or(0);
|
|
let fd_limit = file_descriptor_limit();
|
|
let network = self.network_snapshot();
|
|
let sockets = socket_snapshot().unwrap_or_default();
|
|
|
|
GatewayProcessResourceSnapshot {
|
|
sampled_at_unix_secs: current_unix_secs(),
|
|
system_cpu_usage_basis_points: percent_to_basis_points(system.global_cpu_usage() as f64),
|
|
process_cpu_usage_basis_points,
|
|
memory_total_bytes,
|
|
memory_used_bytes,
|
|
memory_available_bytes,
|
|
memory_used_basis_points: ratio_to_basis_points(memory_used_bytes, memory_total_bytes),
|
|
process_memory_bytes,
|
|
process_virtual_memory_bytes,
|
|
process_memory_basis_points: ratio_to_basis_points(
|
|
process_memory_bytes,
|
|
memory_total_bytes,
|
|
),
|
|
process_uptime_secs,
|
|
process_threads: process_thread_count().unwrap_or(0),
|
|
fd_open_count,
|
|
fd_limit,
|
|
fd_usage_basis_points: ratio_to_basis_points(fd_open_count, fd_limit),
|
|
network_observability_available: network.observability_available,
|
|
network_interface_count: network.interface_count,
|
|
network_received_bytes_total: network.received_bytes_total,
|
|
network_transmitted_bytes_total: network.transmitted_bytes_total,
|
|
network_received_packets_total: network.received_packets_total,
|
|
network_transmitted_packets_total: network.transmitted_packets_total,
|
|
network_receive_errors_total: network.receive_errors_total,
|
|
network_transmit_errors_total: network.transmit_errors_total,
|
|
network_receive_dropped_total: network.receive_dropped_total,
|
|
network_transmit_dropped_total: network.transmit_dropped_total,
|
|
process_socket_fds: sockets.process_socket_fds,
|
|
tcp_state_observability_available: sockets.tcp_state_observability_available,
|
|
host_tcp_connections: sockets.host_tcp.total,
|
|
host_tcp_established_connections: sockets.host_tcp.established,
|
|
host_tcp_listen_connections: sockets.host_tcp.listen,
|
|
host_tcp_time_wait_connections: sockets.host_tcp.time_wait,
|
|
host_tcp_syn_sent_connections: sockets.host_tcp.syn_sent,
|
|
host_tcp_syn_recv_connections: sockets.host_tcp.syn_recv,
|
|
host_tcp_close_wait_connections: sockets.host_tcp.close_wait,
|
|
process_tcp_connections: sockets.process_tcp.total,
|
|
process_tcp_established_connections: sockets.process_tcp.established,
|
|
process_tcp_listen_connections: sockets.process_tcp.listen,
|
|
process_tcp_time_wait_connections: sockets.process_tcp.time_wait,
|
|
process_tcp_syn_sent_connections: sockets.process_tcp.syn_sent,
|
|
process_tcp_syn_recv_connections: sockets.process_tcp.syn_recv,
|
|
process_tcp_close_wait_connections: sockets.process_tcp.close_wait,
|
|
}
|
|
}
|
|
|
|
pub(crate) fn metric_samples(&self) -> Vec<MetricSample> {
|
|
self.snapshot().to_metric_samples()
|
|
}
|
|
|
|
fn network_snapshot(&self) -> GatewayNetworkSnapshot {
|
|
let mut networks = match self.networks.lock() {
|
|
Ok(guard) => guard,
|
|
Err(poisoned) => poisoned.into_inner(),
|
|
};
|
|
networks.refresh();
|
|
|
|
let mut snapshot = GatewayNetworkSnapshot {
|
|
observability_available: u64::from(!networks.list().is_empty()),
|
|
interface_count: networks.list().len() as u64,
|
|
..GatewayNetworkSnapshot::default()
|
|
};
|
|
for network in networks.list().values() {
|
|
snapshot.received_bytes_total = snapshot
|
|
.received_bytes_total
|
|
.saturating_add(network.total_received());
|
|
snapshot.transmitted_bytes_total = snapshot
|
|
.transmitted_bytes_total
|
|
.saturating_add(network.total_transmitted());
|
|
snapshot.received_packets_total = snapshot
|
|
.received_packets_total
|
|
.saturating_add(network.total_packets_received());
|
|
snapshot.transmitted_packets_total = snapshot
|
|
.transmitted_packets_total
|
|
.saturating_add(network.total_packets_transmitted());
|
|
snapshot.receive_errors_total = snapshot
|
|
.receive_errors_total
|
|
.saturating_add(network.total_errors_on_received());
|
|
snapshot.transmit_errors_total = snapshot
|
|
.transmit_errors_total
|
|
.saturating_add(network.total_errors_on_transmitted());
|
|
}
|
|
|
|
let drops = network_drop_totals();
|
|
snapshot.receive_dropped_total = drops.receive_dropped_total;
|
|
snapshot.transmit_dropped_total = drops.transmit_dropped_total;
|
|
snapshot
|
|
}
|
|
}
|
|
|
|
impl Default for GatewayProcessResourceMonitor {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl std::fmt::Debug for GatewayProcessResourceMonitor {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
f.debug_struct("GatewayProcessResourceMonitor")
|
|
.field("current_pid", &self.current_pid)
|
|
.finish_non_exhaustive()
|
|
}
|
|
}
|
|
|
|
fn current_unix_secs() -> u64 {
|
|
SystemTime::now()
|
|
.duration_since(UNIX_EPOCH)
|
|
.map(|duration| duration.as_secs())
|
|
.unwrap_or(0)
|
|
}
|
|
|
|
fn percent_to_basis_points(value: f64) -> u64 {
|
|
if !value.is_finite() || value.is_sign_negative() {
|
|
0
|
|
} else {
|
|
(value * 100.0).round().clamp(0.0, u64::MAX as f64) as u64
|
|
}
|
|
}
|
|
|
|
fn ratio_to_basis_points(value: u64, total: u64) -> u64 {
|
|
value.saturating_mul(10_000).checked_div(total).unwrap_or(0)
|
|
}
|
|
|
|
fn open_file_descriptors() -> Option<u64> {
|
|
#[cfg(unix)]
|
|
{
|
|
for dir in ["/proc/self/fd", "/dev/fd"] {
|
|
if let Ok(entries) = std::fs::read_dir(dir) {
|
|
return Some(entries.count() as u64);
|
|
}
|
|
}
|
|
}
|
|
None
|
|
}
|
|
|
|
fn file_descriptor_limit() -> u64 {
|
|
#[cfg(unix)]
|
|
{
|
|
let mut limit = libc::rlimit {
|
|
rlim_cur: 0,
|
|
rlim_max: 0,
|
|
};
|
|
let result = unsafe { libc::getrlimit(libc::RLIMIT_NOFILE, &mut limit) };
|
|
if result == 0 {
|
|
return limit.rlim_cur;
|
|
}
|
|
}
|
|
0
|
|
}
|
|
|
|
fn process_thread_count() -> Option<u64> {
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
return std::fs::read_to_string("/proc/self/status")
|
|
.ok()
|
|
.and_then(|raw| parse_linux_process_thread_count(&raw));
|
|
}
|
|
|
|
#[allow(unreachable_code)]
|
|
None
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn parse_linux_process_thread_count(raw: &str) -> Option<u64> {
|
|
raw.lines().find_map(|line| {
|
|
let value = line.strip_prefix("Threads:")?.trim();
|
|
value.parse::<u64>().ok()
|
|
})
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
struct GatewayNetworkSnapshot {
|
|
observability_available: u64,
|
|
interface_count: u64,
|
|
received_bytes_total: u64,
|
|
transmitted_bytes_total: u64,
|
|
received_packets_total: u64,
|
|
transmitted_packets_total: u64,
|
|
receive_errors_total: u64,
|
|
transmit_errors_total: u64,
|
|
receive_dropped_total: u64,
|
|
transmit_dropped_total: u64,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
struct NetworkDropTotals {
|
|
receive_dropped_total: u64,
|
|
transmit_dropped_total: u64,
|
|
}
|
|
|
|
fn network_drop_totals() -> NetworkDropTotals {
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
return std::fs::read_to_string("/proc/net/dev")
|
|
.ok()
|
|
.map(|raw| parse_linux_network_drop_totals(&raw))
|
|
.unwrap_or_default();
|
|
}
|
|
|
|
#[allow(unreachable_code)]
|
|
NetworkDropTotals::default()
|
|
}
|
|
|
|
fn parse_linux_network_drop_totals(raw: &str) -> NetworkDropTotals {
|
|
let mut totals = NetworkDropTotals::default();
|
|
for line in raw.lines().skip(2) {
|
|
let Some((_, counters)) = line.split_once(':') else {
|
|
continue;
|
|
};
|
|
let fields: Vec<&str> = counters.split_whitespace().collect();
|
|
if fields.len() < 12 {
|
|
continue;
|
|
}
|
|
totals.receive_dropped_total = totals
|
|
.receive_dropped_total
|
|
.saturating_add(fields[3].parse::<u64>().unwrap_or_default());
|
|
totals.transmit_dropped_total = totals
|
|
.transmit_dropped_total
|
|
.saturating_add(fields[11].parse::<u64>().unwrap_or_default());
|
|
}
|
|
totals
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default)]
|
|
struct SocketSnapshot {
|
|
process_socket_fds: u64,
|
|
tcp_state_observability_available: u64,
|
|
host_tcp: TcpStateCounts,
|
|
process_tcp: TcpStateCounts,
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
|
struct TcpStateCounts {
|
|
total: u64,
|
|
established: u64,
|
|
listen: u64,
|
|
time_wait: u64,
|
|
syn_sent: u64,
|
|
syn_recv: u64,
|
|
close_wait: u64,
|
|
}
|
|
|
|
impl TcpStateCounts {
|
|
fn observe(&mut self, state: &str) {
|
|
self.total = self.total.saturating_add(1);
|
|
match state {
|
|
"01" => self.established = self.established.saturating_add(1),
|
|
"02" => self.syn_sent = self.syn_sent.saturating_add(1),
|
|
"03" => self.syn_recv = self.syn_recv.saturating_add(1),
|
|
"06" => self.time_wait = self.time_wait.saturating_add(1),
|
|
"08" => self.close_wait = self.close_wait.saturating_add(1),
|
|
"0A" | "0a" => self.listen = self.listen.saturating_add(1),
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
fn socket_snapshot() -> Option<SocketSnapshot> {
|
|
#[cfg(target_os = "linux")]
|
|
{
|
|
let process_inodes = process_socket_inodes().unwrap_or_default();
|
|
let mut snapshot = SocketSnapshot {
|
|
process_socket_fds: process_inodes.len() as u64,
|
|
..SocketSnapshot::default()
|
|
};
|
|
let mut observed_tcp_table = false;
|
|
|
|
for path in ["/proc/net/tcp", "/proc/net/tcp6"] {
|
|
let Ok(raw) = std::fs::read_to_string(path) else {
|
|
continue;
|
|
};
|
|
observed_tcp_table = true;
|
|
observe_linux_tcp_table(&raw, &process_inodes, &mut snapshot);
|
|
}
|
|
|
|
if observed_tcp_table {
|
|
snapshot.tcp_state_observability_available = 1;
|
|
return Some(snapshot);
|
|
}
|
|
}
|
|
|
|
#[allow(unreachable_code)]
|
|
None
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
fn process_socket_inodes() -> Option<HashSet<u64>> {
|
|
let mut inodes = HashSet::new();
|
|
for entry in std::fs::read_dir("/proc/self/fd").ok()? {
|
|
let Ok(entry) = entry else {
|
|
continue;
|
|
};
|
|
let Ok(target) = std::fs::read_link(entry.path()) else {
|
|
continue;
|
|
};
|
|
if let Some(inode) = parse_socket_inode(&target.to_string_lossy()) {
|
|
inodes.insert(inode);
|
|
}
|
|
}
|
|
Some(inodes)
|
|
}
|
|
|
|
fn parse_socket_inode(target: &str) -> Option<u64> {
|
|
target
|
|
.strip_prefix("socket:[")
|
|
.and_then(|value| value.strip_suffix(']'))
|
|
.and_then(|value| value.parse::<u64>().ok())
|
|
}
|
|
|
|
fn observe_linux_tcp_table(
|
|
raw: &str,
|
|
process_inodes: &HashSet<u64>,
|
|
snapshot: &mut SocketSnapshot,
|
|
) {
|
|
for line in raw.lines().skip(1) {
|
|
let fields: Vec<&str> = line.split_whitespace().collect();
|
|
if fields.len() <= 9 {
|
|
continue;
|
|
}
|
|
let state = fields[3];
|
|
snapshot.host_tcp.observe(state);
|
|
let inode = fields[9].parse::<u64>().unwrap_or_default();
|
|
if process_inodes.contains(&inode) {
|
|
snapshot.process_tcp.observe(state);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn parses_socket_inode_targets() {
|
|
assert_eq!(parse_socket_inode("socket:[12345]"), Some(12345));
|
|
assert_eq!(parse_socket_inode("anon_inode:[eventpoll]"), None);
|
|
assert_eq!(parse_socket_inode("socket:12345"), None);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_linux_tcp_table_state_counts() {
|
|
let raw = "\
|
|
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
|
|
0: 0100007F:1F90 00000000:0000 0A 00000000:00000000 00:00000000 00000000 501 0 111 1 0000000000000000 100 0 0 10 0
|
|
1: 0100007F:9C40 0100007F:1F90 01 00000000:00000000 00:00000000 00000000 501 0 222 1 0000000000000000 20 4 30 10 -1
|
|
2: 0100007F:9C41 0100007F:1F90 08 00000000:00000000 00:00000000 00000000 501 0 333 1 0000000000000000 20 4 30 10 -1
|
|
";
|
|
let mut inodes = HashSet::new();
|
|
inodes.insert(111);
|
|
inodes.insert(333);
|
|
let mut snapshot = SocketSnapshot::default();
|
|
|
|
observe_linux_tcp_table(raw, &inodes, &mut snapshot);
|
|
|
|
assert_eq!(snapshot.host_tcp.total, 3);
|
|
assert_eq!(snapshot.host_tcp.listen, 1);
|
|
assert_eq!(snapshot.host_tcp.established, 1);
|
|
assert_eq!(snapshot.host_tcp.close_wait, 1);
|
|
assert_eq!(snapshot.process_tcp.total, 2);
|
|
assert_eq!(snapshot.process_tcp.listen, 1);
|
|
assert_eq!(snapshot.process_tcp.close_wait, 1);
|
|
}
|
|
|
|
#[test]
|
|
fn parses_linux_network_drop_totals() {
|
|
let raw = "\
|
|
Inter-| Receive | Transmit
|
|
face |bytes packets errs drop fifo frame compressed multicast|bytes packets errs drop fifo colls carrier compressed
|
|
lo: 100 1 0 2 0 0 0 0 200 2 0 3 0 0 0 0
|
|
eth0: 300 3 0 5 0 0 0 0 400 4 0 7 0 0 0 0
|
|
";
|
|
|
|
let totals = parse_linux_network_drop_totals(raw);
|
|
|
|
assert_eq!(totals.receive_dropped_total, 7);
|
|
assert_eq!(totals.transmit_dropped_total, 10);
|
|
}
|
|
|
|
#[test]
|
|
fn process_resource_monitor_renders_gateway_metrics() {
|
|
let samples = GatewayProcessResourceMonitor::new().metric_samples();
|
|
assert!(samples
|
|
.iter()
|
|
.any(|sample| sample.name == "gateway_process_memory_bytes"));
|
|
assert!(samples
|
|
.iter()
|
|
.any(|sample| sample.name == "gateway_process_open_fds"));
|
|
assert!(samples
|
|
.iter()
|
|
.any(|sample| sample.name == "gateway_process_fd_usage_basis_points"));
|
|
assert!(samples
|
|
.iter()
|
|
.any(|sample| sample.name == "gateway_process_threads"));
|
|
assert!(samples
|
|
.iter()
|
|
.any(|sample| sample.name == "gateway_process_socket_fds"));
|
|
assert!(samples
|
|
.iter()
|
|
.any(|sample| sample.name == "gateway_network_observability_available"));
|
|
}
|
|
|
|
#[cfg(target_os = "linux")]
|
|
#[test]
|
|
fn parses_linux_process_thread_count() {
|
|
let raw = "\
|
|
Name:\taether-gateway
|
|
State:\tS (sleeping)
|
|
Threads:\t42
|
|
";
|
|
|
|
assert_eq!(parse_linux_process_thread_count(raw), Some(42));
|
|
assert_eq!(parse_linux_process_thread_count("Name:\ttest\n"), None);
|
|
}
|
|
}
|