1use std::collections::HashMap;
15use std::sync::{Arc, Mutex};
16use std::time::{Duration, Instant};
17
18use crate::traits::ProcessStore;
19use crate::types::ProcessInfo;
20
21struct Entry {
24 value: ProcessInfo,
25 inserted_at: Instant,
26}
27
28impl Clone for Entry {
29 fn clone(&self) -> Self {
30 Self {
31 value: self.value.clone(),
32 inserted_at: self.inserted_at,
33 }
34 }
35}
36
37type Inner = Arc<Mutex<HashMap<u32, Entry>>>;
40
41fn get_inner(
42 inner: &Inner,
43 pid: u32,
44 ttl: Duration,
45 children_index: &Arc<Mutex<HashMap<u32, Vec<u32>>>>,
46) -> Option<ProcessInfo> {
47 let mut map = inner.lock().unwrap();
48 let entry = map.get(&pid)?;
49 if !ttl.is_zero() && entry.inserted_at.elapsed() >= ttl {
50 let info = map.remove(&pid).unwrap().value;
51 let mut index = children_index.lock().unwrap();
53 if let Some(children) = index.get_mut(&info.ppid) {
54 children.retain(|&c| c != pid);
55 }
56 index.remove(&pid);
58 return None;
59 }
60 Some(entry.value.clone())
61}
62
63fn insert_inner(inner: &Inner, pid: u32, value: ProcessInfo) {
64 let mut map = inner.lock().unwrap();
65 map.insert(
66 pid,
67 Entry {
68 value,
69 inserted_at: Instant::now(),
70 },
71 );
72}
73
74pub struct DefaultStore {
80 inner: Inner,
81 children_index: Arc<Mutex<HashMap<u32, Vec<u32>>>>,
82 ttl: Duration,
83}
84
85impl DefaultStore {
86 pub fn new(ttl_secs: u64) -> Self {
89 Self {
90 inner: Arc::new(Mutex::new(HashMap::new())),
91 children_index: Arc::new(Mutex::new(HashMap::new())),
92 ttl: Duration::from_secs(ttl_secs),
93 }
94 }
95
96 pub fn len(&self) -> usize {
98 self.inner.lock().unwrap().len()
99 }
100
101 pub fn is_empty(&self) -> bool {
103 self.len() == 0
104 }
105
106 pub fn contains_key(&self, pid: u32) -> bool {
111 self.inner.lock().unwrap().contains_key(&pid)
112 }
113}
114
115impl Clone for DefaultStore {
116 fn clone(&self) -> Self {
117 Self {
118 inner: Arc::clone(&self.inner),
119 children_index: Arc::clone(&self.children_index),
120 ttl: self.ttl,
121 }
122 }
123}
124
125impl Default for DefaultStore {
126 fn default() -> Self {
128 Self::new(0)
129 }
130}
131
132impl ProcessStore for DefaultStore {
133 fn get_process(&self, pid: u32) -> Option<ProcessInfo> {
134 get_inner(&self.inner, pid, self.ttl, &self.children_index)
135 }
136
137 fn insert_process(&self, pid: u32, info: ProcessInfo) {
138 let new_ppid = info.ppid;
139
140 let old_ppid = {
142 let map = self.inner.lock().unwrap();
143 map.get(&pid).map(|e| e.value.ppid)
144 };
145
146 insert_inner(&self.inner, pid, info);
148
149 let mut index = self.children_index.lock().unwrap();
151
152 if let Some(old_ppid) = old_ppid
154 && old_ppid != new_ppid
155 && let Some(children) = index.get_mut(&old_ppid)
156 {
157 children.retain(|&c| c != pid);
158 }
159
160 let children = index.entry(new_ppid).or_default();
162 if !children.contains(&pid) {
163 children.push(pid);
164 }
165 }
166
167 fn remove_process(&self, pid: u32) -> Option<ProcessInfo> {
168 let info = {
170 let mut map = self.inner.lock().unwrap();
171 map.remove(&pid).map(|e| e.value)
172 };
173
174 if let Some(ref p) = info {
176 let mut index = self.children_index.lock().unwrap();
177 if let Some(children) = index.get_mut(&p.ppid) {
179 children.retain(|&c| c != pid);
180 }
181 index.remove(&pid);
183 }
184
185 info
186 }
187
188 fn all_pids(&self) -> Vec<u32> {
189 self.inner.lock().unwrap().keys().copied().collect()
190 }
191
192 fn children_of(&self, pid: u32) -> Vec<u32> {
193 self.children_index
195 .lock()
196 .unwrap()
197 .get(&pid)
198 .cloned()
199 .unwrap_or_default()
200 }
201}
202
203#[cfg(test)]
204mod tests {
205 use super::*;
206
207 #[test]
208 fn default_store_insert_get() {
209 let store = DefaultStore::new(0);
210 store.insert_process(
211 1,
212 ProcessInfo {
213 ppid: 0,
214 cmd: "init".into(),
215 user: "root".into(),
216 tgid: 1,
217 start_time_ns: 0,
218 },
219 );
220 let info = store.get_process(1).unwrap();
221 assert_eq!(info.ppid, 0);
222 assert_eq!(info.cmd, "init");
223 }
224
225 #[test]
226 fn default_store_ttl_expired() {
227 let store = DefaultStore::new(0); store.insert_process(
229 1,
230 ProcessInfo {
231 ppid: 0,
232 cmd: "init".into(),
233 user: "root".into(),
234 tgid: 1,
235 start_time_ns: 0,
236 },
237 );
238 assert!(store.get_process(1).is_some());
239
240 let store = DefaultStore::new(1);
242 store.insert_process(
243 1,
244 ProcessInfo {
245 ppid: 0,
246 cmd: "init".into(),
247 user: "root".into(),
248 tgid: 1,
249 start_time_ns: 0,
250 },
251 );
252 assert!(store.get_process(1).is_some());
253 std::thread::sleep(Duration::from_millis(1100));
254 assert!(store.get_process(1).is_none());
255 }
256
257 #[test]
258 fn clone_shares_data() {
259 let store = DefaultStore::new(0);
260 store.insert_process(
261 1,
262 ProcessInfo {
263 ppid: 0,
264 cmd: "init".into(),
265 user: "root".into(),
266 tgid: 1,
267 start_time_ns: 0,
268 },
269 );
270 let store2 = store.clone();
271 assert!(store2.get_process(1).is_some());
272 store2.insert_process(
273 2,
274 ProcessInfo {
275 ppid: 1,
276 cmd: "bash".into(),
277 user: "root".into(),
278 tgid: 2,
279 start_time_ns: 0,
280 },
281 );
282 assert!(store.get_process(2).is_some());
283 }
284
285 #[test]
286 fn len_and_contains() {
287 let store = DefaultStore::new(0);
288 assert_eq!(store.len(), 0);
289 store.insert_process(
290 1,
291 ProcessInfo {
292 ppid: 0,
293 cmd: "a".into(),
294 user: "u".into(),
295 tgid: 1,
296 start_time_ns: 0,
297 },
298 );
299 assert_eq!(store.len(), 1);
300 assert!(store.contains_key(1));
301 assert!(!store.contains_key(999));
302 }
303
304 #[test]
305 fn is_empty_default() {
306 let store = DefaultStore::new(0);
307 assert!(store.is_empty());
308 store.insert_process(
309 1,
310 ProcessInfo {
311 ppid: 0,
312 cmd: "a".into(),
313 user: "u".into(),
314 tgid: 1,
315 start_time_ns: 0,
316 },
317 );
318 assert!(!store.is_empty());
319 }
320
321 #[test]
322 fn all_pids_returns_keys() {
323 let store = DefaultStore::new(0);
324 store.insert_process(
325 1,
326 ProcessInfo {
327 ppid: 0,
328 cmd: "a".into(),
329 user: "u".into(),
330 tgid: 1,
331 start_time_ns: 0,
332 },
333 );
334 store.insert_process(
335 2,
336 ProcessInfo {
337 ppid: 1,
338 cmd: "b".into(),
339 user: "u".into(),
340 tgid: 2,
341 start_time_ns: 0,
342 },
343 );
344 store.insert_process(
345 3,
346 ProcessInfo {
347 ppid: 1,
348 cmd: "c".into(),
349 user: "u".into(),
350 tgid: 3,
351 start_time_ns: 0,
352 },
353 );
354 let mut pids = store.all_pids();
355 pids.sort();
356 assert_eq!(pids, vec![1, 2, 3]);
357 }
358
359 #[test]
360 fn ttl_contains_key_does_not_expire() {
361 let store = DefaultStore::new(1);
362 store.insert_process(
363 1,
364 ProcessInfo {
365 ppid: 0,
366 cmd: "a".into(),
367 user: "u".into(),
368 tgid: 1,
369 start_time_ns: 0,
370 },
371 );
372 assert!(store.contains_key(1));
373 std::thread::sleep(Duration::from_millis(1100));
374 assert!(store.contains_key(1));
376 assert!(store.get_process(1).is_none());
378 }
379
380 #[test]
381 fn remove_process() {
382 let store = DefaultStore::new(0);
383 store.insert_process(
384 1,
385 ProcessInfo {
386 ppid: 0,
387 cmd: "init".into(),
388 user: "root".into(),
389 tgid: 1,
390 start_time_ns: 0,
391 },
392 );
393 store.insert_process(
394 2,
395 ProcessInfo {
396 ppid: 1,
397 cmd: "bash".into(),
398 user: "root".into(),
399 tgid: 2,
400 start_time_ns: 0,
401 },
402 );
403
404 assert_eq!(store.len(), 2);
405 assert!(store.contains_key(2));
406
407 let removed = store.remove_process(2);
408 assert!(removed.is_some());
409 assert_eq!(store.len(), 1);
410 assert!(!store.contains_key(2));
411 }
412
413 #[test]
414 fn children_of() {
415 let store = DefaultStore::new(0);
416 store.insert_process(
417 1,
418 ProcessInfo {
419 ppid: 0,
420 cmd: "init".into(),
421 user: "root".into(),
422 tgid: 1,
423 start_time_ns: 0,
424 },
425 );
426 store.insert_process(
427 100,
428 ProcessInfo {
429 ppid: 1,
430 cmd: "a".into(),
431 user: "root".into(),
432 tgid: 100,
433 start_time_ns: 0,
434 },
435 );
436 store.insert_process(
437 200,
438 ProcessInfo {
439 ppid: 1,
440 cmd: "b".into(),
441 user: "root".into(),
442 tgid: 200,
443 start_time_ns: 0,
444 },
445 );
446 store.insert_process(
447 300,
448 ProcessInfo {
449 ppid: 100,
450 cmd: "c".into(),
451 user: "root".into(),
452 tgid: 300,
453 start_time_ns: 0,
454 },
455 );
456
457 let mut kids = store.children_of(1);
458 kids.sort();
459 assert_eq!(kids, vec![100, 200]);
460
461 let kids_100 = store.children_of(100);
462 assert_eq!(kids_100, vec![300]);
463
464 assert!(store.children_of(999).is_empty());
465 }
466
467 #[test]
468 fn insert_ppid_change_removes_from_old_parent() {
469 let store = DefaultStore::new(0);
470 store.insert_process(
471 1,
472 ProcessInfo {
473 ppid: 0,
474 cmd: "init".into(),
475 user: "root".into(),
476 tgid: 1,
477 start_time_ns: 0,
478 },
479 );
480 store.insert_process(
481 100,
482 ProcessInfo {
483 ppid: 0,
484 cmd: "other".into(),
485 user: "root".into(),
486 tgid: 100,
487 start_time_ns: 0,
488 },
489 );
490 store.insert_process(
491 200,
492 ProcessInfo {
493 ppid: 100,
494 cmd: "child".into(),
495 user: "root".into(),
496 tgid: 200,
497 start_time_ns: 0,
498 },
499 );
500
501 assert_eq!(store.children_of(100), vec![200]);
503 assert!(store.children_of(1).is_empty());
504
505 store.insert_process(
507 200,
508 ProcessInfo {
509 ppid: 1,
510 cmd: "child".into(),
511 user: "root".into(),
512 tgid: 200,
513 start_time_ns: 0,
514 },
515 );
516
517 assert!(
519 store.children_of(100).is_empty(),
520 "old parent should have no children"
521 );
522 assert_eq!(store.children_of(1), vec![200]);
524 }
525
526 #[test]
527 fn insert_same_ppid_no_duplicate() {
528 let store = DefaultStore::new(0);
529 store.insert_process(
530 1,
531 ProcessInfo {
532 ppid: 0,
533 cmd: "init".into(),
534 user: "root".into(),
535 tgid: 1,
536 start_time_ns: 0,
537 },
538 );
539 store.insert_process(
540 100,
541 ProcessInfo {
542 ppid: 1,
543 cmd: "a".into(),
544 user: "root".into(),
545 tgid: 100,
546 start_time_ns: 0,
547 },
548 );
549 store.insert_process(
551 100,
552 ProcessInfo {
553 ppid: 1,
554 cmd: "a".into(),
555 user: "root".into(),
556 tgid: 100,
557 start_time_ns: 0,
558 },
559 );
560 assert_eq!(store.children_of(1), vec![100]);
562 }
563
564 #[test]
565 fn remove_process_cleans_own_children_index() {
566 let store = DefaultStore::new(0);
567 store.insert_process(
568 1,
569 ProcessInfo {
570 ppid: 0,
571 cmd: "init".into(),
572 user: "root".into(),
573 tgid: 1,
574 start_time_ns: 0,
575 },
576 );
577 store.insert_process(
578 100,
579 ProcessInfo {
580 ppid: 1,
581 cmd: "parent".into(),
582 user: "root".into(),
583 tgid: 100,
584 start_time_ns: 0,
585 },
586 );
587 store.insert_process(
588 200,
589 ProcessInfo {
590 ppid: 100,
591 cmd: "child".into(),
592 user: "root".into(),
593 tgid: 200,
594 start_time_ns: 0,
595 },
596 );
597
598 assert_eq!(store.children_of(100), vec![200]);
599
600 store.remove_process(100);
602
603 assert!(
605 store.children_of(100).is_empty(),
606 "removed process should have no children index"
607 );
608 assert!(store.get_process(200).is_some());
610 }
611
612 #[test]
613 fn ttl_expiration_cleans_own_children_index() {
614 let store = DefaultStore::new(1); store.insert_process(
616 1,
617 ProcessInfo {
618 ppid: 0,
619 cmd: "init".into(),
620 user: "root".into(),
621 tgid: 1,
622 start_time_ns: 0,
623 },
624 );
625 store.insert_process(
626 100,
627 ProcessInfo {
628 ppid: 1,
629 cmd: "parent".into(),
630 user: "root".into(),
631 tgid: 100,
632 start_time_ns: 0,
633 },
634 );
635 store.insert_process(
636 200,
637 ProcessInfo {
638 ppid: 100,
639 cmd: "child".into(),
640 user: "root".into(),
641 tgid: 200,
642 start_time_ns: 0,
643 },
644 );
645
646 assert_eq!(store.children_of(100), vec![200]);
647
648 std::thread::sleep(Duration::from_millis(1100));
650
651 assert!(store.get_process(100).is_none());
653
654 assert!(
656 store.children_of(100).is_empty(),
657 "expired process should have no children index"
658 );
659 }
660}