use std::{
cmp::Ordering,
time::{Duration, Instant},
vec::Vec,
};
use rustc_hash::{FxHashMap as HashMap, FxHashSet as HashSet};
use timeless::data::ChunkedData;
use crate::{
app::{AppConfigFields, DataFilters, filter::Filter, layout_manager::UsedWidgets},
collection::Data,
widgets::DiskWidgetData,
};
pub type Values = ChunkedData<f64>;
#[derive(Clone, Debug, Default)]
pub struct TimeSeriesData {
pub time: Vec<Instant>,
pub rx: Values,
pub tx: Values,
pub cpu: Vec<Values>,
pub ram: Values,
pub swap: Values,
#[cfg(not(target_os = "windows"))]
pub cache_mem: Values,
#[cfg(feature = "zfs")]
pub arc_mem: Values,
#[cfg(feature = "gpu")]
pub gpu_mem: HashMap<String, Values>,
pub temperature: HashMap<String, ChunkedData<f32>>,
pub disk_io_read: HashMap<String, ChunkedData<f64>>,
pub disk_io_write: HashMap<String, ChunkedData<f64>>,
}
impl TimeSeriesData {
pub fn add(
&mut self, data: &Data, used_widgets: &UsedWidgets, settings: &AppConfigFields,
filters: &DataFilters,
) {
self.time.push(data.collection_time);
if let Some(network) = &data.network {
self.rx.push(network.rx as f64);
self.tx.push(network.tx as f64);
} else {
self.rx.insert_break();
self.tx.insert_break();
}
if let Some(cpu) = &data.cpu {
match self.cpu.len().cmp(&cpu.len()) {
Ordering::Less => {
let diff = cpu.len() - self.cpu.len();
self.cpu.reserve_exact(diff);
for _ in 0..diff {
self.cpu.push(Default::default());
}
}
Ordering::Greater => {
let diff = self.cpu.len() - cpu.len();
let offset = self.cpu.len() - diff;
for curr in &mut self.cpu[offset..] {
curr.insert_break();
}
}
Ordering::Equal => {}
}
for (curr, new_data) in self.cpu.iter_mut().zip(cpu.iter()) {
curr.push(new_data.usage.into());
}
} else {
for c in &mut self.cpu {
c.insert_break();
}
}
if let Some(memory) = &data.memory {
self.ram.push(memory.percentage());
} else {
self.ram.insert_break();
}
if let Some(swap) = &data.swap {
self.swap.push(swap.percentage());
} else {
self.swap.insert_break();
}
#[cfg(not(target_os = "windows"))]
{
if let Some(cache) = &data.cache {
self.cache_mem.push(cache.percentage());
} else {
self.cache_mem.insert_break();
}
}
#[cfg(feature = "zfs")]
{
if let Some(arc) = &data.arc {
self.arc_mem.push(arc.percentage());
} else {
self.arc_mem.insert_break();
}
}
#[cfg(feature = "gpu")]
{
if let Some(gpu) = &data.gpu {
let mut not_visited = self
.gpu_mem
.keys()
.map(String::to_owned)
.collect::<HashSet<_>>();
for (name, new_data) in gpu {
not_visited.remove(name);
if !self.gpu_mem.contains_key(name) {
self.gpu_mem
.insert(name.to_string(), ChunkedData::default());
}
let curr = self
.gpu_mem
.get_mut(name)
.expect("entry must exist as it was created above");
curr.push(new_data.percentage());
}
for nv in not_visited {
if let Some(entry) = self.gpu_mem.get_mut(&nv) {
entry.insert_break();
}
}
} else {
for g in self.gpu_mem.values_mut() {
g.insert_break();
}
}
}
if used_widgets.use_temp_graph {
if let Some(temperature_sensors) = &data.temperature_sensors {
let mut not_visited = self
.temperature
.keys()
.map(String::to_owned)
.collect::<HashSet<_>>();
for sensor_data in temperature_sensors {
if !Filter::optional_should_keep(&filters.temp_graph_filter, &sensor_data.name)
{
continue;
}
if let Some(temperature) = sensor_data.temperature {
not_visited.remove(&sensor_data.name);
if !self.temperature.contains_key(&sensor_data.name) {
self.temperature
.insert(sensor_data.name.clone(), ChunkedData::default());
}
let curr = self
.temperature
.get_mut(&sensor_data.name)
.expect("entry must exist as it was created above");
let converted_temperature = settings
.temperature_type
.convert_temp_unit_float(temperature);
curr.push(converted_temperature);
}
}
for nv in not_visited {
if let Some(entry) = self.temperature.get_mut(&nv) {
entry.insert_break();
}
}
} else {
for g in self.temperature.values_mut() {
g.insert_break();
}
}
}
}
pub fn update_disk_io(
&mut self, disks: &[DiskWidgetData], filter: &Option<Filter>, show_unmounted: bool,
) {
#[cfg(not(target_os = "linux"))]
let _ = show_unmounted;
let mut not_visited: HashSet<String> = self.disk_io_read.keys().cloned().collect();
for disk in disks {
#[cfg(target_os = "linux")]
if !show_unmounted && disk.mount_point.is_empty() {
continue;
}
if !Filter::optional_should_keep(filter, &disk.name) {
continue;
}
not_visited.remove(&disk.name);
let read_entry = self.disk_io_read.entry(disk.name.clone()).or_default();
match disk.io_read_rate_bytes {
Some(v) => read_entry.push(v as f64),
None => read_entry.insert_break(),
}
let write_entry = self.disk_io_write.entry(disk.name.clone()).or_default();
match disk.io_write_rate_bytes {
Some(v) => write_entry.push(v as f64),
None => write_entry.insert_break(),
}
}
for name in not_visited {
if let Some(entry) = self.disk_io_read.get_mut(&name) {
entry.insert_break();
}
if let Some(entry) = self.disk_io_write.get_mut(&name) {
entry.insert_break();
}
}
}
pub fn prune(&mut self, max_age: Duration) {
if self.time.is_empty() {
return;
}
let now = Instant::now();
let end = {
let partition_point = self
.time
.partition_point(|then| now.duration_since(*then) > max_age);
if partition_point > 0 {
partition_point - 1
} else {
return;
}
};
self.time.drain(0..=end);
self.time.shrink_to_fit();
let _ = self.rx.prune_and_shrink_to_fit(end);
let _ = self.tx.prune_and_shrink_to_fit(end);
for cpu in &mut self.cpu {
let _ = cpu.prune_and_shrink_to_fit(end);
}
let _ = self.ram.prune_and_shrink_to_fit(end);
let _ = self.swap.prune_and_shrink_to_fit(end);
#[cfg(not(target_os = "windows"))]
let _ = self.cache_mem.prune_and_shrink_to_fit(end);
#[cfg(feature = "zfs")]
let _ = self.arc_mem.prune_and_shrink_to_fit(end);
#[cfg(feature = "gpu")]
{
self.gpu_mem.retain(|_, gpu| {
let _ = gpu.prune(end);
if gpu.no_elements() {
false
} else {
gpu.shrink_to_fit();
true
}
});
}
self.temperature.retain(|_, data| {
let _ = data.prune(end);
if data.no_elements() {
false
} else {
data.shrink_to_fit();
true
}
});
self.disk_io_read.retain(|_, data| {
let _ = data.prune(end);
if data.no_elements() {
false
} else {
data.shrink_to_fit();
true
}
});
self.disk_io_write.retain(|_, data| {
let _ = data.prune(end);
if data.no_elements() {
false
} else {
data.shrink_to_fit();
true
}
});
}
}