use std::collections::HashMap;
use sysinfo::System;
pub struct ProcessPageRank {
scores: HashMap<u32, f64>,
outlinks: HashMap<u32, Vec<u32>>,
inlinks: HashMap<u32, Vec<u32>>,
damping: f64,
epsilon: f64,
max_iterations: usize,
}
impl ProcessPageRank {
pub fn new() -> Self {
Self {
scores: HashMap::new(),
outlinks: HashMap::new(),
inlinks: HashMap::new(),
damping: 0.85,
epsilon: 1e-6,
max_iterations: 100,
}
}
pub fn compute(&mut self, system: &System) {
self.outlinks.clear();
self.inlinks.clear();
self.scores.clear();
let processes: Vec<u32> = system.processes().keys().map(|p| p.as_u32()).collect();
let n = processes.len();
if n == 0 {
return;
}
for (pid, process) in system.processes() {
let pid_u32 = pid.as_u32();
if let Some(parent_pid) = process.parent() {
let parent_u32 = parent_pid.as_u32();
self.outlinks
.entry(parent_u32)
.or_default()
.push(pid_u32);
self.inlinks
.entry(pid_u32)
.or_default()
.push(parent_u32);
}
}
let out_degrees: HashMap<u32, f64> = self.outlinks
.iter()
.map(|(&k, v)| (k, v.len() as f64))
.collect();
let dangling_nodes: Vec<u32> = processes.iter()
.filter(|p| !self.outlinks.contains_key(p))
.copied()
.collect();
let initial_score = 1.0 / n as f64;
for &pid in &processes {
self.scores.insert(pid, initial_score);
}
let n_f64 = n as f64;
for iteration in 0..self.max_iterations {
let mut new_scores: HashMap<u32, f64> = HashMap::with_capacity(n);
let mut max_delta = 0.0f64;
let dangling_sum: f64 = dangling_nodes.iter()
.map(|p| self.scores.get(p).unwrap_or(&0.0))
.sum();
let dangling_contrib = self.damping * dangling_sum / n_f64;
let teleport = (1.0 - self.damping) / n_f64;
let base_score = teleport + dangling_contrib;
for &pid in &processes {
let mut score = base_score;
if let Some(sources) = self.inlinks.get(&pid) {
for &source in sources {
let source_score = self.scores.get(&source).unwrap_or(&0.0);
let out_deg = out_degrees.get(&source).unwrap_or(&1.0);
score += self.damping * source_score / out_deg;
}
}
let old_score = self.scores.get(&pid).unwrap_or(&0.0);
max_delta = max_delta.max((score - old_score).abs());
new_scores.insert(pid, score);
}
self.scores = new_scores;
if max_delta < self.epsilon {
tracing::debug!("PageRank converged after {} iterations", iteration + 1);
break;
}
}
let total: f64 = self.scores.values().sum();
if total > 0.0 {
for score in self.scores.values_mut() {
*score /= total;
}
}
}
pub fn get_score(&self, pid: u32) -> f64 {
*self.scores.get(&pid).unwrap_or(&0.0)
}
pub fn get_trim_candidates(&self, limit: usize) -> Vec<(u32, f64)> {
let mut ranked: Vec<(u32, f64)> = self.scores.iter().map(|(&k, &v)| (k, v)).collect();
ranked.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(std::cmp::Ordering::Equal));
ranked.into_iter().take(limit).collect()
}
pub fn get_critical_processes(&self, limit: usize) -> Vec<(u32, f64)> {
let mut ranked: Vec<(u32, f64)> = self.scores.iter().map(|(&k, &v)| (k, v)).collect();
ranked.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
ranked.into_iter().take(limit).collect()
}
pub fn stats(&self) -> PageRankStats {
let scores: Vec<f64> = self.scores.values().copied().collect();
let n = scores.len();
if n == 0 {
return PageRankStats::default();
}
let sum: f64 = scores.iter().sum();
let mean = sum / n as f64;
let variance: f64 = scores.iter().map(|s| (s - mean).powi(2)).sum::<f64>() / n as f64;
let max = scores.iter().cloned().fold(0.0f64, f64::max);
let min = scores.iter().cloned().fold(1.0f64, f64::min);
PageRankStats {
process_count: n,
mean_score: mean,
std_dev: variance.sqrt(),
max_score: max,
min_score: min,
iterations_to_converge: self.max_iterations, }
}
pub fn get_weighted_candidates(
&self,
system: &System,
memory_weight: f64,
rank_weight: f64,
limit: usize,
) -> Vec<(u32, f64)> {
let mut candidates: Vec<(u32, f64)> = Vec::new();
let max_memory = system
.processes()
.values()
.map(|p| p.memory())
.max()
.unwrap_or(1) as f64;
for (pid, process) in system.processes() {
let pid_u32 = pid.as_u32();
let rank_score = self.get_score(pid_u32);
let memory_score = process.memory() as f64 / max_memory;
let combined = memory_weight * memory_score + rank_weight * (1.0 - rank_score);
candidates.push((pid_u32, combined));
}
candidates.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap_or(std::cmp::Ordering::Equal));
candidates.into_iter().take(limit).collect()
}
}
impl Default for ProcessPageRank {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Default)]
pub struct PageRankStats {
pub process_count: usize,
pub mean_score: f64,
pub std_dev: f64,
pub max_score: f64,
pub min_score: f64,
pub iterations_to_converge: usize,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pagerank_basic() {
let pagerank = ProcessPageRank::new();
let stats = pagerank.stats();
assert_eq!(stats.process_count, 0);
}
}