feature: Add zfs feature flag for arc memory (#784)

* freebsd clippy

* add arc support

* Code Review: moved runtime cfg checks to compile time and formatting

* remove compile platform checks

* add zfs feature flag to get_arc_data
This commit is contained in:
Justin Martin 2022-08-22 02:47:22 -04:00 committed by GitHub
parent 11657aa0ab
commit 6e0bc96093
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 375 additions and 8 deletions

34
Cargo.lock generated
View file

@ -236,6 +236,7 @@ dependencies = [
"serde_json",
"smol",
"starship-battery",
"sysctl",
"sysinfo",
"thiserror",
"time",
@ -1358,6 +1359,15 @@ version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
@ -1511,6 +1521,19 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "sysctl"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225e483f02d0ad107168dc57381a8a40c3aeea6abe47f37506931f861643cfa8"
dependencies = [
"bitflags",
"byteorder",
"libc",
"thiserror",
"walkdir",
]
[[package]]
name = "sysinfo"
version = "0.23.10"
@ -1681,6 +1704,17 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d5b2c62b4012a3e1eca5a7e077d13b3bf498c4073e33ccd58626607748ceeca"
[[package]]
name = "walkdir"
version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56"
dependencies = [
"same-file",
"winapi",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.10.2+wasi-snapshot-preview1"

View file

@ -41,11 +41,12 @@ opt-level = 3
codegen-units = 1
[features]
default = ["fern", "log", "battery", "gpu"]
default = ["fern", "log", "battery", "gpu", "zfs"]
battery = ["starship-battery"]
deploy = ["battery", "gpu"]
gpu = ["nvidia"]
nvidia = ["nvml-wrapper"]
zfs = ["sysctl"]
[dependencies]
anyhow = "1.0.57"
@ -96,6 +97,7 @@ winapi = "0.3.9"
[target.'cfg(target_os = "freebsd")'.dependencies]
serde_json = { version = "1.0.82" }
sysctl = { version = "0.4.6", optional = true }
[dev-dependencies]
assert_cmd = "2.0.4"

View file

@ -41,6 +41,8 @@ pub struct TimedData {
pub load_avg_data: [f32; 3],
pub mem_data: Option<Value>,
pub swap_data: Option<Value>,
#[cfg(feature = "zfs")]
pub arc_data: Option<Value>,
}
pub type StringPidMap = FxHashMap<String, Vec<Pid>>;
@ -161,6 +163,8 @@ pub struct DataCollection {
pub temp_harvest: Vec<temperature::TempHarvest>,
#[cfg(feature = "battery")]
pub battery_harvest: Vec<batteries::BatteryHarvest>,
#[cfg(feature = "zfs")]
pub arc_harvest: memory::MemHarvest,
}
impl Default for DataCollection {
@ -182,6 +186,8 @@ impl Default for DataCollection {
temp_harvest: Vec::default(),
#[cfg(feature = "battery")]
battery_harvest: Vec::default(),
#[cfg(feature = "zfs")]
arc_harvest: memory::MemHarvest::default(),
}
}
}
@ -202,6 +208,10 @@ impl DataCollection {
{
self.battery_harvest = Vec::default();
}
#[cfg(feature = "zfs")]
{
self.arc_harvest = memory::MemHarvest::default();
}
}
pub fn freeze(&mut self) {
@ -247,6 +257,12 @@ impl DataCollection {
self.eat_memory_and_swap(memory, swap, &mut new_entry);
}
#[cfg(feature = "zfs")]
{
if let Some(arc) = harvested_data.arc {
self.eat_arc(arc, &mut new_entry);
}
}
// CPU
if let Some(cpu) = harvested_data.cpu {
self.eat_cpu(cpu, &mut new_entry);
@ -427,4 +443,11 @@ impl DataCollection {
fn eat_battery(&mut self, list_of_batteries: Vec<batteries::BatteryHarvest>) {
self.battery_harvest = list_of_batteries;
}
#[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;
}
}

View file

@ -40,6 +40,8 @@ pub struct Data {
pub io: Option<disks::IoHarvest>,
#[cfg(feature = "battery")]
pub list_of_batteries: Option<Vec<batteries::BatteryHarvest>>,
#[cfg(feature = "zfs")]
pub arc: Option<memory::MemHarvest>,
}
impl Default for Data {
@ -57,6 +59,8 @@ impl Default for Data {
network: None,
#[cfg(feature = "battery")]
list_of_batteries: None,
#[cfg(feature = "zfs")]
arc: None,
}
}
}
@ -75,6 +79,10 @@ impl Data {
if let Some(network) = &mut self.network {
network.first_run_cleanup();
}
#[cfg(feature = "zfs")]
{
self.arc = None;
}
}
}
@ -421,6 +429,11 @@ impl DataCollector {
self.data.swap = swap;
}
#[cfg(feature = "zfs")]
if let Ok(arc) = mem_res.2 {
self.data.arc = arc;
}
if let Ok(disks) = disk_res {
self.data.disks = disks;
}

View file

@ -10,7 +10,7 @@ use crate::app::data_harvester::cpu::LoadAvgHarvest;
pub async fn get_cpu_data_list(
sys: &sysinfo::System, show_average_cpu: bool,
_previous_cpu_times: &mut Vec<(PastCpuWork, PastCpuTotal)>,
_previous_cpu_times: &mut [(PastCpuWork, PastCpuTotal)],
_previous_average_cpu_time: &mut Option<(PastCpuWork, PastCpuTotal)>,
) -> crate::error::Result<CpuHarvest> {
let mut cpu_deque: VecDeque<_> = sys

View file

@ -7,13 +7,14 @@ pub async fn get_mem_data(
) -> (
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
) {
use futures::join;
if !actually_get {
(Ok(None), Ok(None))
(Ok(None), Ok(None), Ok(None))
} else {
join!(get_ram_data(), get_swap_data())
join!(get_ram_data(), get_swap_data(), get_arc_data())
}
}
@ -168,3 +169,81 @@ pub async fn get_swap_data() -> crate::utils::error::Result<Option<MemHarvest>>
},
}))
}
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;
use smol::fs::read_to_string;
let arcinfo = read_to_string("/proc/spl/kstat/zfs/arcstats").await?;
for line in arcinfo.lines() {
if let Some((label, value)) = line.split_once(' ') {
let to_write = match label {
"size" => &mut mem_arc,
"memory_all_bytes" => &mut mem_total,
_ => {
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>() {
*to_write = number;
// We only need a few keys, so we can bail early.
zfs_keys_read += 1;
if zfs_keys_read == ZFS_KEYS_NEEDED {
break;
}
}
}
}
}
(mem_total / 1024, mem_arc / 1024)
}
#[cfg(target_os = "freebsd")]
{
use sysctl::Sysctl;
if let (Ok(mem_arc_value), Ok(mem_sys_value)) = (
sysctl::Ctl::new("kstat.zfs.misc.arcstats.size"),
sysctl::Ctl::new("hw.physmem"),
) {
if let (Ok(sysctl::CtlValue::U64(arc)), Ok(sysctl::CtlValue::Ulong(mem))) =
(mem_arc_value.value(), mem_sys_value.value())
{
(mem / 1024, arc / 1024)
} else {
(0, 0)
}
} else {
(0, 0)
}
}
#[cfg(target_os = "macos")]
{
(0, 0)
}
#[cfg(target_os = "windows")]
{
(0, 0)
}
};
Ok(Some(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)
},
}))
}

