procinsh 0.1.0

A local, read-only Linux x86-64 process inspector
use super::procfs::{self, Stat};
use anyhow::Result;
use serde::Serialize;
use std::fs;

#[derive(Clone, Debug, Serialize)]
pub struct ThreadObservation {
    pub tid: i32,
    pub name: String,
    pub state: String,
    pub cpu: i32,
    pub cpu_percent: Option<f64>,
    pub priority: i64,
    pub nice: i64,
    pub scheduler: String,
    pub affinity: Option<String>,
    pub voluntary_context_switches: Option<u64>,
    pub nonvoluntary_context_switches: Option<u64>,
    #[serde(skip)]
    pub ticks: u64,
    #[serde(skip)]
    pub start_time: u64,
}

pub fn tids(pid: i32) -> Result<Vec<i32>> {
    let mut tids: Vec<_> = fs::read_dir(format!("/proc/{pid}/task"))?
        .flatten()
        .filter_map(|e| e.file_name().to_str()?.parse().ok())
        .collect();
    tids.sort_unstable();
    Ok(tids)
}

pub fn read(pid: i32, tid: i32) -> Result<ThreadObservation> {
    let path = format!("/proc/{pid}/task/{tid}");
    let s: Stat = procfs::read_stat(&format!("{path}/stat"))?;
    let f = procfs::fields(&format!("{path}/status")).unwrap_or_default();
    Ok(ThreadObservation {
        tid,
        name: s.name,
        state: s.state,
        cpu: s.cpu,
        cpu_percent: None,
        priority: s.priority,
        nice: s.nice,
        scheduler: match s.policy {
            0 => "OTHER",
            1 => "FIFO",
            2 => "RR",
            3 => "BATCH",
            5 => "IDLE",
            6 => "DEADLINE",
            7 => "EXT",
            _ => "UNKNOWN",
        }
        .into(),
        affinity: f.get("Cpus_allowed_list").cloned(),
        voluntary_context_switches: procfs::field_u64(&f, "voluntary_ctxt_switches"),
        nonvoluntary_context_switches: procfs::field_u64(&f, "nonvoluntary_ctxt_switches"),
        ticks: s.ticks,
        start_time: s.start_time,
    })
}