feature: add gpu ram collector for nvidia feature flag (#794)

* add gpu ram collector for nvidia feature flag

* add row for TX in basic layout

* size gpu point_vec

* use vec for mem basic widget drawing

* remove to_owned

* code review: change mem tuple to struct with cfg fields, rename mem_basic ratio and use vec macro for layout

* build on freebsd
This commit is contained in:
Justin Martin 2022-10-15 15:08:48 -04:00 committed by GitHub
parent 41970d9c64
commit bd35bbdc9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 524 additions and 133 deletions

View File

@ -43,6 +43,8 @@ pub struct TimedData {
pub swap_data: Option<Value>,
#[cfg(feature = "zfs")]
pub arc_data: Option<Value>,
#[cfg(feature = "gpu")]
pub gpu_data: Vec<Option<Value>>,
}
#[derive(Clone, Debug, Default)]
@ -133,6 +135,8 @@ pub struct DataCollection {
pub battery_harvest: Vec<batteries::BatteryHarvest>,
#[cfg(feature = "zfs")]
pub arc_harvest: memory::MemHarvest,
#[cfg(feature = "gpu")]
pub gpu_harvest: Vec<(String, memory::MemHarvest)>,
}
impl Default for DataCollection {
@ -155,6 +159,8 @@ impl Default for DataCollection {
battery_harvest: Vec::default(),
#[cfg(feature = "zfs")]
arc_harvest: memory::MemHarvest::default(),
#[cfg(feature = "gpu")]
gpu_harvest: Vec::default(),
}
}
}
@ -179,6 +185,10 @@ impl DataCollection {
{
self.arc_harvest = memory::MemHarvest::default();
}
#[cfg(feature = "gpu")]
{
self.gpu_harvest = Vec::default();
}
}
pub fn clean_data(&mut self, max_time_millis: u64) {
@ -222,6 +232,14 @@ impl DataCollection {
self.eat_arc(arc, &mut new_entry);
}
}
#[cfg(feature = "gpu")]
{
if let Some(gpu) = harvested_data.gpu {
self.eat_gpu(gpu, &mut new_entry);
}
}
// CPU
if let Some(cpu) = harvested_data.cpu {
self.eat_cpu(cpu, &mut new_entry);
@ -405,8 +423,18 @@ impl DataCollection {
#[cfg(feature = "zfs")]
fn eat_arc(&mut self, arc: memory::MemHarvest, new_entry: &mut TimedData) {
// Arc
new_entry.arc_data = arc.use_percent;
self.arc_harvest = arc;
}
#[cfg(feature = "gpu")]
fn eat_gpu(&mut self, gpu: Vec<(String, memory::MemHarvest)>, new_entry: &mut TimedData) {
// Note this only pre-calculates the data points - the names will be
// within the local copy of gpu_harvest. Since it's all sequential
// it probably doesn't matter anyways.
gpu.iter().for_each(|data| {
new_entry.gpu_data.push(data.1.use_percent);
});
self.gpu_harvest = gpu.to_vec();
}
}

View File

@ -17,6 +17,9 @@ use futures::join;
use super::DataFilters;
#[cfg(feature = "nvidia")]
pub mod nvidia;
#[cfg(feature = "battery")]
pub mod batteries;
pub mod cpu;
@ -42,6 +45,8 @@ pub struct Data {
pub list_of_batteries: Option<Vec<batteries::BatteryHarvest>>,
#[cfg(feature = "zfs")]
pub arc: Option<memory::MemHarvest>,
#[cfg(feature = "gpu")]
pub gpu: Option<Vec<(String, memory::MemHarvest)>>,
}
impl Default for Data {
@ -61,6 +66,8 @@ impl Default for Data {
list_of_batteries: None,
#[cfg(feature = "zfs")]
arc: None,
#[cfg(feature = "gpu")]
gpu: None,
}
}
}
@ -83,6 +90,10 @@ impl Data {
{
self.arc = None;
}
#[cfg(feature = "gpu")]
{
self.gpu = None;
}
}
}
@ -423,19 +434,24 @@ impl DataCollector {
self.data.network = net_data;
}
if let Ok(memory) = mem_res.0 {
if let Ok(memory) = mem_res.ram {
self.data.memory = memory;
}
if let Ok(swap) = mem_res.1 {
if let Ok(swap) = mem_res.swap {
self.data.swap = swap;
}
#[cfg(feature = "zfs")]
if let Ok(arc) = mem_res.2 {
if let Ok(arc) = mem_res.arc {
self.data.arc = arc;
}
#[cfg(feature = "gpu")]
if let Ok(gpu) = mem_res.gpus {
self.data.gpu = gpu;
}
if let Ok(disks) = disk_res {
self.data.disks = disks;
}

View File

@ -3,9 +3,9 @@
use std::collections::VecDeque;
use sysinfo::{LoadAvg, System, SystemExt};
use sysinfo::{CpuExt, LoadAvg, System, SystemExt};
use super::{CpuData, CpuHarvest, PastCpuTotal, PastCpuWork};
use super::{CpuData, CpuDataType, CpuHarvest, PastCpuTotal, PastCpuWork};
use crate::app::data_harvester::cpu::LoadAvgHarvest;
pub async fn get_cpu_data_list(
@ -14,12 +14,11 @@ pub async fn get_cpu_data_list(
_previous_average_cpu_time: &mut Option<(PastCpuWork, PastCpuTotal)>,
) -> crate::error::Result<CpuHarvest> {
let mut cpu_deque: VecDeque<_> = sys
.processors()
.cpus()
.iter()
.enumerate()
.map(|(i, cpu)| CpuData {
cpu_prefix: "CPU".to_string(),
cpu_count: Some(i),
data_type: CpuDataType::Cpu(i),
cpu_usage: cpu.cpu_usage() as f64,
})
.collect();
@ -28,8 +27,7 @@ pub async fn get_cpu_data_list(
let cpu = sys.global_cpu_info();
cpu_deque.push_front(CpuData {
cpu_prefix: "AVG".to_string(),
cpu_count: None,
data_type: CpuDataType::Avg,
cpu_usage: cpu.cpu_usage() as f64,
})
}

View File

@ -14,3 +14,13 @@ pub struct MemHarvest {
pub mem_used_in_kib: u64,
pub use_percent: Option<f64>,
}
#[derive(Debug)]
pub struct MemCollect {
pub ram: crate::utils::error::Result<Option<MemHarvest>>,
pub swap: crate::utils::error::Result<Option<MemHarvest>>,
#[cfg(feature = "zfs")]
pub arc: crate::utils::error::Result<Option<MemHarvest>>,
#[cfg(feature = "gpu")]
pub gpus: crate::utils::error::Result<Option<Vec<(String, MemHarvest)>>>,
}

View File

@ -1,20 +1,26 @@
//! Data collection for memory via heim.
use crate::data_harvester::memory::MemHarvest;
pub async fn get_mem_data(
actually_get: bool,
) -> (
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
) {
use futures::join;
use crate::data_harvester::memory::{MemCollect, MemHarvest};
pub async fn get_mem_data(actually_get: bool) -> MemCollect {
if !actually_get {
(Ok(None), Ok(None), Ok(None))
MemCollect {
ram: Ok(None),
swap: Ok(None),
#[cfg(feature = "zfs")]
arc: Ok(None),
#[cfg(feature = "gpu")]
gpus: Ok(None),
}
} else {
join!(get_ram_data(), get_swap_data(), get_arc_data())
MemCollect {
ram: get_ram_data().await,
swap: get_swap_data().await,
#[cfg(feature = "zfs")]
arc: get_arc_data().await,
#[cfg(feature = "gpu")]
gpus: get_gpu_data().await,
}
}
}
@ -170,16 +176,15 @@ pub async fn get_swap_data() -> crate::utils::error::Result<Option<MemHarvest>>
}))
}
#[cfg(feature = "zfs")]
pub async fn get_arc_data() -> crate::utils::error::Result<Option<MemHarvest>> {
#[cfg(not(feature = "zfs"))]
let (mem_total_in_kib, mem_used_in_kib) = (0, 0);
#[cfg(feature = "zfs")]
let (mem_total_in_kib, mem_used_in_kib) = {
#[cfg(target_os = "linux")]
{
let mut mem_arc = 0;
let mut mem_total = 0;
let mut zfs_keys_read: u8 = 0;
const ZFS_KEYS_NEEDED: u8 = 2;
use smol::fs::read_to_string;
let arcinfo = read_to_string("/proc/spl/kstat/zfs/arcstats").await?;
for line in arcinfo.lines() {
@ -191,8 +196,7 @@ pub async fn get_arc_data() -> crate::utils::error::Result<Option<MemHarvest>> {
continue;
}
};
let mut zfs_keys_read: u8 = 0;
const ZFS_KEYS_NEEDED: u8 = 2;
if let Some((_type, number)) = value.trim_start().rsplit_once(' ') {
// Parse the value, remember it's in bytes!
if let Ok(number) = number.parse::<u64>() {
@ -247,3 +251,39 @@ pub async fn get_arc_data() -> crate::utils::error::Result<Option<MemHarvest>> {
},
}))
}
#[cfg(feature = "nvidia")]
pub async fn get_gpu_data() -> crate::utils::error::Result<Option<Vec<(String, MemHarvest)>>> {
use crate::data_harvester::nvidia::NVML_DATA;
if let Ok(nvml) = &*NVML_DATA {
if let Ok(ngpu) = nvml.device_count() {
let mut results = Vec::with_capacity(ngpu as usize);
for i in 0..ngpu {
if let Ok(device) = nvml.device_by_index(i) {
if let (Ok(name), Ok(mem)) = (device.name(), device.memory_info()) {
// add device memory in bytes
let mem_total_in_kib = mem.total / 1024;
let mem_used_in_kib = mem.used / 1024;
results.push((
name,
MemHarvest {
mem_total_in_kib,
mem_used_in_kib,
use_percent: if mem_total_in_kib == 0 {
None
} else {
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
},
},
));
}
}
}
Ok(Some(results))
} else {
Ok(None)
}
} else {
Ok(None)
}
}

View File

@ -1,26 +1,32 @@
//! Data collection for memory via sysinfo.
use crate::data_harvester::memory::MemHarvest;
use crate::data_harvester::memory::{MemCollect, MemHarvest};
use sysinfo::{System, SystemExt};
pub async fn get_mem_data(
sys: &System, actually_get: bool,
) -> (
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
) {
use futures::join;
pub async fn get_mem_data(sys: &System, actually_get: bool) -> MemCollect {
if !actually_get {
(Ok(None), Ok(None), Ok(None))
MemCollect {
ram: Ok(None),
swap: Ok(None),
#[cfg(feature = "zfs")]
arc: Ok(None),
#[cfg(feature = "gpu")]
gpus: Ok(None),
}
} else {
join!(get_ram_data(sys), get_swap_data(sys), get_arc_data())
MemCollect {
ram: get_ram_data(sys).await,
swap: get_swap_data(sys).await,
#[cfg(feature = "zfs")]
arc: get_arc_data().await,
#[cfg(feature = "gpu")]
gpus: get_gpu_data().await,
}
}
}
pub async fn get_ram_data(sys: &System) -> crate::utils::error::Result<Option<MemHarvest>> {
let (mem_total_in_kib, mem_used_in_kib) = (sys.total_memory() / 1024, sys.used_memory()) / 1024;
let (mem_total_in_kib, mem_used_in_kib) = (sys.total_memory() / 1024, sys.used_memory() / 1024);
Ok(Some(MemHarvest {
mem_total_in_kib,
@ -47,11 +53,8 @@ pub async fn get_swap_data(sys: &System) -> crate::utils::error::Result<Option<M
}))
}
#[cfg(feature = "zfs")]
pub async fn get_arc_data() -> crate::utils::error::Result<Option<MemHarvest>> {
#[cfg(not(feature = "zfs"))]
let (mem_total_in_kib, mem_used_in_kib) = (0, 0);
#[cfg(feature = "zfs")]
let (mem_total_in_kib, mem_used_in_kib) = {
#[cfg(target_os = "freebsd")]
{
@ -82,3 +85,39 @@ pub async fn get_arc_data() -> crate::utils::error::Result<Option<MemHarvest>> {
},
}))
}
#[cfg(feature = "nvidia")]
pub async fn get_gpu_data() -> crate::utils::error::Result<Option<Vec<(String, MemHarvest)>>> {
use crate::data_harvester::nvidia::NVML_DATA;
if let Ok(nvml) = &*NVML_DATA {
if let Ok(ngpu) = nvml.device_count() {
let mut results = Vec::with_capacity(ngpu as usize);
for i in 0..ngpu {
if let Ok(device) = nvml.device_by_index(i) {
if let (Ok(name), Ok(mem)) = (device.name(), device.memory_info()) {
// add device memory in bytes
let mem_total_in_kib = mem.total / 1024;
let mem_used_in_kib = mem.used / 1024;
results.push((
name,
MemHarvest {
mem_total_in_kib,
mem_used_in_kib,
use_percent: if mem_total_in_kib == 0 {
None
} else {
Some(mem_used_in_kib as f64 / mem_total_in_kib as f64 * 100.0)
},
},
));
}
}
}
Ok(Some(results))
} else {
Ok(None)
}
} else {
Ok(None)
}
}

View File

@ -0,0 +1,3 @@
use nvml_wrapper::{error::NvmlError, NVML};
use once_cell::sync::Lazy;
pub static NVML_DATA: Lazy<Result<NVML, NvmlError>> = Lazy::new(NVML::init);

View File

@ -5,12 +5,14 @@ use super::{
TemperatureType,
};
use nvml_wrapper::{enum_wrappers::device::TemperatureSensor, NVML};
use nvml_wrapper::enum_wrappers::device::TemperatureSensor;
use crate::data_harvester::nvidia::NVML_DATA;
pub fn add_nvidia_data(
temperature_vec: &mut Vec<TempHarvest>, temp_type: &TemperatureType, filter: &Option<Filter>,
) -> crate::utils::error::Result<()> {
if let Ok(nvml) = NVML::init() {
if let Ok(nvml) = &*NVML_DATA {
if let Ok(ngpu) = nvml.device_count() {
for i in 0..ngpu {
if let Ok(device) = nvml.device_by_index(i) {

View File

@ -210,6 +210,11 @@ fn main() -> Result<()> {
app.converted_data.arc_data =
convert_arc_data_points(&app.data_collection);
}
#[cfg(feature = "gpu")]
{
app.converted_data.gpu_data =
convert_gpu_data(&app.data_collection);
}
let (memory_labels, swap_labels) =
convert_mem_labels(&app.data_collection);

View File

@ -451,19 +451,29 @@ impl Painter {
}
};
let mut mem_rows = 0;
let mut mem_rows = 1;
if app_state.converted_data.swap_labels.is_some() {
mem_rows += 1; // add row for swap
}
#[cfg(feature = "zfs")]
{
let arc_data = &app_state.converted_data.arc_data;
if let Some(arc) = arc_data.last() {
if arc.1 != 0.0 {
mem_rows += 1; // add row for arc
}
if app_state.converted_data.arc_labels.is_some() {
mem_rows += 1; // add row for arc
}
}
mem_rows += 2; // add rows for SWAP and MEM
#[cfg(feature = "gpu")]
{
if let Some(gpu_data) = &app_state.converted_data.gpu_data {
mem_rows += gpu_data.len() as u16; // add row(s) for gpu
}
}
if mem_rows == 1 {
mem_rows += 1; // need at least 2 rows for RX and TX
}
let vertical_chunks = Layout::default()
.direction(Direction::Vertical)

View File

@ -18,6 +18,7 @@ pub struct CanvasColours {
pub ram_style: Style,
pub swap_style: Style,
pub arc_style: Style,
pub gpu_colour_styles: Vec<Style>,
pub rx_style: Style,
pub tx_style: Style,
pub total_rx_style: Style,
@ -51,6 +52,15 @@ impl Default for CanvasColours {
ram_style: Style::default().fg(STANDARD_FIRST_COLOUR),
swap_style: Style::default().fg(STANDARD_SECOND_COLOUR),
arc_style: Style::default().fg(STANDARD_THIRD_COLOUR),
gpu_colour_styles: vec![
Style::default().fg(STANDARD_FOURTH_COLOUR),
Style::default().fg(Color::LightBlue),
Style::default().fg(Color::LightRed),
Style::default().fg(Color::Cyan),
Style::default().fg(Color::Green),
Style::default().fg(Color::Blue),
Style::default().fg(Color::Red),
],
rx_style: Style::default().fg(STANDARD_FIRST_COLOUR),
tx_style: Style::default().fg(STANDARD_SECOND_COLOUR),
total_rx_style: Style::default().fg(STANDARD_THIRD_COLOUR),
@ -160,6 +170,11 @@ impl CanvasColours {
.context("Update 'arc_color' in your config file..")?;
}
if let Some(gpu_core_colors) = &colours.gpu_core_colors {
self.set_gpu_colours(gpu_core_colors)
.context("Update 'gpu_core_colors' in your config file..")?;
}
if let Some(rx_color) = &colours.rx_color {
self.set_rx_colour(rx_color)
.context("Update 'rx_color' in your config file..")?;
@ -268,6 +283,14 @@ impl CanvasColours {
Ok(())
}
pub fn set_gpu_colours(&mut self, colours: &[String]) -> error::Result<()> {
self.gpu_colour_styles = colours
.iter()
.map(|colour| get_style_from_config(colour))
.collect::<error::Result<Vec<Style>>>()?;
Ok(())
}
pub fn set_rx_colour(&mut self, colour: &str) -> error::Result<()> {
self.rx_style = get_style_from_config(colour)?;
Ok(())

View File

@ -14,23 +14,7 @@ impl Painter {
&self, f: &mut Frame<'_, B>, app_state: &mut App, draw_loc: Rect, widget_id: u64,
) {
let mem_data = &app_state.converted_data.mem_data;
let swap_data = &app_state.converted_data.swap_data;
let margined_loc = Layout::default()
.constraints({
#[cfg(feature = "zfs")]
{
[Constraint::Length(1); 3]
}
#[cfg(not(feature = "zfs"))]
{
[Constraint::Length(1); 2]
}
})
.direction(Direction::Vertical)
.horizontal_margin(1)
.split(draw_loc);
let mut draw_widgets: Vec<PipeGauge<'_>> = Vec::new();
if app_state.current_widget.widget_id == widget_id {
f.render_widget(
@ -41,13 +25,8 @@ impl Painter {
);
}
let ram_ratio = if let Some(mem) = mem_data.last() {
mem.1 / 100.0
} else {
0.0
};
let swap_ratio = if let Some(swap) = swap_data.last() {
swap.1 / 100.0
let ram_percentage = if let Some(mem) = mem_data.last() {
mem.1
} else {
0.0
};
@ -56,64 +35,129 @@ impl Painter {
let memory_fraction_label =
if let Some((_, label_frac)) = &app_state.converted_data.mem_labels {
label_frac.trim()
if app_state.basic_mode_use_percent {
format!("{:3.0}%", ram_percentage.round())
} else {
label_frac.trim().to_string()
}
} else {
EMPTY_MEMORY_FRAC_STRING
EMPTY_MEMORY_FRAC_STRING.to_string()
};
let swap_fraction_label =
if let Some((_, label_frac)) = &app_state.converted_data.swap_labels {
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
f.render_widget(
draw_widgets.push(
PipeGauge::default()
.ratio(ram_ratio)
.ratio(ram_percentage / 100.0)
.start_label("RAM")
.inner_label(memory_fraction_label)
.label_style(self.colours.ram_style)
.gauge_style(self.colours.ram_style),
margined_loc[0],
);
f.render_widget(
PipeGauge::default()
.ratio(swap_ratio)
.start_label("SWP")
.inner_label(swap_fraction_label)
.label_style(self.colours.swap_style)
.gauge_style(self.colours.swap_style),
margined_loc[1],
);
let swap_data = &app_state.converted_data.swap_data;
let swap_percentage = if let Some(swap) = swap_data.last() {
swap.1
} else {
0.0
};
if let Some((_, label_frac)) = &app_state.converted_data.swap_labels {
let swap_fraction_label = if app_state.basic_mode_use_percent {
format!("{:3.0}%", swap_percentage.round())
} else {
label_frac.trim().to_string()
};
draw_widgets.push(
PipeGauge::default()
.ratio(swap_percentage / 100.0)
.start_label("SWP")
.inner_label(swap_fraction_label)
.label_style(self.colours.swap_style)
.gauge_style(self.colours.swap_style),
);
}
#[cfg(feature = "zfs")]
{
let arc_data = &app_state.converted_data.arc_data;
let arc_ratio = if let Some(arc) = arc_data.last() {
arc.1 / 100.0
let arc_percentage = if let Some(arc) = arc_data.last() {
arc.1
} else {
0.0
};
let arc_fraction_label =
if let Some((_, label_frac)) = &app_state.converted_data.arc_labels {
label_frac.trim()
if let Some((_, label_frac)) = &app_state.converted_data.arc_labels {
let arc_fraction_label = if app_state.basic_mode_use_percent {
format!("{:3.0}%", arc_percentage.round())
} else {
EMPTY_MEMORY_FRAC_STRING
label_frac.trim().to_string()
};
f.render_widget(
PipeGauge::default()
.ratio(arc_ratio)
.start_label("ARC")
.inner_label(arc_fraction_label)
.label_style(self.colours.arc_style)
.gauge_style(self.colours.arc_style),
margined_loc[2],
);
draw_widgets.push(
PipeGauge::default()
.ratio(arc_percentage / 100.0)
.start_label("ARC")
.inner_label(arc_fraction_label)
.label_style(self.colours.arc_style)
.gauge_style(self.colours.arc_style),
);
}
}
#[cfg(feature = "gpu")]
{
let gpu_styles = &self.colours.gpu_colour_styles;
let mut color_index = 0;
if let Some(gpu_data) = &app_state.converted_data.gpu_data {
gpu_data.iter().for_each(|gpu_data_vec| {
let gpu_data = gpu_data_vec.points.as_slice();
let gpu_percentage = if let Some(gpu) = gpu_data.last() {
gpu.1
} else {
0.0
};
let trimmed_gpu_frac = {
if app_state.basic_mode_use_percent {
format!("{:3.0}%", gpu_percentage.round())
} else {
gpu_data_vec.mem_total.trim().to_string()
}
};
let style = {
if gpu_styles.is_empty() {
tui::style::Style::default()
} else if color_index >= gpu_styles.len() {
// cycle styles
color_index = 1;
gpu_styles[color_index - 1]
} else {
color_index += 1;
gpu_styles[color_index - 1]
}
};
draw_widgets.push(
PipeGauge::default()
.ratio(gpu_percentage / 100.0)
.start_label("GPU")
.inner_label(trimmed_gpu_frac)
.label_style(style)
.gauge_style(style),
);
});
}
}
let margined_loc = Layout::default()
.constraints(vec![Constraint::Length(1); draw_widgets.len()])
.direction(Direction::Vertical)
.horizontal_margin(1)
.split(draw_loc);
draw_widgets
.into_iter()
.enumerate()
.for_each(|(index, widget)| {
f.render_widget(widget, margined_loc[index]);
});
// Update draw loc in widget map
if app_state.should_get_widget_bounds() {
if let Some(widget) = app_state.widget_map.get_mut(&widget_id) {

View File

@ -29,18 +29,22 @@ impl Painter {
draw_loc,
);
let points = {
let mut size = 0;
let mut size = 1;
if app_state.converted_data.swap_labels.is_some() {
size += 1; // add capacity for SWAP
}
#[cfg(feature = "zfs")]
{
let arc_data = &app_state.converted_data.arc_data;
if let Some(arc) = arc_data.last() {
if arc.1 != 0.0 {
size += 1; // add capacity for ARC
}
if app_state.converted_data.arc_labels.is_some() {
size += 1; // add capacity for ARC
}
}
#[cfg(feature = "gpu")]
{
if let Some(gpu_data) = &app_state.converted_data.gpu_data {
size += gpu_data.len(); // add row(s) for gpu
}
}
size += 2; // add capacity for RAM and SWP
let mut points = Vec::with_capacity(size);
if let Some((label_percent, label_frac)) = &app_state.converted_data.mem_labels {
@ -61,16 +65,39 @@ impl Painter {
}
#[cfg(feature = "zfs")]
if let Some((label_percent, label_frac)) = &app_state.converted_data.arc_labels {
let arc_data = &app_state.converted_data.arc_data;
if let Some(arc) = arc_data.last() {
if arc.1 != 0.0 {
let arc_label = format!("ARC:{}{}", label_percent, label_frac);
let arc_label = format!("ARC:{}{}", label_percent, label_frac);
points.push(GraphData {
points: &app_state.converted_data.arc_data,
style: self.colours.arc_style,
name: Some(arc_label.into()),
});
}
#[cfg(feature = "gpu")]
{
if let Some(gpu_data) = &app_state.converted_data.gpu_data {
let mut color_index = 0;
let gpu_styles = &self.colours.gpu_colour_styles;
gpu_data.iter().for_each(|gpu| {
let gpu_label =
format!("{}:{}{}", gpu.name, gpu.mem_percent, gpu.mem_total);
let style = {
if gpu_styles.is_empty() {
tui::style::Style::default()
} else if color_index >= gpu_styles.len() {
// cycle styles
color_index = 1;
gpu_styles[color_index - 1]
} else {
color_index += 1;
gpu_styles[color_index - 1]
}
};
points.push(GraphData {
points: &app_state.converted_data.arc_data,
style: self.colours.arc_style,
name: Some(arc_label.into()),
points: gpu.points.as_slice(),
style,
name: Some(gpu_label.into()),
});
}
});
}
}

View File

@ -41,6 +41,15 @@ pub static DEFAULT_LIGHT_MODE_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(||
ram_color: Some("blue".to_string()),
swap_color: Some("red".to_string()),
arc_color: Some("LightBlue".to_string()),
gpu_core_colors: Some(vec![
"LightGreen".to_string(),
"LightCyan".to_string(),
"LightRed".to_string(),
"Cyan".to_string(),
"Green".to_string(),
"Blue".to_string(),
"Red".to_string(),
]),
rx_color: Some("blue".to_string()),
tx_color: Some("red".to_string()),
rx_total_color: Some("LightBlue".to_string()),
@ -86,6 +95,15 @@ pub static GRUVBOX_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColo
ram_color: Some("#8ec07c".to_string()),
swap_color: Some("#fabd2f".to_string()),
arc_color: Some("#689d6a".to_string()),
gpu_core_colors: Some(vec![
"#d79921".to_string(),
"#458588".to_string(),
"#b16286".to_string(),
"#fe8019".to_string(),
"#b8bb26".to_string(),
"#cc241d".to_string(),
"#98971a".to_string(),
]),
rx_color: Some("#8ec07c".to_string()),
tx_color: Some("#fabd2f".to_string()),
rx_total_color: Some("#689d6a".to_string()),
@ -132,6 +150,15 @@ pub static GRUVBOX_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| Conf
ram_color: Some("#427b58".to_string()),
swap_color: Some("#cc241d".to_string()),
arc_color: Some("#689d6a".to_string()),
gpu_core_colors: Some(vec![
"#9d0006".to_string(),
"#98971a".to_string(),
"#d79921".to_string(),
"#458588".to_string(),
"#b16286".to_string(),
"#fe8019".to_string(),
"#b8bb26".to_string(),
]),
rx_color: Some("#427b58".to_string()),
tx_color: Some("#cc241d".to_string()),
rx_total_color: Some("#689d6a".to_string()),
@ -166,6 +193,15 @@ pub static NORD_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigColours
ram_color: Some("#88c0d0".to_string()),
swap_color: Some("#d08770".to_string()),
arc_color: Some("#5e81ac".to_string()),
gpu_core_colors: Some(vec![
"#8fbcbb".to_string(),
"#81a1c1".to_string(),
"#d8dee9".to_string(),
"#b48ead".to_string(),
"#a3be8c".to_string(),
"#ebcb8b".to_string(),
"#bf616a".to_string(),
]),
rx_color: Some("#88c0d0".to_string()),
tx_color: Some("#d08770".to_string()),
rx_total_color: Some("#5e81ac".to_string()),
@ -200,6 +236,15 @@ pub static NORD_LIGHT_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(|| ConfigC
ram_color: Some("#81a1c1".to_string()),
swap_color: Some("#d08770".to_string()),
arc_color: Some("#5e81ac".to_string()),
gpu_core_colors: Some(vec![
"#8fbcbb".to_string(),
"#88c0d0".to_string(),
"#4c566a".to_string(),
"#b48ead".to_string(),
"#a3be8c".to_string(),
"#ebcb8b".to_string(),
"#bf616a".to_string(),
]),
rx_color: Some("#81a1c1".to_string()),
tx_color: Some("#d08770".to_string()),
rx_total_color: Some("#5e81ac".to_string()),
@ -520,6 +565,8 @@ pub const CONFIG_TEXT: &str = r##"# This is a default config file for bottom. A
#swap_color="LightYellow"
# Represents the colour ARC will use in the memory legend and graph.
#arc_color="LightCyan"
# Represents the colour the GPU will use in the memory legend and graph.
#gpu_core_colors=["LightGreen", "LightBlue", "LightRed", "Cyan", "Green", "Blue", "Red"]
# Represents the colour rx will use in the network legend and graph.
#rx_color="LightCyan"
# Represents the colour tx will use in the network legend and graph.

View File

@ -64,14 +64,17 @@ pub struct ConvertedData {
pub mem_labels: Option<(String, String)>,
pub swap_labels: Option<(String, String)>,
#[cfg(feature = "zfs")]
pub arc_labels: Option<(String, String)>,
pub mem_data: Vec<Point>, // TODO: Switch this and all data points over to a better data structure...
pub swap_data: Vec<Point>,
#[cfg(feature = "zfs")]
pub arc_data: Vec<Point>,
pub load_avg_data: [f32; 3],
pub cpu_data: Vec<CpuWidgetData>,
pub battery_data: Vec<ConvertedBatteryData>,
#[cfg(feature = "gpu")]
pub gpu_data: Option<Vec<ConvertedGpuData>>,
}
impl ConvertedData {
@ -618,6 +621,97 @@ pub fn convert_arc_data_points(
result
}
#[cfg(feature = "gpu")]
#[derive(Default, Debug)]
pub struct ConvertedGpuData {
pub name: String,
pub mem_total: String,
pub mem_percent: String,
pub points: Vec<Point>,
}
#[cfg(feature = "gpu")]
pub fn convert_gpu_data(
current_data: &crate::app::data_farmer::DataCollection,
) -> Option<Vec<ConvertedGpuData>> {
/// Returns the unit type and denominator for given total amount of memory in kibibytes.
fn return_unit_and_denominator_for_mem_kib(mem_total_kib: u64) -> (&'static str, f64) {
if mem_total_kib < 1024 {
// Stay with KiB
("KiB", 1.0)
} else if mem_total_kib < MEBI_LIMIT {
// Use MiB
("MiB", KIBI_LIMIT_F64)
} else if mem_total_kib < GIBI_LIMIT {
// Use GiB
("GiB", MEBI_LIMIT_F64)
} else {
// Use TiB
("TiB", GIBI_LIMIT_F64)
}
}
let current_time = current_data.current_instant;
// convert points
let mut point_vec: Vec<Vec<Point>> = Vec::with_capacity(current_data.gpu_harvest.len());
for (time, data) in &current_data.timed_data_vec {
data.gpu_data.iter().enumerate().for_each(|(index, point)| {
if let Some(data_point) = point {
let time_from_start: f64 =
(current_time.duration_since(*time).as_millis() as f64).floor();
if let Some(point_slot) = point_vec.get_mut(index) {
point_slot.push((-time_from_start, *data_point));
} else {
point_vec.push(vec![(-time_from_start, *data_point)]);
}
}
});
if *time == current_time {
break;
}
}
// convert labels
let results = current_data
.gpu_harvest
.iter()
.zip(point_vec.into_iter())
.map(|(gpu, points)| {
let short_name = {
let last_words = gpu.0.split_whitespace().rev().take(2).collect::<Vec<_>>();
let short_name = format!("{} {}", last_words[1], last_words[0]);
short_name
};
ConvertedGpuData {
name: short_name,
points,
mem_percent: format!("{:3.0}%", gpu.1.use_percent.unwrap_or(0.0)),
mem_total: {
let (unit, denominator) =
return_unit_and_denominator_for_mem_kib(gpu.1.mem_total_in_kib);
format!(
" {:.1}{}/{:.1}{}",
gpu.1.mem_used_in_kib as f64 / denominator,
unit,
(gpu.1.mem_total_in_kib as f64 / denominator),
unit
)
},
}
})
.collect::<Vec<ConvertedGpuData>>();
if !results.is_empty() {
Some(results)
} else {
None
}
}
#[cfg(test)]
mod test {
use super::*;

View File

@ -350,6 +350,10 @@ pub fn update_data(app: &mut App) {
app.converted_data.arc_data = convert_arc_data_points(data_source);
}
#[cfg(feature = "gpu")]
{
app.converted_data.gpu_data = convert_gpu_data(data_source);
}
app.mem_state.force_update = None;
}

View File

@ -199,6 +199,7 @@ pub struct ConfigColours {
pub ram_color: Option<String>,
pub swap_color: Option<String>,
pub arc_color: Option<String>,
pub gpu_core_colors: Option<Vec<String>>,
pub rx_color: Option<String>,
pub tx_color: Option<String>,
pub rx_total_color: Option<String>, // These only affect basic mode.