Skip to content

Commit

Permalink
refactor: migrate data collection and some others away from BottomErr…
Browse files Browse the repository at this point in the history
…or (#1497)
  • Loading branch information
ClementTsang authored Jul 22, 2024
1 parent c56e283 commit 0401f52
Show file tree
Hide file tree
Showing 13 changed files with 124 additions and 114 deletions.
3 changes: 1 addition & 2 deletions src/canvas.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ use crate::{
},
constants::*,
options::OptionError,
utils::error,
};

#[derive(Debug)]
Expand Down Expand Up @@ -205,7 +204,7 @@ impl Painter {

pub fn draw_data<B: Backend>(
&mut self, terminal: &mut Terminal<B>, app_state: &mut App,
) -> error::Result<()> {
) -> Result<(), std::io::Error> {
use BottomWidgetType::*;

terminal.draw(|f| {
Expand Down
3 changes: 2 additions & 1 deletion src/data_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub mod batteries;

pub mod cpu;
pub mod disks;
pub mod error;
pub mod memory;
pub mod network;
pub mod processes;
Expand Down Expand Up @@ -367,7 +368,7 @@ impl DataCollector {

#[cfg(target_family = "unix")]
{
self.data.load_avg = cpu::get_load_avg().ok();
self.data.load_avg = Some(cpu::get_load_avg());
}
}
}
Expand Down
8 changes: 4 additions & 4 deletions src/data_collection/cpu/sysinfo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ use std::collections::VecDeque;
use sysinfo::{LoadAvg, System};

use super::{CpuData, CpuDataType, CpuHarvest};
use crate::data_collection::cpu::LoadAvgHarvest;
use crate::data_collection::{cpu::LoadAvgHarvest, error::CollectionResult};

pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> crate::error::Result<CpuHarvest> {
pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> CollectionResult<CpuHarvest> {
let mut cpu_deque: VecDeque<_> = sys
.cpus()
.iter()
Expand All @@ -31,10 +31,10 @@ pub fn get_cpu_data_list(sys: &System, show_average_cpu: bool) -> crate::error::
Ok(Vec::from(cpu_deque))
}

pub fn get_load_avg() -> crate::error::Result<LoadAvgHarvest> {
pub fn get_load_avg() -> LoadAvgHarvest {
// The API for sysinfo apparently wants you to call it like this, rather than
// using a &System.
let LoadAvg { one, five, fifteen } = sysinfo::System::load_average();

Ok([one as f32, five as f32, fifteen as f32])
[one as f32, five as f32, fifteen as f32]
}
6 changes: 3 additions & 3 deletions src/data_collection/disks/freebsd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use serde::Deserialize;

use super::{keep_disk_entry, DiskHarvest, IoHarvest};
use crate::{
data_collection::{deserialize_xo, disks::IoData, DataCollector},
data_collection::{deserialize_xo, disks::IoData, error::CollectionResult, DataCollector},
utils::error,
};

Expand All @@ -27,7 +27,7 @@ struct FileSystem {
mounted_on: String,
}

pub fn get_io_usage() -> error::Result<IoHarvest> {
pub fn get_io_usage() -> CollectionResult<IoHarvest> {
// TODO: Should this (and other I/O collectors) fail fast? In general, should
// collection ever fail fast?
#[allow(unused_mut)]
Expand Down Expand Up @@ -59,7 +59,7 @@ pub fn get_io_usage() -> error::Result<IoHarvest> {
Ok(io_harvest)
}

pub fn get_disk_usage(collector: &DataCollector) -> error::Result<Vec<DiskHarvest>> {
pub fn get_disk_usage(collector: &DataCollector) -> CollectionResult<Vec<DiskHarvest>> {
let disk_filter = &collector.filters.disk_filter;
let mount_filter = &collector.filters.mount_filter;
let vec_disks: Vec<DiskHarvest> = get_disk_info().map(|storage_system_information| {
Expand Down
30 changes: 30 additions & 0 deletions src/data_collection/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
use anyhow::anyhow;

/// An error to do with data collection.
#[derive(Debug)]
pub enum CollectionError {
/// A general error to propagate back up. A wrapper around [`anyhow::Error`].
General(anyhow::Error),

/// The collection is unsupported.
Unsupported,
}

impl CollectionError {
// pub(crate) fn general<E: Into<anyhow::Error>>(error: E) -> Self {
// Self::General(error.into())
// }

pub(crate) fn from_str(msg: &'static str) -> Self {
Self::General(anyhow!(msg))
}
}

/// A [`Result`] with the error type being a [`DataCollectionError`].
pub(crate) type CollectionResult<T> = Result<T, CollectionError>;

impl From<std::io::Error> for CollectionError {
fn from(err: std::io::Error) -> Self {
CollectionError::General(err.into())
}
}
5 changes: 2 additions & 3 deletions src/data_collection/processes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,7 @@ cfg_if! {

use std::{borrow::Cow, time::Duration};

use super::DataCollector;
use crate::utils::error;
use super::{error::CollectionResult, DataCollector};

cfg_if! {
if #[cfg(target_family = "windows")] {
Expand Down Expand Up @@ -131,7 +130,7 @@ impl ProcessHarvest {
}

impl DataCollector {
pub(crate) fn get_processes(&mut self) -> error::Result<Vec<ProcessHarvest>> {
pub(crate) fn get_processes(&mut self) -> CollectionResult<Vec<ProcessHarvest>> {
cfg_if! {
if #[cfg(target_os = "linux")] {
let time_diff = self.data.collection_time
Expand Down
145 changes: 69 additions & 76 deletions src/data_collection/processes/linux.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,7 @@ use process::*;
use sysinfo::ProcessStatus;

use super::{Pid, ProcessHarvest, UserTable};
use crate::{
data_collection::DataCollector,
utils::error::{self, BottomError},
};
use crate::data_collection::{error::CollectionResult, DataCollector};

/// Maximum character length of a `/proc/<PID>/stat`` process name.
/// If it's equal or greater, then we instead refer to the command for the name.
Expand Down Expand Up @@ -64,7 +61,9 @@ struct CpuUsage {
cpu_fraction: f64,
}

fn cpu_usage_calculation(prev_idle: &mut f64, prev_non_idle: &mut f64) -> error::Result<CpuUsage> {
fn cpu_usage_calculation(
prev_idle: &mut f64, prev_non_idle: &mut f64,
) -> CollectionResult<CpuUsage> {
let (idle, non_idle) = {
// From SO answer: https://stackoverflow.com/a/23376195
let first_line = {
Expand Down Expand Up @@ -132,7 +131,7 @@ fn get_linux_cpu_usage(

fn read_proc(
prev_proc: &PrevProcDetails, process: Process, args: ReadProcArgs, user_table: &mut UserTable,
) -> error::Result<(ProcessHarvest, u64)> {
) -> CollectionResult<(ProcessHarvest, u64)> {
let Process {
pid: _,
uid,
Expand Down Expand Up @@ -298,7 +297,7 @@ pub(crate) struct ReadProcArgs {

pub(crate) fn linux_process_data(
collector: &mut DataCollector, time_difference_in_secs: u64,
) -> error::Result<Vec<ProcessHarvest>> {
) -> CollectionResult<Vec<ProcessHarvest>> {
let total_memory = collector.total_memory();
let prev_proc = PrevProc {
prev_idle: &mut collector.prev_idle,
Expand All @@ -323,88 +322,82 @@ pub(crate) fn linux_process_data(

// TODO: [PROC THREADS] Add threads

if let Ok(CpuUsage {
let CpuUsage {
mut cpu_usage,
cpu_fraction,
}) = cpu_usage_calculation(prev_idle, prev_non_idle)
{
if unnormalized_cpu {
let num_processors = collector.sys.system.cpus().len() as f64;

// Note we *divide* here because the later calculation divides `cpu_usage` - in
// effect, multiplying over the number of cores.
cpu_usage /= num_processors;
}
} = cpu_usage_calculation(prev_idle, prev_non_idle)?;

let mut pids_to_clear: HashSet<Pid> = pid_mapping.keys().cloned().collect();
if unnormalized_cpu {
let num_processors = collector.sys.system.cpus().len() as f64;

let pids = fs::read_dir("/proc")?.flatten().filter_map(|dir| {
if is_str_numeric(dir.file_name().to_string_lossy().trim()) {
Some(dir.path())
} else {
None
}
});

let args = ReadProcArgs {
use_current_cpu_total,
cpu_usage,
cpu_fraction,
total_memory,
time_difference_in_secs,
uptime: sysinfo::System::uptime(),
};
// Note we *divide* here because the later calculation divides `cpu_usage` - in
// effect, multiplying over the number of cores.
cpu_usage /= num_processors;
}

let process_vector: Vec<ProcessHarvest> = pids
.filter_map(|pid_path| {
if let Ok(process) = Process::from_path(pid_path) {
let pid = process.pid;
let prev_proc_details = pid_mapping.entry(pid).or_default();

#[allow(unused_mut)]
if let Ok((mut process_harvest, new_process_times)) =
read_proc(prev_proc_details, process, args, user_table)
{
#[cfg(feature = "gpu")]
if let Some(gpus) = &collector.gpu_pids {
gpus.iter().for_each(|gpu| {
// add mem/util for all gpus to pid
if let Some((mem, util)) = gpu.get(&(pid as u32)) {
process_harvest.gpu_mem += mem;
process_harvest.gpu_util += util;
}
});
if let Some(gpu_total_mem) = &collector.gpus_total_mem {
process_harvest.gpu_mem_percent = (process_harvest.gpu_mem as f64
/ *gpu_total_mem as f64
* 100.0)
as f32;
let mut pids_to_clear: HashSet<Pid> = pid_mapping.keys().cloned().collect();

let pids = fs::read_dir("/proc")?.flatten().filter_map(|dir| {
if is_str_numeric(dir.file_name().to_string_lossy().trim()) {
Some(dir.path())
} else {
None
}
});

let args = ReadProcArgs {
use_current_cpu_total,
cpu_usage,
cpu_fraction,
total_memory,
time_difference_in_secs,
uptime: sysinfo::System::uptime(),
};

let process_vector: Vec<ProcessHarvest> = pids
.filter_map(|pid_path| {
if let Ok(process) = Process::from_path(pid_path) {
let pid = process.pid;
let prev_proc_details = pid_mapping.entry(pid).or_default();

#[allow(unused_mut)]
if let Ok((mut process_harvest, new_process_times)) =
read_proc(prev_proc_details, process, args, user_table)
{
#[cfg(feature = "gpu")]
if let Some(gpus) = &collector.gpu_pids {
gpus.iter().for_each(|gpu| {
// add mem/util for all gpus to pid
if let Some((mem, util)) = gpu.get(&(pid as u32)) {
process_harvest.gpu_mem += mem;
process_harvest.gpu_util += util;
}
});
if let Some(gpu_total_mem) = &collector.gpus_total_mem {
process_harvest.gpu_mem_percent =
(process_harvest.gpu_mem as f64 / *gpu_total_mem as f64 * 100.0)
as f32;
}
}

prev_proc_details.cpu_time = new_process_times;
prev_proc_details.total_read_bytes = process_harvest.total_read_bytes;
prev_proc_details.total_write_bytes = process_harvest.total_write_bytes;
prev_proc_details.cpu_time = new_process_times;
prev_proc_details.total_read_bytes = process_harvest.total_read_bytes;
prev_proc_details.total_write_bytes = process_harvest.total_write_bytes;

pids_to_clear.remove(&pid);
return Some(process_harvest);
}
pids_to_clear.remove(&pid);
return Some(process_harvest);
}
}

None
})
.collect();
None
})
.collect();

pids_to_clear.iter().for_each(|pid| {
pid_mapping.remove(pid);
});
pids_to_clear.iter().for_each(|pid| {
pid_mapping.remove(pid);
});

Ok(process_vector)
} else {
Err(BottomError::GenericError(
"Could not calculate CPU usage.".to_string(),
))
}
Ok(process_vector)
}

#[cfg(test)]
Expand Down
4 changes: 2 additions & 2 deletions src/data_collection/processes/unix.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ cfg_if! {
use super::ProcessHarvest;

use crate::data_collection::{DataCollector, processes::*};
use crate::utils::error;
use crate::data_collection::error::CollectionResult;

pub fn sysinfo_process_data(collector: &mut DataCollector) -> error::Result<Vec<ProcessHarvest>> {
pub fn sysinfo_process_data(collector: &mut DataCollector) -> CollectionResult<Vec<ProcessHarvest>> {
let sys = &collector.sys.system;
let use_current_cpu_total = collector.use_current_cpu_total;
let unnormalized_cpu = collector.unnormalized_cpu;
Expand Down
7 changes: 2 additions & 5 deletions src/data_collection/processes/unix/process_ext.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,13 @@ use hashbrown::HashMap;
use sysinfo::{ProcessStatus, System};

use super::ProcessHarvest;
use crate::{
data_collection::{processes::UserTable, Pid},
utils::error,
};
use crate::data_collection::{error::CollectionResult, processes::UserTable, Pid};

pub(crate) trait UnixProcessExt {
fn sysinfo_process_data(
sys: &System, use_current_cpu_total: bool, unnormalized_cpu: bool, total_memory: u64,
user_table: &mut UserTable,
) -> error::Result<Vec<ProcessHarvest>> {
) -> CollectionResult<Vec<ProcessHarvest>> {
let mut process_vector: Vec<ProcessHarvest> = Vec::new();
let process_hashmap = sys.processes();
let cpu_usage = sys.global_cpu_info().cpu_usage() as f64 / 100.0;
Expand Down
8 changes: 4 additions & 4 deletions src/data_collection/processes/unix/user_table.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
use hashbrown::HashMap;

use crate::utils::error::{self, BottomError};
use crate::data_collection::error::{CollectionError, CollectionResult};

#[derive(Debug, Default)]
pub struct UserTable {
pub uid_user_mapping: HashMap<libc::uid_t, String>,
}

impl UserTable {
pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> error::Result<String> {
pub fn get_uid_to_username_mapping(&mut self, uid: libc::uid_t) -> CollectionResult<String> {
if let Some(user) = self.uid_user_mapping.get(&uid) {
Ok(user.clone())
} else {
Expand All @@ -17,12 +17,12 @@ impl UserTable {
let passwd = unsafe { libc::getpwuid(uid) };

if passwd.is_null() {
Err(BottomError::GenericError("passwd is inaccessible".into()))
Err(CollectionError::from_str("passwd is inaccessible"))
} else {
// SAFETY: We return early if passwd is null.
let username = unsafe { std::ffi::CStr::from_ptr((*passwd).pw_name) }
.to_str()
.map_err(|err| BottomError::GenericError(err.to_string()))?
.map_err(|err| CollectionError::General(err.into()))?
.to_string();
self.uid_user_mapping.insert(uid, username.clone());

Expand Down
Loading

0 comments on commit 0401f52

Please sign in to comment.