use std::sync::Mutex;
use sysinfo::System;
use crate::payload::{Body, EntriesData, Payload, RatioData, TextData};
use crate::render::Shape;
use crate::samples;
use super::super::{FetchContext, RealtimeFetcher, Safety};
use super::{entry, format_bytes, payload, ratio_of};
pub struct SystemMonitorMemory {
state: Mutex<System>,
}
impl SystemMonitorMemory {
pub fn new() -> Self {
let mut sys = System::new();
sys.refresh_memory();
Self {
state: Mutex::new(sys),
}
}
}
impl Default for SystemMonitorMemory {
fn default() -> Self {
Self::new()
}
}
impl RealtimeFetcher for SystemMonitorMemory {
fn name(&self) -> &str {
"system_monitor_memory"
}
fn safety(&self) -> Safety {
Safety::Safe
}
fn description(&self) -> &'static str {
"RAM utilisation as a used/total ratio. `Text` formats as `\"6.4 GiB / 16 GiB\"` and `Entries` breaks it into used / total / free rows."
}
fn shapes(&self) -> &[Shape] {
&[Shape::Ratio, Shape::Text, Shape::Entries]
}
fn sample_body(&self, shape: Shape) -> Option<Body> {
Some(match shape {
Shape::Ratio => samples::ratio(0.67, "memory"),
Shape::Text => samples::text("6.4 GiB / 16 GiB"),
Shape::Entries => samples::entries(&[
("used", "6.4 GiB"),
("total", "16 GiB"),
("free", "9.6 GiB"),
]),
_ => return None,
})
}
fn compute(&self, ctx: &FetchContext) -> Payload {
let mut sys = self.state.lock().unwrap_or_else(|e| e.into_inner());
sys.refresh_memory();
let total = sys.total_memory();
let used = sys.used_memory();
let ratio = ratio_of(used, total);
let label = format!("{} / {}", format_bytes(used), format_bytes(total));
match ctx.shape.unwrap_or(Shape::Ratio) {
Shape::Text => payload(Body::Text(TextData { value: label })),
Shape::Entries => payload(Body::Entries(EntriesData {
items: vec![
entry("used", &format_bytes(used)),
entry("total", &format_bytes(total)),
entry("free", &format_bytes(total.saturating_sub(used))),
],
})),
_ => payload(Body::Ratio(RatioData {
value: ratio,
label: Some(label),
denominator: None,
})),
}
}
}
#[cfg(test)]
mod tests {
use super::super::test_helpers::ctx_with_shape;
use super::*;
#[test]
fn memory_defaults_to_ratio() {
let p = SystemMonitorMemory::new().compute(&ctx_with_shape(None));
assert!(matches!(p.body, Body::Ratio(_)));
if let Body::Ratio(r) = p.body {
assert!((0.0..=1.0).contains(&r.value));
}
}
#[test]
fn memory_entries_shape_has_three_rows() {
let p = SystemMonitorMemory::new().compute(&ctx_with_shape(Some(Shape::Entries)));
assert!(matches!(p.body, Body::Entries(_)));
if let Body::Entries(e) = p.body {
let keys: Vec<_> = e.items.iter().map(|i| i.key.as_str()).collect();
assert_eq!(keys, ["used", "total", "free"]);
}
}
#[test]
fn memory_text_shape_formats_used_over_total() {
let p = SystemMonitorMemory::new().compute(&ctx_with_shape(Some(Shape::Text)));
assert!(matches!(p.body, Body::Text(_)));
if let Body::Text(text) = p.body {
assert!(text.value.contains(" / "));
}
}
}