Skip to content

Commit

Permalink
implement free features (#70)
Browse files Browse the repository at this point in the history
* added count and seconds to free command

* fix for count = 1

* add --total and fix clippy warning

* fix --count, --human and swap and make clippy happy

* test --count and --total

* add --lohi and clean code

* test --lohi

* mv --lohi -> --minmax & implement more correct --lohi

* change from iter.collect() to iter.last()

* remove unneeded import tests/by-util/test_free.rs

Co-authored-by: Daniel Hofstetter <[email protected]>

* make free run forever if -c is not set but -s is

* put the number conversion in a closure to clean up the code

* add --committed

* add --line

* remove test files

* Update src/uu/free/src/free.rs

smalll comment edit

Co-authored-by: Sylvestre Ledru <[email protected]>

* edit comments

* Update src/uu/free/src/free.rs

correct grammar in comment

Co-authored-by: Sylvestre Ledru <[email protected]>

* use .min & .max and fix 'free memory' value for the maximum and minimum

* rename closures

* Update src/uu/free/src/free.rs

add suggestion by @sylvestre

Co-authored-by: Sylvestre Ledru <[email protected]>

* replace closures with function

* remove cool features because the reviewers want to

* remove minmax test

* when using --lohi high should output high_free not free

* Update src/uu/free/src/free.rs

small change to comment

Co-authored-by: Daniel Hofstetter <[email protected]>

* remove HighUsed and LowUsed as they dont actually exist

* set LowMem if its missing from /proc/meminfo

* fix macos

* replace match with if in n2s closure

---------

Co-authored-by: D3V1LC0D3R <D3V1LC0D3R@github>
Co-authored-by: Daniel Hofstetter <[email protected]>
Co-authored-by: Sylvestre Ledru <[email protected]>
  • Loading branch information
4 people authored May 9, 2024
1 parent 30d3a32 commit 1f0ae84
Show file tree
Hide file tree
Showing 3 changed files with 253 additions and 84 deletions.
283 changes: 201 additions & 82 deletions src/uu/free/src/free.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,14 +24,18 @@ use std::env;
use std::fs;
#[cfg(target_os = "linux")]
use std::io::Error;
use std::ops::Mul;
use std::process;
use std::thread::sleep;
use std::time::Duration;
use std::u64;
use uucore::{error::UResult, format_usage, help_about, help_usage};

const ABOUT: &str = help_about!("free.md");
const USAGE: &str = help_usage!("free.md");

/// The unit of number is [UnitMultiplier::Bytes]
#[derive(Default)]
#[derive(Default, Clone)]
struct MemInfo {
total: u64,
free: u64,
Expand All @@ -43,10 +47,17 @@ struct MemInfo {
swap_free: u64,
swap_used: u64,
reclaimable: u64,
low_total: u64,
low_free: u64,
high_total: u64,
high_free: u64,
commit_limit: u64,
committed: u64,
}

#[cfg(target_os = "linux")]
fn parse_meminfo() -> Result<MemInfo, Error> {
// kernel docs: https://www.kernel.org/doc/html/latest/filesystems/proc.html#meminfo
let contents = fs::read_to_string("/proc/meminfo")?;
let mut mem_info = MemInfo::default();

Expand All @@ -63,10 +74,27 @@ fn parse_meminfo() -> Result<MemInfo, Error> {
"SwapTotal" => mem_info.swap_total = parsed_value,
"SwapFree" => mem_info.swap_free = parsed_value,
"SReclaimable" => mem_info.reclaimable = parsed_value,
"LowTotal" => mem_info.low_total = parsed_value,
"LowFree" => mem_info.low_free = parsed_value,
"HighTotal" => mem_info.high_total = parsed_value,
"HighFree" => mem_info.high_free = parsed_value,
"CommitLimit" => mem_info.commit_limit = parsed_value,
"Committed_AS" => mem_info.committed = parsed_value,
_ => {}
}
}
}
// as far as i understand the kernel doc everything that is not highmem (out of all the memory) is lowmem
// from kernel doc: "Highmem is all memory above ~860MB of physical memory."
// it would be better to implement this via optionals, etc. but that would require a refactor so lets not do that right now

if mem_info.low_total == u64::default() {
mem_info.low_total = mem_info.total - mem_info.high_total;
}

if mem_info.low_free == u64::default() {
mem_info.low_free = mem_info.free - mem_info.high_free;
}

mem_info.swap_used = mem_info.swap_total - mem_info.swap_free;

Expand All @@ -90,6 +118,12 @@ fn parse_meminfo() -> Result<MemInfo, Box<dyn std::error::Error>> {
swap_free: sys.free_swap(),
swap_used: sys.total_swap().saturating_sub(sys.free_swap()),
reclaimable: 0,
low_total: 0,
low_free: 0,
high_total: 0,
high_free: 0,
commit_limit: 0,
committed: 0,
};

Ok(mem_info)
Expand All @@ -101,88 +135,160 @@ fn parse_meminfo() -> Result<MemInfo, Box<dyn std::error::Error>> {
Ok(MemInfo::default())
}

// print total - used - free combo that is used for everything except memory for now
// free can be negative if the memory is overcommitted so it has to be signed
fn tuf_combo<F>(name: &str, total: u64, used: u64, free: i128, f: F)
where
F: Fn(u64) -> String,
{
// ugly hack to convert negative values
let free_str: String = if free < 0 {
"-".to_owned() + &f((-free) as u64)
} else {
f(free as u64)
};

println!("{:8}{:>12}{:>12}{:>12}", name, f(total), f(used), free_str);
}

#[uucore::main]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
let matches = uu_app().try_get_matches_from(args)?;
let wide = matches.get_flag("wide");

let wide = matches.get_flag("wide");
let human = matches.get_flag("human");

let si = matches.get_flag("si");
let total = matches.get_flag("total");
let lohi = matches.get_flag("lohi");
let count_flag = matches.get_one("count");
let mut count: u64 = count_flag.unwrap_or(&1_u64).to_owned();
let seconds_flag = matches.get_one("seconds");
let seconds: f64 = seconds_flag.unwrap_or(&1.0_f64).to_owned();
let committed = matches.get_flag("committed");
let one_line = matches.get_flag("line");

let dur = Duration::from_nanos(seconds.mul(1_000_000_000.0).round() as u64);
let convert = detect_unit(&matches);

match parse_meminfo() {
Ok(mem_info) => {
let buff_cache = if wide {
mem_info.buffers
} else {
mem_info.buffers + mem_info.cached
};
let cache = if wide { mem_info.cached } else { 0 };
let used = mem_info.total - mem_info.available;

if wide {
wide_header();
if human {
println!(
"{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
"Mem:",
humanized(mem_info.total, si),
humanized(used, si),
humanized(mem_info.free, si),
humanized(mem_info.shared, si),
humanized(buff_cache, si),
humanized(cache + mem_info.reclaimable, si),
humanized(mem_info.available, si),
)
let infinite: bool = count_flag.is_none() && seconds_flag.is_some();

while count > 0 || infinite {
// prevent underflow
if !infinite {
count -= 1;
}
match parse_meminfo() {
Ok(mem_info) => {
let buff_cache = if wide {
mem_info.buffers
} else {
mem_info.buffers + mem_info.cached
};
let cache = if wide { mem_info.cached } else { 0 };
let used = mem_info.total - mem_info.available;

// function that converts the number to the correct string
let n2s = |x| {
if human {
humanized(x, si)
} else {
convert(x).to_string()
}
};
if one_line {
println!(
"{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
"Mem:",
convert(mem_info.total),
convert(used),
convert(mem_info.free),
convert(mem_info.shared),
convert(buff_cache),
convert(cache + mem_info.reclaimable),
convert(mem_info.available),
)
}
} else {
header();
if human {
println!(
"{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
"Mem:",
humanized(mem_info.total, si),
humanized(used, si),
humanized(mem_info.free, si),
humanized(mem_info.shared, si),
humanized(buff_cache + mem_info.reclaimable, si),
humanized(mem_info.available, si),
)
"{:8}{:>12} {:8}{:>12} {:8}{:>12} {:8}{:>12}",
"SwapUse",
n2s(mem_info.swap_used),
"CacheUse",
n2s(buff_cache + mem_info.reclaimable),
"MemUse",
n2s(used),
"MemFree",
n2s(mem_info.free)
);
} else {
println!(
"{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
"Mem:",
convert(mem_info.total),
convert(used),
convert(mem_info.free),
convert(mem_info.shared),
convert(buff_cache + mem_info.reclaimable),
convert(mem_info.available),
)
if wide {
wide_header();
println!(
"{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
"Mem:",
n2s(mem_info.total),
n2s(used),
n2s(mem_info.free),
n2s(mem_info.shared),
n2s(buff_cache),
n2s(cache + mem_info.reclaimable),
n2s(mem_info.available),
);
} else {
header();
println!(
"{:8}{:>12}{:>12}{:>12}{:>12}{:>12}{:>12}",
"Mem:",
n2s(mem_info.total),
n2s(used),
n2s(mem_info.free),
n2s(mem_info.shared),
n2s(buff_cache + mem_info.reclaimable),
n2s(mem_info.available),
)
}

if lohi {
tuf_combo(
"Low:",
mem_info.low_total,
mem_info.low_total - mem_info.low_free,
mem_info.low_free.into(),
n2s,
);
tuf_combo(
"High:",
mem_info.high_total,
mem_info.high_total - mem_info.high_free,
mem_info.high_free.into(),
n2s,
);
}

tuf_combo(
"Swap:",
mem_info.swap_total,
mem_info.swap_used,
mem_info.swap_free.into(),
n2s,
);
if total {
tuf_combo(
"Total:",
mem_info.total + mem_info.swap_total,
used + mem_info.swap_used,
(mem_info.free + mem_info.swap_free).into(),
n2s,
);
}

if committed {
tuf_combo(
"Comm:",
mem_info.commit_limit,
mem_info.committed,
(mem_info.commit_limit as i128) - (mem_info.committed as i128),
n2s,
);
}
}
}
println!(
"{:8}{:>12}{:>12}{:>12}",
"Swap:", mem_info.swap_total, mem_info.swap_used, mem_info.swap_free
);
Err(e) => {
eprintln!("free: failed to read memory info: {}", e);
process::exit(1);
}
}
Err(e) => {
eprintln!("free: failed to read memory info: {}", e);
process::exit(1);
if count > 0 || infinite {
// the original free prints a newline everytime before waiting for the next round
println!();
sleep(dur);
}
}

Expand All @@ -200,20 +306,33 @@ pub fn uu_app() -> Command {
"bytes", "kilo", "mega", "giga", "tera", "peta", "kibi", "mebi", "gibi", "tebi", "pebi",
]))
.args([
arg!(-b --bytes "show output in bytes").action(ArgAction::SetTrue),
arg!( --kilo "show output in kilobytes").action(ArgAction::SetFalse),
arg!( --mega "show output in megabytes").action(ArgAction::SetTrue),
arg!( --giga "show output in gigabytes").action(ArgAction::SetTrue),
arg!( --tera "show output in terabytes").action(ArgAction::SetTrue),
arg!( --peta "show output in petabytes").action(ArgAction::SetTrue),
arg!(-k --kibi "show output in kibibytes").action(ArgAction::SetTrue),
arg!(-m --mebi "show output in mebibytes").action(ArgAction::SetTrue),
arg!(-g --gibi "show output in gibibytes").action(ArgAction::SetTrue),
arg!( --tebi "show output in tebibytes").action(ArgAction::SetTrue),
arg!( --pebi "show output in pebibytes").action(ArgAction::SetTrue),
arg!(-h --human "show human-readable output").action(ArgAction::SetTrue),
arg!( --si "use powers of 1000 not 1024").action(ArgAction::SetFalse),
// arg!(-L --line "show output on a single line"),
arg!(-b --bytes "show output in bytes").action(ArgAction::SetTrue),
arg!( --kilo "show output in kilobytes").action(ArgAction::SetFalse),
arg!( --mega "show output in megabytes").action(ArgAction::SetTrue),
arg!( --giga "show output in gigabytes").action(ArgAction::SetTrue),
arg!( --tera "show output in terabytes").action(ArgAction::SetTrue),
arg!( --peta "show output in petabytes").action(ArgAction::SetTrue),
arg!(-k --kibi "show output in kibibytes").action(ArgAction::SetTrue),
arg!(-m --mebi "show output in mebibytes").action(ArgAction::SetTrue),
arg!(-g --gibi "show output in gibibytes").action(ArgAction::SetTrue),
arg!( --tebi "show output in tebibytes").action(ArgAction::SetTrue),
arg!( --pebi "show output in pebibytes").action(ArgAction::SetTrue),
arg!(-h --human "show human-readable output").action(ArgAction::SetTrue),
arg!( --si "use powers of 1000 not 1024").action(ArgAction::SetFalse),
arg!(-l --lohi "show detailed low and high memory statistics")
.action(ArgAction::SetTrue),
arg!(-t --total "show total for RAM + swap").action(ArgAction::SetTrue),
arg!(-v --committed "show committed memory and commit limit")
.action(ArgAction::SetTrue),
// accept 1 as well as 0.5, 0.55, ...
arg!(-s --seconds "repeat printing every N seconds")
.action(ArgAction::Set)
.value_parser(clap::value_parser!(f64)),
// big int because predecesor accepts them as well (some scripts might have huge values as some sort of infinite)
arg!(-c --count "repeat printing N times, then exit")
.action(ArgAction::Set)
.value_parser(clap::value_parser!(u64)),
arg!(-L --line "show output on a single line").action(ArgAction::SetTrue),
])
.arg(
Arg::new("wide")
Expand Down
Loading

0 comments on commit 1f0ae84

Please sign in to comment.