View file

@ -8,13 +8,14 @@ pub async fn get_mem_data(
) -> (
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
crate::utils::error::Result<Option<MemHarvest>>,
) {
use futures::join;
if !actually_get {
(Ok(None), Ok(None))
(Ok(None), Ok(None), Ok(None))
} else {
join!(get_ram_data(sys), get_swap_data(sys))
join!(get_ram_data(sys), get_swap_data(sys), get_arc_data())
}
}
@ -45,3 +46,39 @@ pub async fn get_swap_data(sys: &System) -> crate::utils::error::Result<Option<M
},
}))
}
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")]
{
use sysctl::Sysctl;
if let (Ok(mem_arc_value), Ok(mem_sys_value)) = (
sysctl::Ctl::new("kstat.zfs.misc.arcstats.size"),
sysctl::Ctl::new("hw.physmem"),
) {
if let (Ok(sysctl::CtlValue::U64(arc)), Ok(sysctl::CtlValue::Ulong(mem))) =
(mem_arc_value.value(), mem_sys_value.value())
{
(mem / 1024, arc / 1024)
} else {
(0, 0)
}
} else {
(0, 0)
}
}
};
Ok(Some(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)
},
}))
}

View file

@ -191,11 +191,21 @@ fn main() -> Result<()> {
convert_mem_data_points(&app.data_collection);
app.converted_data.swap_data =
convert_swap_data_points(&app.data_collection);
#[cfg(feature = "zfs")]
{
app.converted_data.arc_data =
convert_arc_data_points(&app.data_collection);
}
let (memory_labels, swap_labels) =
convert_mem_labels(&app.data_collection);
app.converted_data.mem_labels = memory_labels;
app.converted_data.swap_labels = swap_labels;
#[cfg(feature = "zfs")]
{
let arc_labels = convert_arc_labels(&app.data_collection);
app.converted_data.arc_labels = arc_labels;
}
}
if app.used_widgets.use_cpu {

View file

@ -502,12 +502,26 @@ impl Painter {
}
};
let mut mem_rows = 0;
#[cfg(feature = "zfs")]
{
let arc_data: &[(f64, f64)] = &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
}
}
}
mem_rows += 2; // add rows for SWAP and MEM
let vertical_chunks = Layout::default()
.direction(Direction::Vertical)
.margin(0)
.constraints([
Constraint::Length(cpu_height),
Constraint::Length(2),
Constraint::Length(mem_rows),
Constraint::Length(2),
Constraint::Min(5),
])

