1pub mod config;
7pub mod proc;
8pub mod ringbuf;
9
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Serialize, Deserialize, Clone)]
15pub struct ProcessInfo {
16 pub pid: i32,
17 pub name: String,
18 pub cmdline: String,
19 pub exe: Option<String>,
21 pub state: String,
22 pub ppid: i32,
23 pub uid: u32,
24 pub gid: u32,
25 pub started_at: Option<chrono::DateTime<chrono::Local>>,
26 pub threads: i32,
27 pub vm_size_kb: u64,
28 pub rss_kb: u64,
29 pub pss_kb: Option<u64>,
31 pub swap_kb: Option<u64>,
33 pub cpu_percent: Option<f64>,
35 pub io_read_bytes: Option<u64>,
36 pub io_write_bytes: Option<u64>,
37 pub fd_count: Option<usize>,
38 pub kernel: Option<KernelInfo>,
40 pub network: Option<NetworkInfo>,
41 pub open_files: Option<Vec<OpenFile>>,
42 pub env_vars: Option<Vec<EnvVar>>,
43 pub process_tree: Option<ProcessNode>,
44 pub gpu: Option<Vec<GpuInfo>>,
45}
46
47#[derive(Debug, Serialize, Deserialize, Clone)]
50pub struct KernelInfo {
51 pub sched_policy: String,
52 pub nice: i32,
53 pub priority: i32,
54 pub oom_score: i32,
55 pub oom_score_adj: i32,
56 pub cgroup: String,
57 pub namespaces: Vec<NamespaceEntry>,
58 pub cap_permitted: String,
59 pub cap_effective: String,
60 pub seccomp: u32,
61 pub voluntary_ctxt_switches: Option<u64>,
62 pub nonvoluntary_ctxt_switches: Option<u64>,
63 pub security_label: Option<String>,
65}
66
67#[derive(Debug, Serialize, Deserialize, Clone)]
68pub struct NamespaceEntry {
69 pub ns_type: String,
70 pub inode: String,
71}
72
73#[derive(Debug, Serialize, Deserialize, Clone)]
76pub struct NetworkInfo {
77 pub listening_tcp: Vec<SocketEntry>,
78 pub listening_udp: Vec<SocketEntry>,
79 pub connections: Vec<ConnectionEntry>,
80 pub unix_sockets: Option<Vec<UnixSocketEntry>>,
82 pub traffic_rx_bytes_per_sec: Option<u64>,
85 pub traffic_tx_bytes_per_sec: Option<u64>,
87}
88
89#[derive(Debug, Serialize, Deserialize, Clone)]
90pub struct UnixSocketEntry {
91 pub path: String,
92}
93
94#[derive(Debug, Serialize, Deserialize, Clone)]
95pub struct SocketEntry {
96 pub protocol: String,
97 pub local_addr: String,
98 pub local_port: u16,
99}
100
101#[derive(Debug, Serialize, Deserialize, Clone)]
102pub struct ConnectionEntry {
103 pub protocol: String,
104 pub local_addr: String,
105 pub local_port: u16,
106 pub remote_addr: String,
107 pub remote_port: u16,
108 pub state: String,
109}
110
111#[derive(Debug, Serialize, Deserialize, Clone)]
114pub struct OpenFile {
115 pub fd: u32,
116 pub fd_type: String,
117 pub description: String,
118}
119
120#[derive(Debug, Serialize, Deserialize, Clone)]
123pub struct EnvVar {
124 pub key: String,
125 pub value: String,
126 pub redacted: bool,
127}
128
129#[derive(Debug, Serialize, Deserialize, Clone)]
132pub struct ProcessNode {
133 pub pid: i32,
134 pub name: String,
135 pub uid: u32,
136 pub rss_kb: u64,
137 pub children: Vec<ProcessNode>,
138}
139
140pub use resource_sampler::gpu::GpuInfo;
143
144pub use signal_engine::impact::SignalImpact;
146
147#[derive(Debug, Serialize, Deserialize, Clone)]
150pub struct FdLeakWarning {
151 pub start_count: usize,
153 pub end_count: usize,
155 pub consecutive_increases: usize,
157}
158
159#[derive(Debug, thiserror::Error)]
162pub enum PeekError {
163 #[error("process {0} not found")]
164 NotFound(i32),
165
166 #[error("failed to read /proc for pid {pid}: {source}")]
167 ProcIo {
168 pid: i32,
169 #[source]
170 source: std::io::Error,
171 },
172
173 #[error("failed to parse /proc for pid {pid}: {msg}")]
174 ProcParse { pid: i32, msg: String },
175}
176
177impl From<peek_proc_reader::ProcReaderError> for PeekError {
178 fn from(e: peek_proc_reader::ProcReaderError) -> Self {
179 let pid = e.pid().unwrap_or(-1);
180 match e {
181 peek_proc_reader::ProcReaderError::NotFound(pid) => PeekError::NotFound(pid),
182 peek_proc_reader::ProcReaderError::Io { source, .. } => {
183 PeekError::ProcIo { pid, source }
184 }
185 peek_proc_reader::ProcReaderError::Parse { msg, .. } => {
186 PeekError::ProcParse { pid, msg }
187 }
188 }
189 }
190}
191
192pub type Result<T> = std::result::Result<T, PeekError>;
193
194#[derive(Debug, Default, Clone)]
197pub struct CollectOptions {
198 pub resources: bool,
199 pub kernel: bool,
200 pub network: bool,
201 pub files: bool,
202 pub env: bool,
203 pub tree: bool,
204 pub gpu: bool,
205}
206
207pub fn collect(pid: i32) -> Result<ProcessInfo> {
211 proc::collect_process(pid, false)
212}
213
214pub fn collect_extended(pid: i32, opts: &CollectOptions) -> Result<ProcessInfo> {
216 let mut info = proc::collect_process(pid, opts.resources)?;
217
218 #[cfg(target_os = "linux")]
219 {
220 if opts.resources {
221 info.io_read_bytes = proc::resources::read_io(pid).map(|io| io.0).ok();
222 info.io_write_bytes = proc::resources::read_io(pid).map(|io| io.1).ok();
223 info.fd_count = proc::files::count_fds(pid).ok();
224 if let Some((_rss, pss, swap)) = resource_sampler::memory::sample_memory(pid) {
225 info.pss_kb = Some(pss);
226 info.swap_kb = Some(swap);
227 }
228 }
229 if opts.kernel {
230 info.kernel = proc::kernel::collect_kernel(pid).ok();
231 }
232 if opts.network {
233 info.network = proc::network::collect_network(pid).ok();
234 }
235 if opts.files {
236 info.open_files = proc::files::collect_files(pid).ok();
237 }
238 if opts.env {
239 info.env_vars = proc::env::collect_env(pid).ok();
240 }
241 if opts.tree {
242 info.process_tree = proc::tree::build_tree(pid).ok();
243 }
244 if opts.gpu {
245 info.gpu = Some(proc::gpu::collect_gpu(pid));
246 }
247 }
248
249 Ok(info)
250}
251
252pub fn signal_impact(_pid: i32) -> anyhow::Result<SignalImpact> {
254 #[cfg(target_os = "linux")]
255 {
256 signal_engine::impact::analyze_impact(_pid)
257 }
258 #[cfg(not(target_os = "linux"))]
259 {
260 anyhow::bail!("Signal impact analysis is only available on Linux")
261 }
262}
263
264pub fn binary_description(name: &str) -> Option<String> {
266 kernel_explainer::well_known::binary_description(name).map(|s| s.to_string())
267}
268
269pub fn oom_description(score: i32) -> &'static str {
271 kernel_explainer::oom::oom_description(score)
272}
273
274#[cfg(target_os = "linux")]
276pub fn fd_soft_limit(pid: i32) -> Option<u64> {
277 use peek_proc_reader::limits::read_limits;
278
279 let limits = read_limits(pid).ok()?;
280 limits.max_open_files_soft
281}
282
283#[cfg(not(target_os = "linux"))]
284pub fn fd_soft_limit(_pid: i32) -> Option<u64> {
285 None
286}
287
288#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
291pub fn current_syscall(pid: i32) -> Option<(String, String)> {
292 let (num, _) = peek_proc_reader::current::read_syscall(pid)?;
293 let name = kernel_explainer::syscalls::syscall_name_x86_64(num)?;
294 let desc = kernel_explainer::syscalls::syscall_description(name);
295 Some((name.to_string(), desc.to_string()))
296}
297
298#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
299pub fn current_syscall(_pid: i32) -> Option<(String, String)> {
300 None
301}
302
303pub fn resolve_remote(addr: &str) -> Option<String> {
305 network_inspector::resolver::resolve(addr)
306}