Skip to content

Commit

Permalink
Merge pull request #730 from ClementTsang/consolidate_tables
Browse files Browse the repository at this point in the history
This serves as somewhat of an intermediary refactor to unify some scrollable table code - in particular, in regards to drawing. This is almost a parallel refactor as #710, which did something similar for time graphs. However, this one has a bit more work in regards to the concepts of component state, in particular, for width calculation caching and scroll position management.
  • Loading branch information
ClementTsang authored May 17, 2022
2 parents 45680da + 01574c8 commit de765fc
Show file tree
Hide file tree
Showing 37 changed files with 4,028 additions and 4,701 deletions.
5 changes: 3 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ doc = false

[profile.release]
debug = 0
strip = "symbols"
lto = true
opt-level = 3
codegen-units = 1
strip = "symbols"

[features]
default = ["fern", "log", "battery", "gpu"]
Expand All @@ -40,11 +40,12 @@ nvidia = ["nvml-wrapper"]
[dependencies]
anyhow = "1.0.57"
backtrace = "0.3.65"
cfg-if = "1.0.0"
crossterm = "0.18.2"
ctrlc = { version = "3.1.9", features = ["termination"] }
clap = { version = "3.1.12", features = ["default", "cargo", "wrap_help"] }
cfg-if = "1.0.0"
concat-string = "1.0.1"
# const_format = "0.2.23"
dirs = "4.0.0"
futures = "0.3.21"
futures-timer = "3.0.2"
Expand Down
889 changes: 282 additions & 607 deletions src/app.rs

Large diffs are not rendered by default.

142 changes: 121 additions & 21 deletions src/app/data_farmer.rs
Original file line number Diff line number Diff line change
@@ -1,27 +1,32 @@
/// In charge of cleaning, processing, and managing data. I couldn't think of
/// a better name for the file. Since I called data collection "harvesting",
/// then this is the farmer I guess.
///
/// Essentially the main goal is to shift the initial calculation and distribution
/// of joiner points and data to one central location that will only do it
/// *once* upon receiving the data --- as opposed to doing it on canvas draw,
/// which will be a costly process.
///
/// This will also handle the *cleaning* of stale data. That should be done
/// in some manner (timer on another thread, some loop) that will occasionally
/// call the purging function. Failure to do so *will* result in a growing
/// memory usage and higher CPU usage - you will be trying to process more and
/// more points as this is used!
//! In charge of cleaning, processing, and managing data. I couldn't think of
//! a better name for the file. Since I called data collection "harvesting",
//! then this is the farmer I guess.
//!
//! Essentially the main goal is to shift the initial calculation and distribution
//! of joiner points and data to one central location that will only do it
//! *once* upon receiving the data --- as opposed to doing it on canvas draw,
//! which will be a costly process.
//!
//! This will also handle the *cleaning* of stale data. That should be done
//! in some manner (timer on another thread, some loop) that will occasionally
//! call the purging function. Failure to do so *will* result in a growing
//! memory usage and higher CPU usage - you will be trying to process more and
//! more points as this is used!

use once_cell::sync::Lazy;

use fxhash::FxHashMap;
use itertools::Itertools;

use std::{time::Instant, vec::Vec};

#[cfg(feature = "battery")]
use crate::data_harvester::batteries;

use crate::{
data_harvester::{cpu, disks, memory, network, processes, temperature, Data},
data_harvester::{cpu, disks, memory, network, processes::ProcessHarvest, temperature, Data},
utils::gen_util::{get_decimal_bytes, GIGA_LIMIT},
Pid,
};
use regex::Regex;

Expand All @@ -38,6 +43,97 @@ pub struct TimedData {
pub swap_data: Option<Value>,
}

pub type StringPidMap = FxHashMap<String, Vec<Pid>>;

#[derive(Clone, Debug, Default)]
pub struct ProcessData {
/// A PID to process data map.
pub process_harvest: FxHashMap<Pid, ProcessHarvest>,

/// A mapping from a process name to any PID with that name.
pub name_pid_map: StringPidMap,

/// A mapping from a process command to any PID with that name.
pub cmd_pid_map: StringPidMap,

/// A mapping between a process PID to any children process PIDs.
pub process_parent_mapping: FxHashMap<Pid, Vec<Pid>>,

/// PIDs corresponding to processes that have no parents.
pub orphan_pids: Vec<Pid>,
}

