Move to tui-rs' built in linear interpolation

This commit is contained in:
ClementTsang 2020-04-20 00:29:59 -04:00
parent 0574678746
commit 7004649a6d
6 changed files with 140 additions and 199 deletions

View file

@ -34,9 +34,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Use custom legend-hiding to stop hiding legends for memory and network widgets. - Use custom legend-hiding to stop hiding legends for memory and network widgets.
- In addition, changed to using only legends within the graph for network, as well as redesigned the legend. - In addition, changed to using only legends within the graph for network, as well as redesigned the legend.
The old legend style can still be used via the `--use_old_network_legend` flag or `use_old_network_legend = true` option. The old legend style can still be used via the `--use_old_network_legend` flag or `use_old_network_legend = true` config option.
- Allow for option to hide the header gap on tables. - Allow for option to hide the header gap on tables via `--hide_table_gap` or `hide_table_gap = true`.
- Switch to stateful widget style for tables.
- Switch to using tui-rs' new built in linear interpolation rather than doing it manually.
### Bug Fixes ### Bug Fixes

View file

@ -21,18 +21,14 @@ use crate::data_harvester::{
pub type TimeOffset = f64; pub type TimeOffset = f64;
pub type Value = f64; pub type Value = f64;
pub type JoinedDataPoints = (Value, Vec<(TimeOffset, Value)>);
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct TimedData { pub struct TimedData {
pub rx_data: JoinedDataPoints, pub rx_data: Value,
pub tx_data: JoinedDataPoints, pub tx_data: Value,
pub cpu_data: Vec<JoinedDataPoints>, pub cpu_data: Vec<Value>,
pub mem_data: JoinedDataPoints, pub mem_data: Value,
pub swap_data: JoinedDataPoints, pub swap_data: Value,
// Unused for now
// pub io_data : JoinedDataPoints
// pub temp_data: JoinedDataPoints,
} }
/// AppCollection represents the pooled data stored within the main app /// AppCollection represents the pooled data stored within the main app
@ -121,19 +117,19 @@ impl DataCollection {
// Network // Network
if let Some(network) = &harvested_data.network { if let Some(network) = &harvested_data.network {
self.eat_network(network, harvested_time, &mut new_entry); self.eat_network(network, &mut new_entry);
} }
// Memory and Swap // Memory and Swap
if let Some(memory) = &harvested_data.memory { if let Some(memory) = &harvested_data.memory {
if let Some(swap) = &harvested_data.swap { if let Some(swap) = &harvested_data.swap {
self.eat_memory_and_swap(memory, swap, harvested_time, &mut new_entry); self.eat_memory_and_swap(memory, swap, &mut new_entry);
} }
} }
// CPU // CPU
if let Some(cpu) = &harvested_data.cpu { if let Some(cpu) = &harvested_data.cpu {
self.eat_cpu(cpu, harvested_time, &mut new_entry); self.eat_cpu(cpu, &mut new_entry);
} }
// Temp // Temp
@ -164,7 +160,7 @@ impl DataCollection {
} }
fn eat_memory_and_swap( fn eat_memory_and_swap(
&mut self, memory: &mem::MemHarvest, swap: &mem::MemHarvest, harvested_time: Instant, &mut self, memory: &mem::MemHarvest, swap: &mem::MemHarvest,
new_entry: &mut TimedData, new_entry: &mut TimedData,
) { ) {
// Memory // Memory
@ -172,13 +168,7 @@ impl DataCollection {
0 => 0f64, 0 => 0f64,
total => (memory.mem_used_in_mb as f64) / (total as f64) * 100.0, total => (memory.mem_used_in_mb as f64) / (total as f64) * 100.0,
}; };
let mem_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() { new_entry.mem_data = mem_percent;
generate_joining_points(*time, last_pt.mem_data.0, harvested_time, mem_percent)
} else {
Vec::new()
};
let mem_pt = (mem_percent, mem_joining_pts);
new_entry.mem_data = mem_pt;
// Swap // Swap
if swap.mem_total_in_mb > 0 { if swap.mem_total_in_mb > 0 {
@ -186,13 +176,7 @@ impl DataCollection {
0 => 0f64, 0 => 0f64,
total => (swap.mem_used_in_mb as f64) / (total as f64) * 100.0, total => (swap.mem_used_in_mb as f64) / (total as f64) * 100.0,
}; };
let swap_joining_pt = if let Some((time, last_pt)) = self.timed_data_vec.last() { new_entry.swap_data = swap_percent;
generate_joining_points(*time, last_pt.swap_data.0, harvested_time, swap_percent)
} else {
Vec::new()
};
let swap_pt = (swap_percent, swap_joining_pt);
new_entry.swap_data = swap_pt;
} }
// In addition copy over latest data for easy reference // In addition copy over latest data for easy reference
@ -201,7 +185,7 @@ impl DataCollection {
} }
fn eat_network( fn eat_network(
&mut self, network: &network::NetworkHarvest, harvested_time: Instant, &mut self, network: &network::NetworkHarvest,
new_entry: &mut TimedData, new_entry: &mut TimedData,
) { ) {
// RX // RX
@ -210,14 +194,7 @@ impl DataCollection {
} else { } else {
0.0 0.0
}; };
new_entry.rx_data = logged_rx_val;
let rx_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.rx_data.0, harvested_time, logged_rx_val)
} else {
Vec::new()
};
let rx_pt = (logged_rx_val, rx_joining_pts);
new_entry.rx_data = rx_pt;
// TX // TX
let logged_tx_val = if network.tx as f64 > 0.0 { let logged_tx_val = if network.tx as f64 > 0.0 {
@ -225,38 +202,20 @@ impl DataCollection {
} else { } else {
0.0 0.0
}; };
new_entry.tx_data = logged_tx_val;
let tx_joining_pts = if let Some((time, last_pt)) = self.timed_data_vec.last() {
generate_joining_points(*time, last_pt.tx_data.0, harvested_time, logged_tx_val)
} else {
Vec::new()
};
let tx_pt = (logged_tx_val, tx_joining_pts);
new_entry.tx_data = tx_pt;
// In addition copy over latest data for easy reference // In addition copy over latest data for easy reference
self.network_harvest = network.clone(); self.network_harvest = network.clone();
} }
fn eat_cpu( fn eat_cpu(
&mut self, cpu: &[cpu::CPUData], harvested_time: Instant, new_entry: &mut TimedData, &mut self, cpu: &[cpu::CPUData], new_entry: &mut TimedData,
) { ) {
// Note this only pre-calculates the data points - the names will be // Note this only pre-calculates the data points - the names will be
// within the local copy of cpu_harvest. Since it's all sequential // within the local copy of cpu_harvest. Since it's all sequential
// it probably doesn't matter anyways. // it probably doesn't matter anyways.
if let Some((time, last_pt)) = self.timed_data_vec.last() { cpu.iter()
for (cpu, last_pt_data) in cpu.iter().zip(&last_pt.cpu_data) { .for_each(|cpu| new_entry.cpu_data.push(cpu.cpu_usage));
let cpu_joining_pts =
generate_joining_points(*time, last_pt_data.0, harvested_time, cpu.cpu_usage);
let cpu_pt = (cpu.cpu_usage, cpu_joining_pts);
new_entry.cpu_data.push(cpu_pt);
}
} else {
for cpu in cpu.iter() {
let cpu_pt = (cpu.cpu_usage, Vec::new());
new_entry.cpu_data.push(cpu_pt);
}
}
self.cpu_harvest = cpu.to_vec(); self.cpu_harvest = cpu.to_vec();
} }
@ -309,36 +268,3 @@ impl DataCollection {
self.battery_harvest = list_of_batteries.to_vec(); self.battery_harvest = list_of_batteries.to_vec();
} }
} }
pub fn generate_joining_points(
start_x: Instant, start_y: f64, end_x: Instant, end_y: f64,
) -> Vec<(TimeOffset, Value)> {
let mut points: Vec<(TimeOffset, Value)> = Vec::new();
// Convert time floats first:
let tmp_time_diff = (end_x).duration_since(start_x).as_millis() as f64;
let time_difference = if tmp_time_diff == 0.0 {
0.001
} else {
tmp_time_diff
};
let value_difference = end_y - start_y;
// Let's generate... about this many points!
let num_points = std::cmp::min(
std::cmp::max(
(value_difference.abs() / time_difference * 2000.0) as u64,
50,
),
2000,
);
for itx in (0..num_points).step_by(2) {
points.push((
time_difference - (itx as f64 / num_points as f64 * time_difference),
start_y + (itx as f64 / num_points as f64 * value_difference),
));
}
points
}