View file

@ -11,6 +11,7 @@ pub struct CanvasColours {
pub table_header_style: Style,
pub ram_style: Style,
pub swap_style: Style,
pub arc_style: Style,
pub rx_style: Style,
pub tx_style: Style,
pub total_rx_style: Style,
@ -43,6 +44,7 @@ impl Default for CanvasColours {
table_header_style: Style::default().fg(STANDARD_HIGHLIGHT_COLOUR),
ram_style: Style::default().fg(STANDARD_FIRST_COLOUR),
swap_style: Style::default().fg(STANDARD_SECOND_COLOUR),
arc_style: Style::default().fg(STANDARD_THIRD_COLOUR),
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),
@ -117,6 +119,11 @@ impl CanvasColours {
.context("Update 'swap_color' in your config file..")?;
}
if let Some(arc_color) = &colours.arc_color {
self.set_arc_colour(arc_color)
.context("Update 'arc_color' 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..")?;
@ -220,6 +227,11 @@ impl CanvasColours {
Ok(())
}
pub fn set_arc_colour(&mut self, colour: &str) -> error::Result<()> {
self.arc_style = get_style_from_config(colour)?;
Ok(())
}
pub fn set_rx_colour(&mut self, colour: &str) -> error::Result<()> {
self.rx_style = get_style_from_config(colour)?;
Ok(())

View file

@ -105,6 +105,41 @@ impl Painter {
let mem_text = vec![
Spans::from(Span::styled(mem_label, self.colours.ram_style)),
Spans::from(Span::styled(swap_label, self.colours.swap_style)),
#[cfg(feature = "zfs")]
{
let arc_data: &[(f64, f64)] = &app_state.converted_data.arc_data;
let arc_use_percentage = if let Some(arc) = arc_data.last() {
arc.1
} else {
0.0
};
let trimmed_arc_frac = if let Some((_label_percent, label_frac)) =
&app_state.converted_data.arc_labels
{
label_frac.trim()
} else {
EMPTY_MEMORY_FRAC_STRING
};
let arc_bar_length = usize::from(draw_loc.width.saturating_sub(7))
.saturating_sub(trimmed_arc_frac.len());
let num_bars_arc = calculate_basic_use_bars(arc_use_percentage, arc_bar_length);
let arc_label = if app_state.basic_mode_use_percent {
format!(
"ARC[{}{}{:3.0}%]",
"|".repeat(num_bars_arc),
" ".repeat(arc_bar_length - num_bars_arc + trimmed_arc_frac.len() - 4),
arc_use_percentage.round()
)
} else {
format!(
"ARC[{}{}{}]",
"|".repeat(num_bars_arc),
" ".repeat(arc_bar_length - num_bars_arc),
trimmed_arc_frac
)
};
Spans::from(Span::styled(arc_label, self.colours.arc_style))
},
];
f.render_widget(

View file

@ -29,7 +29,20 @@ impl Painter {
draw_loc,
);
let points = {
let mut points = Vec::with_capacity(2);
let mut size = 0;
#[cfg(feature = "zfs")]
{
let arc_data: &[(f64, f64)] = &app_state.converted_data.arc_data;
if let Some(arc) = arc_data.last() {
if arc.1 != 0.0 {
size += 1; // add capacity for ARC
}
}
}
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 {
let mem_label = format!("RAM:{}{}", label_percent, label_frac);
points.push(GraphData {
@ -46,6 +59,20 @@ impl Painter {
name: Some(swap_label.into()),
});
}
#[cfg(feature = "zfs")]
if let Some((label_percent, label_frac)) = &app_state.converted_data.arc_labels {
let arc_data: &[(f64, f64)] = &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);
points.push(GraphData {
points: &app_state.converted_data.arc_data,
style: self.colours.arc_style,
name: Some(arc_label.into()),
});
}
}
}
points
};

View file

@ -40,6 +40,7 @@ pub static DEFAULT_LIGHT_MODE_COLOUR_PALETTE: Lazy<ConfigColours> = Lazy::new(||
disabled_text_color: Some("gray".to_string()),
ram_color: Some("blue".to_string()),
swap_color: Some("red".to_string()),
arc_color: Some("LightBlue".to_string()),
rx_color: Some("blue".to_string()),
tx_color: Some("red".to_string()),
rx_total_color: Some("LightBlue".to_string()),
@ -84,6 +85,7 @@ 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()),
rx_color: Some("#8ec07c".to_string()),
tx_color: Some("#fabd2f".to_string()),
rx_total_color: Some("#689d6a".to_string()),
@ -129,6 +131,7 @@ 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()),
rx_color: Some("#427b58".to_string()),
tx_color: Some("#cc241d".to_string()),
rx_total_color: Some("#689d6a".to_string()),
@ -162,6 +165,7 @@ 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()),
rx_color: Some("#88c0d0".to_string()),
tx_color: Some("#d08770".to_string()),
rx_total_color: Some("#5e81ac".to_string()),
@ -195,6 +199,7 @@ 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()),
rx_color: Some("#81a1c1".to_string()),
tx_color: Some("#d08770".to_string()),
rx_total_color: Some("#5e81ac".to_string()),
@ -513,6 +518,8 @@ pub const CONFIG_TEXT: &str = r##"# This is a default config file for bottom. A
#ram_color="LightMagenta"
# Represents the colour SWAP will use in the memory legend and graph.
#swap_color="LightYellow"
# Represents the colour ARC will use in the memory legend and graph.
#arc_color="LightCyan"
# 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