impl ProcessData {
fn ingest(&mut self, list_of_processes: Vec<ProcessHarvest>) {
// TODO: [Optimization] Probably more efficient to all of this in the data collection step, but it's fine for now.
self.name_pid_map.clear();
self.cmd_pid_map.clear();
self.process_parent_mapping.clear();

// Reverse as otherwise the pid mappings are in the wrong order.
list_of_processes.iter().rev().for_each(|process_harvest| {
if let Some(entry) = self.name_pid_map.get_mut(&process_harvest.name) {
entry.push(process_harvest.pid);
} else {
self.name_pid_map
.insert(process_harvest.name.to_string(), vec![process_harvest.pid]);
}

if let Some(entry) = self.cmd_pid_map.get_mut(&process_harvest.command) {
entry.push(process_harvest.pid);
} else {
self.cmd_pid_map.insert(
process_harvest.command.to_string(),
vec![process_harvest.pid],
);
}

if let Some(parent_pid) = process_harvest.parent_pid {
if let Some(entry) = self.process_parent_mapping.get_mut(&parent_pid) {
entry.push(process_harvest.pid);
} else {
self.process_parent_mapping
.insert(parent_pid, vec![process_harvest.pid]);
}
}
});

self.name_pid_map.shrink_to_fit();
self.cmd_pid_map.shrink_to_fit();
self.process_parent_mapping.shrink_to_fit();

let process_pid_map = list_of_processes
.into_iter()
.map(|process| (process.pid, process))
.collect();
self.process_harvest = process_pid_map;

// This also needs a quick sort + reverse to be in the correct order.
self.orphan_pids = {
let mut res: Vec<Pid> = self
.process_harvest
.iter()
.filter_map(|(pid, process_harvest)| {
if let Some(parent_pid) = process_harvest.parent_pid {
if self.process_harvest.contains_key(&parent_pid) {
None
} else {
Some(*pid)
}
} else {
Some(*pid)
}
})
.sorted()
.collect();

res.reverse();

res
}
}
}

