use std::collections::HashMap;
use crate::extensions::braille;
use crate::extensions::model::Proc;
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Sample {
pub cpu: f32,
pub mem_kb: u64,
}
#[derive(Clone, Debug)]
struct Ring {
cap: usize,
buf: Vec<Sample>,
}
impl Ring {
fn new(cap: usize) -> Self {
Ring {
cap,
buf: Vec::with_capacity(cap),
}
}
fn push(&mut self, s: Sample) {
if self.buf.len() == self.cap {
self.buf.remove(0);
}
self.buf.push(s);
}
}
pub struct ProcRing {
cap: usize,
map: HashMap<u32, Ring>,
}
impl ProcRing {
pub fn new(cap: usize) -> Self {
ProcRing {
cap: cap.max(1),
map: HashMap::new(),
}
}
pub fn record(&mut self, table: &[Proc]) {
let mut seen = Vec::with_capacity(table.len());
for p in table {
seen.push(p.pid);
self.map
.entry(p.pid)
.or_insert_with(|| Ring::new(self.cap))
.push(Sample {
cpu: p.cpu,
mem_kb: p.mem_kb,
});
}
self.map.retain(|pid, _| seen.contains(pid));
}
pub fn tracked(&self) -> usize {
self.map.len()
}
pub fn latest_cpu(&self, pid: u32) -> f32 {
self.map
.get(&pid)
.and_then(|r| r.buf.last())
.map(|s| s.cpu)
.unwrap_or(0.0)
}
pub fn cpu_series(&self, pid: u32) -> Vec<f32> {
self.map
.get(&pid)
.map(|r| r.buf.iter().map(|s| s.cpu).collect())
.unwrap_or_default()
}
pub fn mem_series(&self, pid: u32) -> Vec<u64> {
self.map
.get(&pid)
.map(|r| r.buf.iter().map(|s| s.mem_kb).collect())
.unwrap_or_default()
}
pub fn cpu_braille(
&self,
pid: u32,
width_cells: usize,
height_cells: usize,
max: f32,
) -> Vec<String> {
let series: Vec<f64> = self.cpu_series(pid).iter().map(|&v| v as f64).collect();
braille::graph_rows(&series, width_cells, height_cells, max as f64)
}
pub fn cpu_sparkline(&self, pid: u32, width: usize, max: f32) -> String {
let series = self.cpu_series(pid);
let tail = if series.len() > width {
&series[series.len() - width..]
} else {
&series[..]
};
let pad = width.saturating_sub(tail.len());
let mut out = " ".repeat(pad);
out.push_str(&braille::spark(tail, max));
out
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::extensions::model::synthetic_table;
#[test]
fn records_and_bounds_by_capacity() {
let mut r = ProcRing::new(3);
for t in 0..10 {
r.record(&synthetic_table(t));
}
assert_eq!(r.cpu_series(1).len(), 3);
}
#[test]
fn evicts_dead_pids() {
let mut r = ProcRing::new(8);
r.record(&synthetic_table(0)); assert!(r.cpu_series(500).len() == 1);
r.record(&synthetic_table(4)); assert!(r.cpu_series(500).is_empty());
}
#[test]
fn sparkline_is_exactly_width_and_right_aligned() {
let mut r = ProcRing::new(64);
for t in 0..3 {
r.record(&synthetic_table(t));
}
let s = r.cpu_sparkline(1, 10, 100.0);
assert_eq!(s.chars().count(), 10);
assert!(s.starts_with(" "));
}
}