Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(wip) refactor: redo how data collection is laid out #1558

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion src/data_collection/batteries.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,14 @@
//! the battery crate.

cfg_if::cfg_if! {
if #[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux", target_os = "freebsd", target_os = "dragonfly", target_os = "ios"))] {
if #[cfg(any(
target_os = "windows",
target_os = "macos",
target_os = "linux",
target_os = "freebsd",
target_os = "dragonfly",
target_os = "ios",
))] {
pub mod battery;
pub use self::battery::*;
}
Expand Down
3 changes: 0 additions & 3 deletions src/data_collection/cpu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,3 @@ pub struct CpuData {
}

pub type CpuHarvest = Vec<CpuData>;

pub type PastCpuWork = f64;
pub type PastCpuTotal = f64;
2 changes: 1 addition & 1 deletion src/data_collection/disks/unix/macos/counters.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::data_collection::disks::IoCounters;
fn get_device_io(device: io_kit::IoObject) -> anyhow::Result<IoCounters> {
let parent = device.service_parent()?;

// XXX: Re: Conform check being disabled.
// NB: Regarding the conform check being disabled.
//
// Okay, so this is weird.
//
Expand Down
2 changes: 1 addition & 1 deletion src/data_collection/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ pub mod arc;
pub struct MemHarvest {
pub used_bytes: u64,
pub total_bytes: u64,
pub use_percent: Option<f64>, /* TODO: Might be find to just make this an f64, and any
pub use_percent: Option<f64>, /* TODO: Might be fine to just make this an f64, and any
* consumer checks NaN. */
}
File renamed without changes.
1 change: 1 addition & 0 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ pub mod constants;
pub mod data_collection;
pub mod data_conversion;
pub mod event;
pub mod new_data_collection;
pub mod options;
pub mod widgets;

Expand Down
55 changes: 55 additions & 0 deletions src/new_data_collection/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Data Collection

**Note:** This information is really only useful to _developers_ of bottom,
and can be ignored by users.

Data collection in bottom has two main components: **sources** and **collectors**.

**Sources** are either libraries or system APIs that actually extract the data.
These may map to multiple different operating systems. Examples are `sysinfo`,
or `libc` bindings, or Linux-specific code.

**Collectors** are _platform-specific_ (typically OS-specific), and can pull from
different sources to get all the data needed, with some glue code in between. As
such, sources should be written to be per-"job", and be divided such that
collectors can import specific code as needed.

We can kinda visualize this with a quick-and-dirty diagram (note this is not accurate or up-to-date):

```mermaid
flowchart TB
subgraph sources
direction TB
linux
windows
macos
unix
sysinfo
freebsd
end
subgraph collectors
direction TB
Linux
Windows
macOS
FreeBSD
end
linux -..-> Linux
unix -..-> Linux
sysinfo -..-> Linux
windows -..-> Windows
sysinfo -..-> Windows
macos -..-> macOS
unix -..-> macOS
sysinfo -..-> macOS
freebsd -..-> FreeBSD
sysinfo -..-> FreeBSD
```

## Sources

As mentioned above, sources should be written in a way where collectors can easily pick the necessary code required.

## Collectors

Each platform should implement the `DataCollector` trait in `collectors/common.rs`. The trait has default implementations where essentially no work is done, which is used as fallback behaviour.
51 changes: 51 additions & 0 deletions src/new_data_collection/collectors/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//! Common code amongst all data collectors.

use crate::{
data_collection::Data,
new_data_collection::{
error::CollectionResult,
sources::{
cpu::CpuHarvest, disk::DiskHarvest, memory::MemHarvest, processes::ProcessHarvest,
temperature::TemperatureData,
},
},
};

#[cfg(feature = "battery")]
use crate::new_data_collection::sources::battery::BatteryHarvest;

// /// Represents data collected at an instance.
// #[derive(Debug)]
// pub struct Data {
// pub collection_time: Instant,
// pub temperature_data: Option<Vec<TemperatureData>>,
// pub process_data: Option<Vec<ProcessHarvest>>,
// pub disk_data: Option<DiskHarvest>,
// }

/// The trait representing what a per-platform data collector should implement.
pub trait DataCollector {
/// Return data.
///
/// For now, this returns the old data type for cross-compatibility as we migrate.
fn get_data(&mut self) -> Data;

/// Return temperature data.
fn get_temperature_data(&mut self) -> CollectionResult<Vec<TemperatureData>>;

/// Return process data.
fn get_process_data(&mut self) -> CollectionResult<Vec<ProcessHarvest>>;

/// Return disk data.
fn get_disk_data(&mut self) -> CollectionResult<DiskHarvest>;

/// Return CPU data.
fn get_cpu_data(&mut self) -> CollectionResult<CpuHarvest>;

/// Return memory data.
fn get_memory_data(&mut self) -> CollectionResult<MemHarvest>;

#[cfg(feature = "battery")]
/// Return battery data.
fn get_battery_data(&mut self) -> CollectionResult<Vec<BatteryHarvest>>;
}
7 changes: 7 additions & 0 deletions src/new_data_collection/collectors/fallback.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
use super::common::DataCollector;

/// A fallback [`DataCollector`] for unsupported systems
/// that does nothing.
pub struct FallbackDataCollector {}

impl DataCollector for FallbackDataCollector {}
135 changes: 135 additions & 0 deletions src/new_data_collection/collectors/linux.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//! The data collector for Linux.

use std::time::Instant;

use crate::{
app::filter::Filter,
data_collection::Data,
new_data_collection::{
error::CollectionResult,
sources::{
cpu::CpuHarvest,
disk::DiskHarvest,
linux::{get_temperature_data, linux_process_data, ProcessCollector},
memory::MemHarvest,
processes::ProcessHarvest,
sysinfo::{
cpu::{get_cpu_data_list, get_load_avg},
memory::{get_cache_usage, get_ram_usage, get_swap_usage},
},
temperature::{TemperatureData, TemperatureType},
},
},
};

use super::common::DataCollector;

cfg_if::cfg_if! {
if #[cfg(feature = "battery")] {
use starship_battery::{Battery, Manager};
use crate::new_data_collection::sources::battery::BatteryHarvest;
}

}

