pub mod config;
pub mod proc;
pub mod ringbuf;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ProcessInfo {
pub pid: i32,
pub name: String,
pub cmdline: String,
pub exe: Option<String>,
pub state: String,
pub ppid: i32,
pub uid: u32,
pub gid: u32,
pub started_at: Option<chrono::DateTime<chrono::Local>>,
pub threads: i32,
pub vm_size_kb: u64,
pub rss_kb: u64,
pub pss_kb: Option<u64>,
pub swap_kb: Option<u64>,
pub cpu_percent: Option<f64>,
pub io_read_bytes: Option<u64>,
pub io_write_bytes: Option<u64>,
pub fd_count: Option<usize>,
pub kernel: Option<KernelInfo>,
pub network: Option<NetworkInfo>,
pub open_files: Option<Vec<OpenFile>>,
pub env_vars: Option<Vec<EnvVar>>,
pub process_tree: Option<ProcessNode>,
pub gpu: Option<Vec<GpuInfo>>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct KernelInfo {
pub sched_policy: String,
pub nice: i32,
pub priority: i32,
pub oom_score: i32,
pub oom_score_adj: i32,
pub cgroup: String,
pub namespaces: Vec<NamespaceEntry>,
pub cap_permitted: String,
pub cap_effective: String,
pub seccomp: u32,
pub voluntary_ctxt_switches: Option<u64>,
pub nonvoluntary_ctxt_switches: Option<u64>,
pub security_label: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NamespaceEntry {
pub ns_type: String,
pub inode: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct NetworkInfo {
pub listening_tcp: Vec<SocketEntry>,
pub listening_udp: Vec<SocketEntry>,
pub connections: Vec<ConnectionEntry>,
pub unix_sockets: Option<Vec<UnixSocketEntry>>,
pub traffic_rx_bytes_per_sec: Option<u64>,
pub traffic_tx_bytes_per_sec: Option<u64>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct UnixSocketEntry {
pub path: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SocketEntry {
pub protocol: String,
pub local_addr: String,
pub local_port: u16,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ConnectionEntry {
pub protocol: String,
pub local_addr: String,
pub local_port: u16,
pub remote_addr: String,
pub remote_port: u16,
pub state: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct OpenFile {
pub fd: u32,
pub fd_type: String,
pub description: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct EnvVar {
pub key: String,
pub value: String,
pub redacted: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ProcessNode {
pub pid: i32,
pub name: String,
pub uid: u32,
pub rss_kb: u64,
pub children: Vec<ProcessNode>,
}
pub use resource_sampler::gpu::GpuInfo;
pub use signal_engine::impact::SignalImpact;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FdLeakWarning {
pub start_count: usize,
pub end_count: usize,
pub consecutive_increases: usize,
}
#[derive(Debug, thiserror::Error)]
pub enum PeekError {
#[error("process {0} not found")]
NotFound(i32),
#[error("failed to read /proc for pid {pid}: {source}")]
ProcIo {
pid: i32,
#[source]
source: std::io::Error,
},
#[error("failed to parse /proc for pid {pid}: {msg}")]
ProcParse { pid: i32, msg: String },
}
impl From<peek_proc_reader::ProcReaderError> for PeekError {
fn from(e: peek_proc_reader::ProcReaderError) -> Self {
let pid = e.pid().unwrap_or(-1);
match e {
peek_proc_reader::ProcReaderError::NotFound(pid) => PeekError::NotFound(pid),
peek_proc_reader::ProcReaderError::Io { source, .. } => {
PeekError::ProcIo { pid, source }
}
peek_proc_reader::ProcReaderError::Parse { msg, .. } => {
PeekError::ProcParse { pid, msg }
}
}
}
}
pub type Result<T> = std::result::Result<T, PeekError>;
#[derive(Debug, Default, Clone)]
pub struct CollectOptions {
pub resources: bool,
pub kernel: bool,
pub network: bool,
pub files: bool,
pub env: bool,
pub tree: bool,
pub gpu: bool,
}
pub fn collect(pid: i32) -> Result<ProcessInfo> {
proc::collect_process(pid, false)
}
pub fn collect_extended(pid: i32, opts: &CollectOptions) -> Result<ProcessInfo> {
let mut info = proc::collect_process(pid, opts.resources)?;
#[cfg(target_os = "linux")]
{
if opts.resources {
info.io_read_bytes = proc::resources::read_io(pid).map(|io| io.0).ok();
info.io_write_bytes = proc::resources::read_io(pid).map(|io| io.1).ok();
info.fd_count = proc::files::count_fds(pid).ok();
if let Some((_rss, pss, swap)) = resource_sampler::memory::sample_memory(pid) {
info.pss_kb = Some(pss);
info.swap_kb = Some(swap);
}
}
if opts.kernel {
info.kernel = proc::kernel::collect_kernel(pid).ok();
}
if opts.network {
info.network = proc::network::collect_network(pid).ok();
}
if opts.files {
info.open_files = proc::files::collect_files(pid).ok();
}
if opts.env {
info.env_vars = proc::env::collect_env(pid).ok();
}
if opts.tree {
info.process_tree = proc::tree::build_tree(pid).ok();
}
if opts.gpu {
info.gpu = Some(proc::gpu::collect_gpu(pid));
}
}
Ok(info)
}
pub fn signal_impact(_pid: i32) -> anyhow::Result<SignalImpact> {
#[cfg(target_os = "linux")]
{
signal_engine::impact::analyze_impact(_pid)
}
#[cfg(not(target_os = "linux"))]
{
anyhow::bail!("Signal impact analysis is only available on Linux")
}
}
pub fn binary_description(name: &str) -> Option<String> {
kernel_explainer::well_known::binary_description(name).map(|s| s.to_string())
}
pub fn oom_description(score: i32) -> &'static str {
kernel_explainer::oom::oom_description(score)
}
#[cfg(target_os = "linux")]
pub fn fd_soft_limit(pid: i32) -> Option<u64> {
use peek_proc_reader::limits::read_limits;
let limits = read_limits(pid).ok()?;
limits.max_open_files_soft
}
#[cfg(not(target_os = "linux"))]
pub fn fd_soft_limit(_pid: i32) -> Option<u64> {
None
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub fn current_syscall(pid: i32) -> Option<(String, String)> {
let (num, _) = peek_proc_reader::current::read_syscall(pid)?;
let name = kernel_explainer::syscalls::syscall_name_x86_64(num)?;
let desc = kernel_explainer::syscalls::syscall_description(name);
Some((name.to_string(), desc.to_string()))
}
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
pub fn current_syscall(_pid: i32) -> Option<(String, String)> {
None
}
pub fn resolve_remote(addr: &str) -> Option<String> {
network_inspector::resolver::resolve(addr)
}