@ -89,9 +89,11 @@ pub struct ConvertedData {
pub mem_labels: Option<(String, String)>,
pub swap_labels: Option<(String, String)>,
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>,
pub arc_data: Vec<Point>,
pub load_avg_data: [f32; 3],
pub cpu_data: Vec<ConvertedCpuData>,
pub battery_data: Vec<ConvertedBatteryData>,
@ -654,6 +656,73 @@ pub fn convert_battery_harvest(
.collect()
}
#[cfg(feature = "zfs")]
pub fn convert_arc_labels(current_data: &data_farmer::DataCollection) -> Option<(String, String)> {
/// 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)
}
}
if current_data.arc_harvest.mem_total_in_kib > 0 {
Some((
format!(
"{:3.0}%",
current_data.arc_harvest.use_percent.unwrap_or(0.0)
),
{
let (unit, denominator) = return_unit_and_denominator_for_mem_kib(
current_data.arc_harvest.mem_total_in_kib,
);
format!(
" {:.1}{}/{:.1}{}",
current_data.arc_harvest.mem_used_in_kib as f64 / denominator,
unit,
(current_data.arc_harvest.mem_total_in_kib as f64 / denominator),
unit
)
},
))
} else {
None
}
}
#[cfg(feature = "zfs")]
pub fn convert_arc_data_points(current_data: &data_farmer::DataCollection) -> Vec<Point> {
let mut result: Vec<Point> = Vec::new();
let current_time = if let Some(frozen_instant) = current_data.frozen_instant {
frozen_instant
} else {
current_data.current_instant
};
for (time, data) in &current_data.timed_data_vec {
if let Some(arc_data) = data.arc_data {
let time_from_start: f64 =
(current_time.duration_since(*time).as_millis() as f64).floor();
result.push((-time_from_start, arc_data));
if *time == current_time {
break;
}
}
}
result
}
#[cfg(test)]
mod test {
use super::*;

View file

@ -339,6 +339,10 @@ pub fn update_data(app: &mut App) {
if app.mem_state.force_update.is_some() {
app.converted_data.mem_data = convert_mem_data_points(&app.data_collection);
app.converted_data.swap_data = convert_swap_data_points(&app.data_collection);
#[cfg(feature = "zfs")]
{
app.converted_data.arc_data = convert_arc_data_points(&app.data_collection);
}
app.mem_state.force_update = None;
}

View file

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