/// The [`DataCollector`] for Linux.
pub struct LinuxDataCollector {
current_collection_time: Instant,
last_collection_time: Instant,

temp_type: TemperatureType,
temp_filters: Option<Filter>,

proc_collector: ProcessCollector,

system: sysinfo::System,
network: sysinfo::Networks,

show_average_cpu: bool,

#[cfg(feature = "battery")]
batteries: Option<(Manager, Vec<Battery>)>,

#[cfg(feature = "gpu")]
nvml: nvml_wrapper::Nvml,

#[cfg(feature = "gpu")]
gpus_total_mem: Option<u64>,
}

impl LinuxDataCollector {
fn refresh_data(&mut self) -> CollectionResult<()> {
Ok(())
}
}

impl DataCollector for LinuxDataCollector {
fn get_data(&mut self) -> Data {
let collection_time = Instant::now();

todo!()
}

fn get_temperature_data(&mut self) -> CollectionResult<Vec<TemperatureData>> {
Ok(get_temperature_data(&self.temp_type, &self.temp_filters))
}

fn get_process_data(&mut self) -> CollectionResult<Vec<ProcessHarvest>> {
let time_diff = self
.current_collection_time
.duration_since(self.last_collection_time)
.as_secs();

linux_process_data(
&self.system,
time_diff,
&mut self.proc_collector,
#[cfg(feature = "gpu")]
self.gpus_total_mem,
)
}

fn get_disk_data(&mut self) -> CollectionResult<DiskHarvest> {
todo!()
}

fn get_cpu_data(&mut self) -> CollectionResult<CpuHarvest> {
let usages = get_cpu_data_list(&self.system, self.show_average_cpu);
let load_average = get_load_avg();

CollectionResult::Ok(CpuHarvest {
usages,
load_average,
})
}

fn get_memory_data(&mut self) -> CollectionResult<MemHarvest> {
let memory = get_ram_usage(&self.system);
let swap = get_swap_usage(&self.system);
let cache = get_cache_usage(&self.system);

CollectionResult::Ok(MemHarvest {
memory,
swap,
cache,
#[cfg(feature = "zfs")]
arc: crate::new_data_collection::sources::linux::get_arc_usage(),
#[cfg(feature = "gpu")]
gpu: crate::new_data_collection::sources::nvidia::get_gpu_memory_usage(&self.nvml),
})
}

#[cfg(feature = "battery")]
fn get_battery_data(&mut self) -> CollectionResult<Vec<BatteryHarvest>> {
use crate::new_data_collection::{
error::CollectionError, sources::starship::refresh_batteries,
};

match &mut self.batteries {
Some((battery_manager, battery_list)) => {
CollectionResult::Ok(refresh_batteries(battery_manager, battery_list))
}
None => CollectionResult::Err(CollectionError::NoData),
}
}
}
48 changes: 48 additions & 0 deletions src/new_data_collection/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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),

/// No data.
NoData,

/// Collection is unsupported.
Unsupported,
}

impl std::fmt::Display for CollectionError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CollectionError::General(err) => err.fmt(f),
CollectionError::NoData => {
write!(f, "no data found")
}
CollectionError::Unsupported => {
write!(
f,
"bottom does not support this type of data collection for this platform."
)
}
}
}
}

impl std::error::Error for CollectionError {}

/// 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 {
Self::General(err.into())
}
}

impl From<&'static str> for CollectionError {
fn from(msg: &'static str) -> Self {
Self::General(anyhow!(msg))
}
}
28 changes: 28 additions & 0 deletions src/new_data_collection/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//! Module that just re-exports the right data collector for a given platform.

pub mod error;

mod collectors {
pub mod common;

cfg_if::cfg_if! {
if #[cfg(target_os = "linux")] {
pub mod linux;
pub use linux::LinuxDataCollector as DataCollectorImpl;
// } else if #[cfg(target_os = "macos")] {
// pub mod macos;
// pub use macos::MacOsDataCollector as DataCollectorImpl;
// } else if #[cfg(target_os = "windows")] {
// pub mod windows;
// pub use windows::WindowsDataCollector as DataCollectorImpl;
// } else if #[cfg(target_os = "freebsd")] {
// pub mod freebsd;
// pub use freebsd::FreeBsdDataCollector as DataCollectorImpl;
} else {
pub mod fallback;
pub use fallback::FallbackDataCollector as DataCollectorImpl;
}
}
}

pub mod sources;
20 changes: 20 additions & 0 deletions src/new_data_collection/sources/common/battery.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
//! Common code for retrieving battery data.

#[derive(Debug, Clone)]
pub enum State {
Unknown,
Charging,
Discharging,
Empty,
Full,
}

#[derive(Debug, Clone)]
pub struct BatteryHarvest {
pub charge_percent: f64,
pub secs_until_full: Option<i64>,
pub secs_until_empty: Option<i64>,
pub power_consumption_rate_watts: f64,
pub health_percent: f64,
pub state: State,
}
Loading
Loading