use parking_lot::RwLock;
use std::collections::HashMap;
use std::time::{Duration, Instant};
const CHILD_CACHE_OFFSET: u32 = 1_000_000_000;
const ENV_CACHE_OFFSET: u32 = 2_000_000_000;
#[derive(Debug, Clone)]
pub struct ProcessInfo {
pub cmdline: String,
pub last_update: Instant,
}
pub struct ProcessCache {
cache: RwLock<HashMap<u32, ProcessInfo>>,
ttl: Duration,
}
impl ProcessCache {
pub fn new() -> Self {
Self {
cache: RwLock::new(HashMap::new()),
ttl: Duration::from_secs(5),
}
}
pub fn with_ttl(ttl: Duration) -> Self {
Self {
cache: RwLock::new(HashMap::new()),
ttl,
}
}
pub fn get_cmdline(&self, pid: u32) -> Option<String> {
{
let cache = self.cache.read();
if let Some(info) = cache.get(&pid) {
if info.last_update.elapsed() < self.ttl {
return Some(info.cmdline.clone());
}
}
}
let cmdline = self.read_cmdline(pid)?;
{
let mut cache = self.cache.write();
cache.insert(
pid,
ProcessInfo {
cmdline: cmdline.clone(),
last_update: Instant::now(),
},
);
}
Some(cmdline)
}
fn read_cmdline(&self, pid: u32) -> Option<String> {
let path = format!("/proc/{}/cmdline", pid);
std::fs::read_to_string(&path)
.ok()
.map(|s| s.replace('\0', " ").trim().to_string())
}
pub fn get_child_cmdline(&self, pid: u32) -> Option<String> {
let cache_key = pid + CHILD_CACHE_OFFSET;
{
let cache = self.cache.read();
if let Some(info) = cache.get(&cache_key) {
if info.last_update.elapsed() < self.ttl {
return Some(info.cmdline.clone());
}
}
}
let children_path = format!("/proc/{}/task/{}/children", pid, pid);
let children = std::fs::read_to_string(&children_path).ok()?;
let child_pid: u32 = children.split_whitespace().next()?.parse().ok()?;
let cmdline = self.read_cmdline(child_pid)?;
{
let mut cache = self.cache.write();
cache.insert(
cache_key,
ProcessInfo {
cmdline: cmdline.clone(),
last_update: Instant::now(),
},
);
}
Some(cmdline)
}
pub fn cleanup(&self) {
let mut cache = self.cache.write();
cache.retain(|_, info| info.last_update.elapsed() < self.ttl);
}
pub fn get_env_var(&self, pid: u32, var_name: &str) -> Option<String> {
let _cache_key = pid + ENV_CACHE_OFFSET;
let environ_path = format!("/proc/{}/environ", pid);
let content = std::fs::read(&environ_path).ok()?;
let prefix = format!("{}=", var_name);
for entry in content.split(|&b| b == 0) {
if let Ok(entry_str) = std::str::from_utf8(entry) {
if let Some(value) = entry_str.strip_prefix(&prefix) {
return Some(value.to_string());
}
}
}
None
}
pub fn clear(&self) {
let mut cache = self.cache.write();
cache.clear();
}
pub fn len(&self) -> usize {
self.cache.read().len()
}
pub fn is_empty(&self) -> bool {
self.cache.read().is_empty()
}
}
impl Default for ProcessCache {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_cache_creation() {
let cache = ProcessCache::new();
assert!(cache.is_empty());
}
#[test]
fn test_cache_with_ttl() {
let cache = ProcessCache::with_ttl(Duration::from_secs(10));
assert!(cache.is_empty());
}
#[test]
fn test_cache_clear() {
let cache = ProcessCache::new();
cache.clear();
assert!(cache.is_empty());
}
}