/// AppCollection represents the pooled data stored within the main app
/// thread. Basically stores a (occasionally cleaned) record of the data
/// collected, and what is needed to convert into a displayable form.
Expand All @@ -57,7 +153,7 @@ pub struct DataCollection {
pub swap_harvest: memory::MemHarvest,
pub cpu_harvest: cpu::CpuHarvest,
pub load_avg_harvest: cpu::LoadAvgHarvest,
pub process_harvest: Vec<processes::ProcessHarvest>,
pub process_data: ProcessData,
pub disk_harvest: Vec<disks::DiskHarvest>,
pub io_harvest: disks::IoHarvest,
pub io_labels_and_prev: Vec<((u64, u64), (u64, u64))>,
Expand All @@ -78,7 +174,7 @@ impl Default for DataCollection {
swap_harvest: memory::MemHarvest::default(),
cpu_harvest: cpu::CpuHarvest::default(),
load_avg_harvest: cpu::LoadAvgHarvest::default(),
process_harvest: Vec::default(),
process_data: Default::default(),
disk_harvest: Vec::default(),
io_harvest: disks::IoHarvest::default(),
io_labels_and_prev: Vec::default(),
Expand All @@ -97,7 +193,7 @@ impl DataCollection {
self.memory_harvest = memory::MemHarvest::default();
self.swap_harvest = memory::MemHarvest::default();
self.cpu_harvest = cpu::CpuHarvest::default();
self.process_harvest = Vec::default();
self.process_data = Default::default();
self.disk_harvest = Vec::default();
self.io_harvest = disks::IoHarvest::default();
self.io_labels_and_prev = Vec::default();
Expand All @@ -108,10 +204,14 @@ impl DataCollection {
}
}

pub fn set_frozen_time(&mut self) {
pub fn freeze(&mut self) {
self.frozen_instant = Some(self.current_instant);
}

pub fn thaw(&mut self) {
self.frozen_instant = None;
}

pub fn clean_data(&mut self, max_time_millis: u64) {
let current_time = Instant::now();

Expand Down Expand Up @@ -319,8 +419,8 @@ impl DataCollection {
self.io_harvest = io;
}

fn eat_proc(&mut self, list_of_processes: Vec<processes::ProcessHarvest>) {
self.process_harvest = list_of_processes;
fn eat_proc(&mut self, list_of_processes: Vec<ProcessHarvest>) {
self.process_data.ingest(list_of_processes);
}

#[cfg(feature = "battery")]
Expand Down
30 changes: 24 additions & 6 deletions src/app/data_harvester.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ pub struct DataCollector {
#[cfg(feature = "battery")]
battery_list: Option<Vec<Battery>>,
filters: DataFilters,

#[cfg(target_family = "unix")]
user_table: self::processes::UserTable,
}

impl DataCollector {
Expand Down Expand Up @@ -133,6 +136,8 @@ impl DataCollector {
#[cfg(feature = "battery")]
battery_list: None,
filters,
#[cfg(target_family = "unix")]
user_table: Default::default(),
}
}

Expand Down Expand Up @@ -191,7 +196,7 @@ impl DataCollector {
};
}

pub fn set_collected_data(&mut self, used_widgets: UsedWidgets) {
pub fn set_data_collection(&mut self, used_widgets: UsedWidgets) {
self.widgets_to_harvest = used_widgets;
}

Expand Down Expand Up @@ -270,15 +275,28 @@ impl DataCollector {
.duration_since(self.last_collection_time)
.as_secs(),
self.mem_total_kb,
&mut self.user_table,
)
}
#[cfg(not(target_os = "linux"))]
{
processes::get_process_data(
&self.sys,
self.use_current_cpu_total,
self.mem_total_kb,
)
#[cfg(target_family = "unix")]
{
processes::get_process_data(
&self.sys,
self.use_current_cpu_total,
self.mem_total_kb,
&mut self.user_table,
)
}
#[cfg(not(target_family = "unix"))]
{
processes::get_process_data(
&self.sys,
self.use_current_cpu_total,
self.mem_total_kb,
)
}
}
} {
self.data.list_of_processes = Some(process_list);
Expand Down
2 changes: 1 addition & 1 deletion src/app/data_harvester/network/heim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
use super::NetworkHarvest;
use std::time::Instant;

// FIXME: Eventually make it so that this thing also takes individual usage into account, so we can allow for showing per-interface!
// TODO: Eventually make it so that this thing also takes individual usage into account, so we can show per-interface!
pub async fn get_network_data(
prev_net_access_time: Instant, prev_net_rx: &mut u64, prev_net_tx: &mut u64,
curr_time: Instant, actually_get: bool, filter: &Option<crate::app::Filter>,
Expand Down
18 changes: 13 additions & 5 deletions src/app/data_harvester/processes/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use std::collections::hash_map::Entry;
use crate::utils::error::{self, BottomError};
use crate::Pid;

use super::ProcessHarvest;
use super::{ProcessHarvest, UserTable};

use sysinfo::ProcessStatus;

Expand Down Expand Up @@ -120,6 +120,7 @@ fn get_linux_cpu_usage(
fn read_proc(
prev_proc: &PrevProcDetails, stat: &Stat, cpu_usage: f64, cpu_fraction: f64,
use_current_cpu_total: bool, time_difference_in_secs: u64, mem_total_kb: u64,
user_table: &mut UserTable,
) -> error::Result<(ProcessHarvest, u64)> {
use std::convert::TryFrom;

Expand Down Expand Up @@ -156,7 +157,10 @@ fn read_proc(
};

let process_state_char = stat.state;
let process_state = ProcessStatus::from(process_state_char).to_string();
let process_state = (
ProcessStatus::from(process_state_char).to_string(),
process_state_char,
);
let (cpu_usage_percent, new_process_times) = get_linux_cpu_usage(
stat,
cpu_usage,
Expand Down Expand Up @@ -199,7 +203,7 @@ fn read_proc(
(0, 0, 0, 0)
};

let uid = Some(process.owner);
let uid = process.owner;

Ok((
ProcessHarvest {
Expand All @@ -215,8 +219,11 @@ fn read_proc(
total_read_bytes,
total_write_bytes,
process_state,
process_state_char,
uid,
user: user_table
.get_uid_to_username_mapping(uid)
.map(Into::into)
.unwrap_or_else(|_| "N/A".into()),
},
new_process_times,
))
Expand All @@ -225,7 +232,7 @@ fn read_proc(
pub fn get_process_data(
prev_idle: &mut f64, prev_non_idle: &mut f64,
pid_mapping: &mut FxHashMap<Pid, PrevProcDetails>, use_current_cpu_total: bool,
time_difference_in_secs: u64, mem_total_kb: u64,
time_difference_in_secs: u64, mem_total_kb: u64, user_table: &mut UserTable,
) -> crate::utils::error::Result<Vec<ProcessHarvest>> {
// TODO: [PROC THREADS] Add threads

Expand Down Expand Up @@ -268,6 +275,7 @@ pub fn get_process_data(
use_current_cpu_total,
time_difference_in_secs,
mem_total_kb,
user_table,
) {
prev_proc_details.cpu_time = new_process_times;
prev_proc_details.total_read_bytes =
Expand Down
Loading

0 comments on commit de765fc

Please sign in to comment.