use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::{CacheStore, PidNode, ProcInfo, TreeStore};
struct Entry<V> {
value: V,
inserted_at: Instant,
}
impl<V: Clone> Clone for Entry<V> {
fn clone(&self) -> Self {
Self {
value: self.value.clone(),
inserted_at: self.inserted_at,
}
}
}
type Inner<V> = Arc<Mutex<HashMap<u32, Entry<V>>>>;
fn get_inner<V: Clone>(inner: &Inner<V>, pid: u32, ttl: Duration) -> Option<V> {
let mut map = inner.lock().unwrap();
let entry = map.get(&pid)?;
if !ttl.is_zero() && entry.inserted_at.elapsed() >= ttl {
map.remove(&pid);
return None;
}
Some(entry.value.clone())
}
fn insert_inner<V: Clone>(inner: &Inner<V>, pid: u32, value: V) {
let mut map = inner.lock().unwrap();
map.insert(
pid,
Entry {
value,
inserted_at: Instant::now(),
},
);
}
fn len_inner<V>(inner: &Inner<V>) -> usize {
inner.lock().unwrap().len()
}
pub struct DefaultStore<V> {
inner: Inner<V>,
ttl: Duration,
}
pub type DefaultTree = DefaultStore<PidNode>;
pub type DefaultCache = DefaultStore<ProcInfo>;
impl<V: Clone> DefaultStore<V> {
pub fn new(_capacity: u64, ttl_secs: u64) -> Self {
Self {
inner: Arc::new(Mutex::new(HashMap::new())),
ttl: Duration::from_secs(ttl_secs),
}
}
pub fn len(&self) -> usize {
len_inner(&self.inner)
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn contains_key(&self, pid: u32) -> bool {
get_inner(&self.inner, pid, self.ttl).is_some()
}
}
impl<V: Clone> Clone for DefaultStore<V> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
ttl: self.ttl,
}
}
}
impl<V: Clone> Default for DefaultStore<V> {
fn default() -> Self {
Self::new(100, 0)
}
}
impl TreeStore for DefaultTree {
fn get_node(&self, pid: u32) -> Option<PidNode> {
get_inner(&self.inner, pid, self.ttl)
}
fn insert_node(&self, pid: u32, node: PidNode) {
insert_inner(&self.inner, pid, node);
}
fn all_pids(&self) -> Vec<u32> {
self.inner.lock().unwrap().keys().copied().collect()
}
}
impl CacheStore for DefaultCache {
fn get_info(&self, pid: u32) -> Option<ProcInfo> {
get_inner(&self.inner, pid, self.ttl)
}
fn insert_info(&self, pid: u32, info: ProcInfo) {
insert_inner(&self.inner, pid, info);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_tree_insert_get() {
let tree = DefaultTree::new(100, 0);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
let node = tree.get_node(1).unwrap();
assert_eq!(node.ppid, 0);
assert_eq!(node.cmd, "init");
}
#[test]
fn default_tree_ttl_expired() {
let tree = DefaultTree::new(100, 0); tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
assert!(tree.get_node(1).is_some());
let tree = DefaultTree::new(100, 1);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
assert!(tree.get_node(1).is_some());
std::thread::sleep(Duration::from_millis(1100));
assert!(tree.get_node(1).is_none());
}
#[test]
fn default_cache_insert_get() {
let cache = DefaultCache::new(100, 0);
cache.insert_info(
42,
ProcInfo {
cmd: "bash".into(),
user: "root".into(),
ppid: 1,
tgid: 42,
start_time_ns: 0,
},
);
let info = cache.get_info(42).unwrap();
assert_eq!(info.cmd, "bash");
assert_eq!(info.ppid, 1);
}
#[test]
fn clone_shares_data() {
let tree = DefaultTree::new(100, 0);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
let tree2 = tree.clone();
assert!(tree2.get_node(1).is_some());
tree2.insert_node(
2,
PidNode {
ppid: 1,
cmd: "bash".into(),
},
);
assert!(tree.get_node(2).is_some());
}
#[test]
fn len_and_contains() {
let cache = DefaultCache::new(100, 0);
assert_eq!(cache.len(), 0);
cache.insert_info(
1,
ProcInfo {
cmd: "a".into(),
user: "u".into(),
ppid: 0,
tgid: 1,
start_time_ns: 0,
},
);
assert_eq!(cache.len(), 1);
assert!(cache.contains_key(1));
assert!(!cache.contains_key(999));
}
#[test]
fn default_cache_ttl_expired() {
let cache = DefaultCache::new(100, 0);
cache.insert_info(
1,
ProcInfo {
cmd: "a".into(),
user: "u".into(),
ppid: 0,
tgid: 1,
start_time_ns: 0,
},
);
assert!(cache.get_info(1).is_some());
let cache = DefaultCache::new(100, 1);
cache.insert_info(
1,
ProcInfo {
cmd: "a".into(),
user: "u".into(),
ppid: 0,
tgid: 1,
start_time_ns: 0,
},
);
assert!(cache.get_info(1).is_some());
std::thread::sleep(Duration::from_millis(1100));
assert!(cache.get_info(1).is_none());
}
#[test]
fn is_empty_default() {
let tree = DefaultTree::new(100, 0);
assert!(tree.is_empty());
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "init".into(),
},
);
assert!(!tree.is_empty());
let cache = DefaultCache::new(100, 0);
assert!(cache.is_empty());
cache.insert_info(
1,
ProcInfo {
cmd: "a".into(),
user: "u".into(),
ppid: 0,
tgid: 1,
start_time_ns: 0,
},
);
assert!(!cache.is_empty());
}
#[test]
fn all_pids_returns_keys() {
let tree = DefaultTree::new(100, 0);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "a".into(),
},
);
tree.insert_node(
2,
PidNode {
ppid: 1,
cmd: "b".into(),
},
);
tree.insert_node(
3,
PidNode {
ppid: 1,
cmd: "c".into(),
},
);
let mut pids = tree.all_pids();
pids.sort();
assert_eq!(pids, vec![1, 2, 3]);
}
#[test]
fn tree_ttl_contains_key_expires() {
let tree = DefaultTree::new(100, 1);
tree.insert_node(
1,
PidNode {
ppid: 0,
cmd: "a".into(),
},
);
assert!(tree.contains_key(1));
std::thread::sleep(Duration::from_millis(1100));
assert!(!tree.contains_key(1));
}
}