View file

@ -146,7 +146,7 @@ impl CpuGraphWidget for Painter {
.enumerate() .enumerate()
.rev() .rev()
.filter_map(|(itx, (cpu, cpu_show_vec))| { .filter_map(|(itx, (cpu, cpu_show_vec))| {
if *cpu_show_vec { if *cpu_show_vec && !cpu.cpu_data.is_empty() {
Some( Some(
Dataset::default() Dataset::default()
.marker(if use_dot { .marker(if use_dot {
@ -160,7 +160,8 @@ impl CpuGraphWidget for Painter {
self.colours.cpu_colour_styles self.colours.cpu_colour_styles
[itx % self.colours.cpu_colour_styles.len()] [itx % self.colours.cpu_colour_styles.len()]
}) })
.data(&cpu.cpu_data[..]), .data(&cpu.cpu_data[..])
.graph_type(tui::widgets::GraphType::Line),
) )
} else { } else {
None None

View file

@ -61,26 +61,37 @@ impl MemGraphWidget for Painter {
.bounds([-0.5, 100.5]) .bounds([-0.5, 100.5])
.labels(&["0%", "100%"]); .labels(&["0%", "100%"]);
let mem_canvas_vec: Vec<Dataset<'_>> = vec![ let mut mem_canvas_vec: Vec<Dataset<'_>> = vec![];
Dataset::default()
.name(&app_state.canvas_data.mem_label) if !mem_data.is_empty() {
.marker(if app_state.app_config_fields.use_dot { mem_canvas_vec.push(
Marker::Dot Dataset::default()
} else { .name(&app_state.canvas_data.mem_label)
Marker::Braille .marker(if app_state.app_config_fields.use_dot {
}) Marker::Dot
.style(self.colours.ram_style) } else {
.data(&mem_data), Marker::Braille
Dataset::default() })
.name(&app_state.canvas_data.swap_label) .style(self.colours.ram_style)
.marker(if app_state.app_config_fields.use_dot { .data(&mem_data)
Marker::Dot .graph_type(tui::widgets::GraphType::Line),
} else { );
Marker::Braille }
})
.style(self.colours.swap_style) if !swap_data.is_empty() {
.data(&swap_data), mem_canvas_vec.push(
]; Dataset::default()
.name(&app_state.canvas_data.swap_label)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.swap_style)
.data(&swap_data)
.graph_type(tui::widgets::GraphType::Line),
);
}
let title = if app_state.is_expanded { let title = if app_state.is_expanded {
const TITLE_BASE: &str = " Memory ── Esc to go back "; const TITLE_BASE: &str = " Memory ── Esc to go back ";

View file

@ -132,59 +132,90 @@ impl NetworkGraphWidget for Painter {
}; };
let dataset = if app_state.app_config_fields.use_old_network_legend && !hide_legend { let dataset = if app_state.app_config_fields.use_old_network_legend && !hide_legend {
vec![ let mut ret_val = vec![];
Dataset::default()
.name(format!("RX: {:7}", app_state.canvas_data.rx_display)) if !network_data_rx.is_empty() {
.marker(if app_state.app_config_fields.use_dot { ret_val.push(
Marker::Dot Dataset::default()
} else { .name(format!("RX: {:7}", app_state.canvas_data.rx_display))
Marker::Braille .marker(if app_state.app_config_fields.use_dot {
}) Marker::Dot
.style(self.colours.rx_style) } else {
.data(&network_data_rx), Marker::Braille
Dataset::default() })
.name(format!("TX: {:7}", app_state.canvas_data.tx_display)) .style(self.colours.rx_style)
.marker(if app_state.app_config_fields.use_dot { .data(&network_data_rx)
Marker::Dot .graph_type(tui::widgets::GraphType::Line),
} else { );
Marker::Braille }
})
.style(self.colours.tx_style) if !network_data_tx.is_empty() {
.data(&network_data_tx), ret_val.push(
Dataset::default() Dataset::default()
.name(format!( .name(format!("TX: {:7}", app_state.canvas_data.tx_display))
"Total RX: {:7}", .marker(if app_state.app_config_fields.use_dot {
app_state.canvas_data.total_rx_display Marker::Dot
)) } else {
.style(self.colours.total_rx_style), Marker::Braille
})
.style(self.colours.tx_style)
.data(&network_data_tx)
.graph_type(tui::widgets::GraphType::Line),
);
ret_val.push(
Dataset::default()
.name(format!(
"Total RX: {:7}",
app_state.canvas_data.total_rx_display
))
.style(self.colours.total_rx_style),
);
}
ret_val.push(
Dataset::default() Dataset::default()
.name(format!( .name(format!(
"Total TX: {:7}", "Total TX: {:7}",
app_state.canvas_data.total_tx_display app_state.canvas_data.total_tx_display
)) ))
.style(self.colours.total_tx_style), .style(self.colours.total_tx_style),
] );
ret_val
} else { } else {
vec![ let mut ret_val = vec![];
Dataset::default()
.name(&app_state.canvas_data.rx_display) if !network_data_rx.is_empty() {
.marker(if app_state.app_config_fields.use_dot { ret_val.push(
Marker::Dot Dataset::default()
} else { .name(&app_state.canvas_data.rx_display)
Marker::Braille .marker(if app_state.app_config_fields.use_dot {
}) Marker::Dot
.style(self.colours.rx_style) } else {
.data(&network_data_rx), Marker::Braille
Dataset::default() })
.name(&app_state.canvas_data.tx_display) .style(self.colours.rx_style)
.marker(if app_state.app_config_fields.use_dot { .data(&network_data_rx)
Marker::Dot .graph_type(tui::widgets::GraphType::Line),
} else { );
Marker::Braille }
})
.style(self.colours.tx_style) if !network_data_tx.is_empty() {
.data(&network_data_tx), ret_val.push(
] Dataset::default()
.name(&app_state.canvas_data.tx_display)
.marker(if app_state.app_config_fields.use_dot {
Marker::Dot
} else {
Marker::Braille
})
.style(self.colours.tx_style)
.data(&network_data_tx)
.graph_type(tui::widgets::GraphType::Line),
);
}
ret_val
}; };
f.render_widget( f.render_widget(

View file

@ -160,15 +160,8 @@ pub fn convert_cpu_data_points(
} }
if let Some(cpu_data) = cpu_data_vector.get_mut(itx) { if let Some(cpu_data) = cpu_data_vector.get_mut(itx) {
cpu_data.legend_value = format!("{:.0}%", cpu.0.round()); cpu_data.legend_value = format!("{:.0}%", cpu.round());
cpu_data.cpu_data.push((-time_from_start, *cpu));
//Insert joiner points
for &(joiner_offset, joiner_val) in &cpu.1 {
let offset_time = time_from_start + joiner_offset as f64;
cpu_data.cpu_data.push((-offset_time, joiner_val));
}
cpu_data.cpu_data.push((-time_from_start, cpu.0));
} }
} }
@ -197,13 +190,7 @@ pub fn convert_mem_data_points(
for (time, data) in &current_data.timed_data_vec { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
//Insert joiner points result.push((-time_from_start, data.mem_data));
for &(joiner_offset, joiner_val) in &data.mem_data.1 {
let offset_time = time_from_start + joiner_offset as f64;
result.push((-offset_time, joiner_val));
}
result.push((-time_from_start, data.mem_data.0));
if *time == current_time { if *time == current_time {
break; break;
@ -229,14 +216,7 @@ pub fn convert_swap_data_points(
for (time, data) in &current_data.timed_data_vec { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
result.push((-time_from_start, data.swap_data));
//Insert joiner points
for &(joiner_offset, joiner_val) in &data.swap_data.1 {
let offset_time = time_from_start + joiner_offset as f64;
result.push((-offset_time, joiner_val));
}
result.push((-time_from_start, data.swap_data.0));
if *time == current_time { if *time == current_time {
break; break;
@ -300,20 +280,8 @@ pub fn get_rx_tx_data_points(
for (time, data) in &current_data.timed_data_vec { for (time, data) in &current_data.timed_data_vec {
let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor(); let time_from_start: f64 = (current_time.duration_since(*time).as_millis() as f64).floor();
rx.push((-time_from_start, data.rx_data));
//Insert joiner points tx.push((-time_from_start, data.tx_data));
for &(joiner_offset, joiner_val) in &data.rx_data.1 {
let offset_time = time_from_start + joiner_offset as f64;
rx.push((-offset_time, joiner_val));
}
for &(joiner_offset, joiner_val) in &data.tx_data.1 {
let offset_time = time_from_start + joiner_offset as f64;
tx.push((-offset_time, joiner_val));
}
rx.push((-time_from_start, data.rx_data.0));
tx.push((-time_from_start, data.tx_data.0));
if *time == current_time { if *time == current_time {
break; break;