From cb9703f820461d8741f068838395f4de776b8c7a Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 15 Aug 2024 11:27:28 +0300 Subject: [PATCH 01/82] update demo image Also optimizes, and removes all metadata from the demo image. --- .github/assets/demo.png | Bin 33152 -> 20668 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.github/assets/demo.png b/.github/assets/demo.png index 945e489340a9628ce18893687ab431bb06248fbc..8a40cf788c6465a340d72d05b68f1870868139aa 100644 GIT binary patch literal 20668 zcmZ5{WmuF^*EI-Aw{(YeBQUfeAt4RY-Q6H5-5@Pe3P>|__s}ifF(BRD@ZIS1yx)7h z{21n%nLEz8_t|^xwblt&l$Sz7CPIdRfkBi0Ag&Ap^CB5|KZf`Mc)d|J{Q(1`7$_|+ zs_HSnKcy?H0!z^DDAH54F&Amuh$D|SqQlVmIh$5Vx?hk$gQbeI?l@StEBgnhYCr^4 zz)NbB!S7Si%Cx1CjXP8Ub~)SQ^t(pnKO7AzK*7Ol#82d-4{)Q-Cu}${Fy*h39Dz%I z0KvdeKw)6M1i`_)gTTVTAR)lO(2&5uV59%vFH^*vZaV^~i`&D8&i#ff4`0AA6CvSa z%L7Nzqz5H90LMO&0*5D3LB$}_*Sl}sDqT;FRPla7)G|lkaJNZ4YPwDKZN&%k;aX!w zx)dEO?5{4IWNv;@kSoegFDq-qN6%0DKChNk21^0;6NAjOB`MS|XzFP~Jsj7+a6_BK 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 Date: Thu, 15 Aug 2024 18:32:02 +0800 Subject: [PATCH 02/82] micro optimizations (#8) * perf: break early after parsing required meminfo Also a match statement for compiler magic. * perf: pre-allocate strings when reading files * refactor: remove duplicate .to_string() * perf: try to print everything in one syscall println! sends a syscall for each line. * perf: get rid of duplicate uname syscall * perf: simplify first letter capitalization * refactor: directly use key in match statement --- src/desktop.rs | 23 ++++++++--------------- src/main.rs | 12 ++++++++---- src/release.rs | 22 +++++++++++----------- src/system.rs | 37 +++++++++++++++++++++++++------------ 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/src/desktop.rs b/src/desktop.rs index bf0da35..f35cd2a 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,21 +1,13 @@ pub fn get_desktop_info() -> String { - fn capitalize_first_letter(s: &str) -> String { - if s.is_empty() { - return String::new(); - } - - let mut chars = s.chars(); - let first_char = chars.next().unwrap().to_uppercase().to_string(); - let rest: String = chars.collect(); - first_char + &rest - } - // Retrieve the environment variables and handle Result types let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); let display_backend_result = std::env::var("XDG_SESSION_TYPE"); // Capitalize the first letter of the display backend value - let display_backend = capitalize_first_letter(display_backend_result.as_deref().unwrap_or("")); + let mut display_backend = display_backend_result.unwrap_or_default(); + if let Some(c) = display_backend.as_mut_str().get_mut(0..1) { + c.make_ascii_uppercase(); + } // Trim "none+" from the start of desktop_env if present // Use "Unknown" if desktop_env is empty or has an error @@ -26,10 +18,11 @@ pub fn get_desktop_info() -> String { // Handle the case where display_backend might be empty after capitalization let display_backend = if display_backend.is_empty() { - "Unknown".to_string() + "Unknown" } else { - display_backend - }; + &display_backend + } + .to_string(); format!("{desktop_env} ({display_backend})") } diff --git a/src/main.rs b/src/main.rs index aace49d..3da592e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,8 @@ mod release; mod system; mod uptime; +use std::io::Write; + use crate::colors::{print_dots, BLUE, CYAN, RESET}; use crate::desktop::get_desktop_info; use crate::release::{get_os_pretty_name, get_system_info}; @@ -15,10 +17,11 @@ use color_eyre::Report; fn main() -> Result<(), Report> { color_eyre::install()?; + let utsname = nix::sys::utsname::uname()?; let fields = Fields { - user_info: get_username_and_hostname(), + user_info: get_username_and_hostname(&utsname), os_name: get_os_pretty_name()?, - kernel_version: get_system_info()?, + kernel_version: get_system_info(&utsname)?, shell: get_shell(), desktop: get_desktop_info(), uptime: get_current()?, @@ -61,7 +64,7 @@ fn print_system_info(fields: &Fields) { colors, } = fields; - println!( + let _ = std::io::stdout().write_all(format!( " {CYAN} ▟█▖ {BLUE}▝█▙ ▗█▛ {user_info} ~{RESET} {CYAN} ▗▄▄▟██▄▄▄▄▄{BLUE}▝█▙█▛ {CYAN}▖ {CYAN} {BLUE}System{RESET}  {os_name} @@ -71,5 +74,6 @@ fn print_system_info(fields: &Fields) { {BLUE} ▟█▛{CYAN}▗█▖ {CYAN}▟█▛ {CYAN} {BLUE}Desktop{RESET}  {desktop} {BLUE} ▝█▛ {CYAN}██▖{BLUE}▗▄▄▄▄▄▄▄▄▄▄▄ {CYAN}󰍛 {BLUE}Memory{RESET}  {memory_usage} {BLUE} ▝ {CYAN}▟█▜█▖{BLUE}▀▀▀▀▀██▛▀▀▘ {CYAN}󱥎 {BLUE}Storage (/){RESET}  {storage} - {CYAN} ▟█▘ ▜█▖ {BLUE}▝█▛ {CYAN} {BLUE}Colors{RESET}  {colors}"); + {CYAN} ▟█▘ ▜█▖ {BLUE}▝█▛ {CYAN} {BLUE}Colors{RESET}  {colors} +").as_bytes()); } diff --git a/src/release.rs b/src/release.rs index 4c6a9b8..7600d0e 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,9 +1,11 @@ use color_eyre::Result; -use std::fs::read_to_string; -use std::io; +use nix::sys::utsname::UtsName; +use std::{ + fs::File, + io::{self, Read}, +}; -pub fn get_system_info() -> nix::Result { - let utsname = nix::sys::utsname::uname()?; +pub fn get_system_info(utsname: &UtsName) -> nix::Result { Ok(format!( "{} {} ({})", utsname.sysname().to_str().unwrap_or("Unknown"), @@ -13,15 +15,13 @@ pub fn get_system_info() -> nix::Result { } pub fn get_os_pretty_name() -> Result { - let os_release_content = read_to_string("/etc/os-release")?; + let mut os_release_content = String::with_capacity(1024); + File::open("/etc/os-release")?.read_to_string(&mut os_release_content)?; + let pretty_name = os_release_content .lines() .find(|line| line.starts_with("PRETTY_NAME=")) - .map(|line| { - line.trim_start_matches("PRETTY_NAME=") - .trim_matches('"') - .to_string() - }); + .map(|line| line.trim_start_matches("PRETTY_NAME=").trim_matches('"')); - Ok(pretty_name.unwrap_or("Unknown".to_string())) + Ok(pretty_name.unwrap_or("Unknown").to_string()) } diff --git a/src/system.rs b/src/system.rs index 964aec1..74d774f 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,15 +1,21 @@ use color_eyre::Result; -use nix::sys::statvfs::statvfs; +use nix::sys::{statvfs::statvfs, utsname::UtsName}; -use std::env; -use std::io::{self}; +use std::{ + env, + fs::File, + io::{self, Read}, +}; use crate::colors::{CYAN, GREEN, RED, RESET, YELLOW}; -pub fn get_username_and_hostname() -> String { +pub fn get_username_and_hostname(utsname: &UtsName) -> String { let username = env::var("USER").unwrap_or("unknown_user".to_string()); - let hostname = nix::unistd::gethostname().unwrap_or("unknown_host".to_string().into()); - let hostname = hostname.to_string_lossy(); + let hostname = utsname + .nodename() + .to_str() + .unwrap_or("unknown_host") + .to_string(); format!("{YELLOW}{username}{RED}@{GREEN}{hostname}") } @@ -45,13 +51,20 @@ pub fn get_memory_usage() -> Result { let mut total_memory_kb = 0.0; let mut available_memory_kb = 0.0; - for line in std::fs::read_to_string("/proc/meminfo")?.lines() { + let mut meminfo = String::with_capacity(2048); + File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?; + + for line in meminfo.lines() { let mut split = line.split_whitespace(); - let key = split.next().unwrap_or(""); - if key == "MemTotal:" { - total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); - } else if key == "MemAvailable:" { - available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); + match split.next().unwrap_or_default() { + "MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0), + "MemAvailable:" => { + available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); + + // MemTotal comes before MemAvailable, stop parsing + break; + } + _ => (), } } From f1e8ca87730237469cec5c51d52ee2a5f54cae31 Mon Sep 17 00:00:00 2001 From: vali Date: Thu, 15 Aug 2024 17:43:39 +0000 Subject: [PATCH 03/82] add benchmarking for individual functions (#9) * add benchmarking * update README to include benchmarking of individual functions * README: formatting * README: add link to Criterion's getting_started * shell.nix: add gnuplot Since Criterion.rs uses gnuplot to generate nice plots, add it to the shell. * Cargo.toml: fixed microfetch binary name * benchmark.rs: fix benchmark function's name * Update README.md --------- Co-authored-by: raf --- Cargo.lock | 572 +++++++++++++++++++++++++++++++++++++++++++ Cargo.toml | 15 ++ README.md | 6 + benches/benchmark.rs | 27 ++ nix/shell.nix | 3 + src/lib.rs | 5 + 6 files changed, 628 insertions(+) create mode 100644 benches/benchmark.rs create mode 100644 src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 1f39265..d798c09 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,33 @@ version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "anstyle" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" + +[[package]] +name = "autocfg" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" + [[package]] name = "backtrace" version = "0.3.71" @@ -38,6 +65,18 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bumpalo" +version = "3.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" + +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.1.7" @@ -56,6 +95,58 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "ciborium-io", + "ciborium-ll", + "serde", +] + +[[package]] +name = "ciborium-io" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" + +[[package]] +name = "ciborium-ll" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" +dependencies = [ + "ciborium-io", + "half", +] + +[[package]] +name = "clap" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +dependencies = [ + "clap_builder", +] + +[[package]] +name = "clap_builder" +version = "4.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +dependencies = [ + "anstyle", + "clap_lex", +] + +[[package]] +name = "clap_lex" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" + [[package]] name = "color-eyre" version = "0.6.3" @@ -69,6 +160,79 @@ dependencies = [ "owo-colors", ] +[[package]] +name = "criterion" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +dependencies = [ + "anes", + "cast", + "ciborium", + "clap", + "criterion-plot", + "is-terminal", + "itertools", + "num-traits", + "once_cell", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" + +[[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "eyre" version = "0.6.12" @@ -85,18 +249,75 @@ version = "0.28.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" +[[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] +name = "hermit-abi" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" + [[package]] name = "indenter" version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" +[[package]] +name = "is-terminal" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.52.0", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] +name = "js-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + [[package]] name = "memchr" version = "2.7.4" @@ -108,6 +329,7 @@ name = "microfetch" version = "0.3.5" dependencies = [ "color-eyre", + "criterion", "nix", ] @@ -132,6 +354,15 @@ dependencies = [ "libc", ] +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.32.2" @@ -147,14 +378,355 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +[[package]] +name = "oorandom" +version = "11.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" + [[package]] name = "owo-colors" version = "3.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" +[[package]] +name = "plotters" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +dependencies = [ + "num-traits", + "plotters-backend", + "plotters-svg", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "plotters-backend" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" + +[[package]] +name = "plotters-svg" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +dependencies = [ + "plotters-backend", +] + +[[package]] +name = "proc-macro2" +version = "1.0.86" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rayon" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "regex" +version = "1.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" + [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" + +[[package]] +name = "ryu" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" + +[[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 = "serde" +version = "1.0.207" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.207" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.125" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +dependencies = [ + "itoa", + "memchr", + "ryu", + "serde", +] + +[[package]] +name = "syn" +version = "2.0.74" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tinytemplate" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.93" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" + +[[package]] +name = "web-sys" +version = "0.3.70" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" diff --git a/Cargo.toml b/Cargo.toml index 347b0bc..9918e33 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,10 +3,25 @@ name = "microfetch" version = "0.3.5" edition = "2021" +[lib] +name = "microfetch_lib" +path = "src/lib.rs" + +[[bin]] +name = "microfetch" +path = "src/main.rs" + [dependencies] nix = { version = "0.29", features = ["fs", "hostname", "feature"] } color-eyre = { version = "0.6", default-features = false } +[dev-dependencies] +criterion = "0.5" + +[[bench]] +name = "benchmark" +harness = false + [profile.dev] opt-level = 3 diff --git a/README.md b/README.md index 88d7d39..d1fb3ca 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,12 @@ there is. The only downside of using Rust is introducing more "bloated" dependency trees and increasing build times. The latter is easily mitigated with Nix's binary cache, though._ +[Criterion.rs]: https://github.com/bheisler/criterion.rs +[Getting Started Guide]: https://bheisler.github.io/criterion.rs/book/getting_started.html + +To benchmark individual functions, [Criterion.rs] is used. See Criterion's +[Getting Started Guide] for details or just run `cargo bench` to benchmark +all features of Microfetch ## Customizing You can't\*. diff --git a/benches/benchmark.rs b/benches/benchmark.rs new file mode 100644 index 0000000..7706617 --- /dev/null +++ b/benches/benchmark.rs @@ -0,0 +1,27 @@ +use criterion::{criterion_group, criterion_main, Criterion}; +use microfetch_lib::colors::print_dots; +use microfetch_lib::desktop::get_desktop_info; +use microfetch_lib::release::{get_os_pretty_name, get_system_info}; +use microfetch_lib::system::{ + get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname, +}; +use microfetch_lib::uptime::get_current; + +fn main_benchmark(c: &mut Criterion) { + let utsname = nix::sys::utsname::uname().expect("lol"); + c.bench_function("user_info", |b| { + b.iter(|| get_username_and_hostname(&utsname)) + }); + c.bench_function("os_name", |b| b.iter(get_os_pretty_name)); + c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname))); + c.bench_function("shell", |b| b.iter(get_shell)); + + c.bench_function("desktop", |b| b.iter(get_desktop_info)); + c.bench_function("uptime", |b| b.iter(get_current)); + c.bench_function("memory_usage", |b| b.iter(get_memory_usage)); + c.bench_function("storage", |b| b.iter(get_root_disk_usage)); + c.bench_function("colors", |b| b.iter(print_dots)); +} + +criterion_group!(benches, main_benchmark); +criterion_main!(benches); diff --git a/nix/shell.nix b/nix/shell.nix index 5efe65c..2350dbd 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -7,6 +7,7 @@ rustc, gcc, rustPlatform, + gnuplot, }: mkShell { strictDeps = true; @@ -19,6 +20,8 @@ mkShell { rust-analyzer-unwrapped rustfmt clippy + + gnuplot # For Criterion.rs plots ]; env.RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 0000000..4ba33f8 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,5 @@ +pub mod colors; +pub mod desktop; +pub mod release; +pub mod system; +pub mod uptime; From 3bb2a0bd10f871aa1f5732c87619f183820426a2 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 15 Aug 2024 20:45:46 +0300 Subject: [PATCH 04/82] update demo again, again Use a better resolution, strip, use new RAM calculation --- .github/assets/demo.png | Bin 20668 -> 21010 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/.github/assets/demo.png b/.github/assets/demo.png index 8a40cf788c6465a340d72d05b68f1870868139aa..137b84523c9ce01d2015e020926ae200c44b5957 100644 GIT binary patch literal 21010 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 literal 20668 zcmZ5{WmuF^*EI-Aw{(YeBQUfeAt4RY-Q6H5-5@Pe3P>|__s}ifF(BRD@ZIS1yx)7h z{21n%nLEz8_t|^xwblt&l$Sz7CPIdRfkBi0Ag&Ap^CB5|KZf`Mc)d|J{Q(1`7$_|+ zs_HSnKcy?H0!z^DDAH54F&Amuh$D|SqQlVmIh$5Vx?hk$gQbeI?l@StEBgnhYCr^4 zz)NbB!S7Si%Cx1CjXP8Ub~)SQ^t(pnKO7AzK*7Ol#82d-4{)Q-Cu}${Fy*h39Dz%I z0KvdeKw)6M1i`_)gTTVTAR)lO(2&5uV59%vFH^*vZaV^~i`&D8&i#ff4`0AA6CvSa z%L7Nzqz5H90LMO&0*5D3LB$}_*Sl}sDqT;FRPla7)G|lkaJNZ4YPwDKZN&%k;aX!w zx)dEO?5{4IWNv;@kSoegFDq-qN6%0DKChNk21^0;6NAjOB`MS|XzFP~Jsj7+a6_BK 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 Date: Thu, 15 Aug 2024 21:07:40 +0300 Subject: [PATCH 05/82] improve credits section; reorganize README --- README.md | 65 ++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index d1fb3ca..25f0151 100644 --- a/README.md +++ b/README.md @@ -41,16 +41,15 @@ than welcome to use it on your system: it's pretty [fast...](#benchmarks) - Shell Colors - Did I mention fast? -## Installation +## Motivation -Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). You can -get it through the unstable channel for the time being. The Nix flake can also -be used for bleeding-edge builds. +Fastfetch, as its name indicates, a very fast fetch tool written in C, however, +I am not interested in any of its additional features and I very much dislike +the defaults. Microfetch is a fetch tool that you would normally write in Bash +and put in your `~/.bashrc` but actually _really_ fast because it opts-out of +all customization options provided by Fastfetch. Why? Because I can. -Non-Nix users will have to build Microfetch with `cargo`. - -Microfetch is _currently_ not available anywhere else. Though, does it _really_ -have to be? +I cannot re-iterate it enough, Microfetch is annoyingly fast. ## Benchmarks @@ -61,9 +60,9 @@ benchmarks with Hyperfine on my desktop system. | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? | | :----------- | ----------: | -------: | -------: | -------------: | --------------: | | `microfetch` | 1.3 ± 0.0 | 1.3 | 1.4 | 1.00 | yes | +| `fastfetch` | 31.9 ± 0.8 | 30.8 | 33.8 | 24.08 ± 0.98 | no | | `pfetch` | 254.2 ± 4.8 | 246.7 | 264.9 | 191.97 ± 7.10 | no | | `neofetch` | 735.4 ± 9.5 | 721.1 | 752.8 | 555.48 ± 19.08 | no | -| `fastfetch` | 31.9 ± 0.8 | 30.8 | 33.8 | 24.08 ± 0.98 | no | _As far as I'm concerned, Microfetch is faster than almost every fetch tool there is. The only downside of using Rust is introducing more "bloated" @@ -74,11 +73,29 @@ Nix's binary cache, though._ [Getting Started Guide]: https://bheisler.github.io/criterion.rs/book/getting_started.html To benchmark individual functions, [Criterion.rs] is used. See Criterion's -[Getting Started Guide] for details or just run `cargo bench` to benchmark -all features of Microfetch +[Getting Started Guide] for details or just run `cargo bench` to benchmark all +features of Microfetch. + +## Installation + +Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). You can +get it through the unstable channel for the time being. The Nix flake can also +be used for bleeding-edge builds. + +Non-Nix users will have to build Microfetch with `cargo`. It is not published +anywhere but I imagine you can use `cargo install --git` to install it from +source. + +```bash +cargo install --git https://github.com/notashelf/microfetch.git +``` + +Microfetch is _currently_ not available anywhere else. Though, does it _really_ +have to be? + ## Customizing -You can't\*. +You can't. ### Why? @@ -88,9 +105,12 @@ those increment execution time and resource consumption by a lot. ### Really? -To be fair, you _can_ customize Microfetch by... Well, patching it. It's not the +To be fair, you _can_ customize Microfetch by, well, patching it. It's not the best way per se, but it will be the only way that does not compromise on speed. +The Nix package allows passing patches in a streamlined manner by passing +`.overrideAttrs` to the derivation. + ## Contributing I will, mostly, reject feature additions. This is not to say you should avoid @@ -98,7 +118,8 @@ them altogether, as you might have a really good idea worth discussing but as a general rule of thumb consider talking to me before creating a feature PR. Contributions that help improve performance in specific areas of Microfetch are -welcome. Though, prepare to be bombarded with questions. +welcome. Though, prepare to be bombarded with questions if your changes are +large. ## Hacking @@ -111,7 +132,21 @@ Non-nix users will need `cargo` and `gcc` installed on their system, see ## Thanks Huge thanks to everyone who took the time to make pull requests or nag me in -person about current issues. +person about current issues. To list a few, special thanks to: + +- [@Nydragon](https://github.com/Nydragon) - For packaging Microfetch in Nixpkgs +- [@ErrorNoInternet](https://github.com/ErrorNoInternet) - Performance + improvements and code assistance +- [@SoraTenshi](https://github.com/SoraTenshi) - General tips and code + improvements +- [@bloxx12](https://github.com/bloxx12) - Performance improvements and + benchmarking plots +- [@sioodmy](https://github.com/sioodmy) - Being cute +- [@mewoocat](https://github.com/mewoocat) - The awesome NixOS logo ASCII used + in Microfetch + +Additionally a big thank you to everyone who used, talked about or criticized +Microfetch. I might have missed your name here, but you have my thanks. ## License From 64ac7a6ef3a903959ffe4c9d621ec5451104de29 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 15 Aug 2024 21:07:58 +0300 Subject: [PATCH 06/82] provide a microfetch package ...and alias default package to it. I like this convention better --- flake.nix | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/flake.nix b/flake.nix index b5035c3..a481c30 100644 --- a/flake.nix +++ b/flake.nix @@ -11,7 +11,8 @@ pkgsForEach = nixpkgs.legacyPackages; in { packages = forEachSystem (system: { - default = pkgsForEach.${system}.callPackage ./nix/package.nix {}; + default = self.packages.${system}.microfetch; + microfetch = pkgsForEach.${system}.callPackage ./nix/package.nix {}; }); devShells = forEachSystem (system: { From 9ed9e8d930b18895fd7629a58a2a322ea52eea21 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sat, 17 Aug 2024 21:14:09 +0300 Subject: [PATCH 07/82] increment cargo version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d798c09..f21044e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -326,7 +326,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.3.5" +version = "0.4.0" dependencies = [ "color-eyre", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 9918e33..5c346cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.3.5" +version = "0.4.0" edition = "2021" [lib] From 8f5bfcbd0576c3123220321464083ffc9fe9434a Mon Sep 17 00:00:00 2001 From: raf Date: Tue, 29 Oct 2024 16:45:55 +0000 Subject: [PATCH 08/82] implement --version for checking program version (#10) --- src/main.rs | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3da592e..5cb9904 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,20 +17,24 @@ use color_eyre::Report; fn main() -> Result<(), Report> { color_eyre::install()?; - let utsname = nix::sys::utsname::uname()?; - let fields = Fields { - user_info: get_username_and_hostname(&utsname), - os_name: get_os_pretty_name()?, - kernel_version: get_system_info(&utsname)?, - shell: get_shell(), - desktop: get_desktop_info(), - uptime: get_current()?, - memory_usage: get_memory_usage()?, - storage: get_root_disk_usage()?, - colors: print_dots(), - }; - - print_system_info(&fields); + let args: Vec = std::env::args().collect(); + if args.len() > 1 && args[1] == "--version" { + println!("Microfetch {}", env!("CARGO_PKG_VERSION")); + } else { + let utsname = nix::sys::utsname::uname()?; + let fields = Fields { + user_info: get_username_and_hostname(&utsname), + os_name: get_os_pretty_name()?, + kernel_version: get_system_info(&utsname)?, + shell: get_shell(), + desktop: get_desktop_info(), + uptime: get_current()?, + memory_usage: get_memory_usage()?, + storage: get_root_disk_usage()?, + colors: print_dots(), + }; + print_system_info(&fields); + } Ok(()) } From 3960b37089f6486be8d25bbab7f0fea93e0a2b3b Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 9 Dec 2024 17:29:32 +0300 Subject: [PATCH 09/82] faster release parsing Around 20% faster when you think about it, but results vary because of hardware race conditions. --- src/release.rs | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/release.rs b/src/release.rs index 7600d0e..3f38977 100644 --- a/src/release.rs +++ b/src/release.rs @@ -2,7 +2,7 @@ use color_eyre::Result; use nix::sys::utsname::UtsName; use std::{ fs::File, - io::{self, Read}, + io::{self, BufRead, BufReader}, }; pub fn get_system_info(utsname: &UtsName) -> nix::Result { @@ -15,13 +15,15 @@ pub fn get_system_info(utsname: &UtsName) -> nix::Result { } pub fn get_os_pretty_name() -> Result { - let mut os_release_content = String::with_capacity(1024); - File::open("/etc/os-release")?.read_to_string(&mut os_release_content)?; + let file = File::open("/etc/os-release")?; + let reader = BufReader::new(file); - let pretty_name = os_release_content - .lines() - .find(|line| line.starts_with("PRETTY_NAME=")) - .map(|line| line.trim_start_matches("PRETTY_NAME=").trim_matches('"')); + for line in reader.lines() { + let line = line?; + if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { + return Ok(pretty_name.trim_matches('"').to_string()); + } + } - Ok(pretty_name.unwrap_or("Unknown").to_string()) + Ok("Unknown".to_string()) } From 1b0d15a24fbcbb0fa17b81479bb868c9a30ddf70 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 10 Dec 2024 21:26:14 +0300 Subject: [PATCH 10/82] docs: mention nerdfonts requirement; update benchmarks section --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 25f0151..804fa36 100644 --- a/README.md +++ b/README.md @@ -47,15 +47,17 @@ Fastfetch, as its name indicates, a very fast fetch tool written in C, however, I am not interested in any of its additional features and I very much dislike the defaults. Microfetch is a fetch tool that you would normally write in Bash and put in your `~/.bashrc` but actually _really_ fast because it opts-out of -all customization options provided by Fastfetch. Why? Because I can. +all customization options provided by Fastfetch. Why? Because I can, and because +I prefer Rust for "structured" Bash scripts. I cannot re-iterate it enough, Microfetch is annoyingly fast. ## Benchmarks -Microfetch's performance is mostly hardware-dependant, however, the overall -trend seems to be < 2ms on any modern (2015 and after) CPU. Below are the -benchmarks with Hyperfine on my desktop system. +Microfetch's performance is capped by hardware-specific race conditions, meaning +it may (at times) depend on your hardware. However, the overall trend seems to +be < 2ms on any modern (2015 and after) CPU. Below are the benchmarks with +Hyperfine on my desktop system. | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? | | :----------- | ----------: | -------: | -------: | -------------: | --------------: | @@ -65,7 +67,7 @@ benchmarks with Hyperfine on my desktop system. | `neofetch` | 735.4 ± 9.5 | 721.1 | 752.8 | 555.48 ± 19.08 | no | _As far as I'm concerned, Microfetch is faster than almost every fetch tool -there is. The only downside of using Rust is introducing more "bloated" +there is. The only downsides of using Rust are introducing more "bloated" dependency trees and increasing build times. The latter is easily mitigated with Nix's binary cache, though._ @@ -78,6 +80,10 @@ features of Microfetch. ## Installation +> [!NOTE] +> You will need a Nerdfonts patched font installed, and for your terminal +> emulator to support said font. Microfetch uses nerdfonts glyphs by default. + Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). You can get it through the unstable channel for the time being. The Nix flake can also be used for bleeding-edge builds. From 065216af7ca5d1f5a6efd19ae5969d5fa9815689 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 17:20:46 +0300 Subject: [PATCH 11/82] colors: respect NO_COLOR spec Microfetch will now respect the NO_COLOR environment variable if it has been passed to the program. The performance overhead of this operation is almost none. In addition, the main function has been updated to lock stdout. --- Cargo.lock | 7 ++++++ Cargo.toml | 1 + src/colors.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++------- src/main.rs | 38 ++++++++++++++++++------------- src/system.rs | 33 +++++++++++++-------------- 5 files changed, 99 insertions(+), 42 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f21044e..980d4ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -306,6 +306,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.155" @@ -330,6 +336,7 @@ version = "0.4.0" dependencies = [ "color-eyre", "criterion", + "lazy_static", "nix", ] diff --git a/Cargo.toml b/Cargo.toml index 5c346cb..107d793 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ path = "src/main.rs" [dependencies] nix = { version = "0.29", features = ["fs", "hostname", "feature"] } color-eyre = { version = "0.6", default-features = false } +lazy_static = "1.5.0" [dev-dependencies] criterion = "0.5" diff --git a/src/colors.rs b/src/colors.rs index a8aee60..061db15 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,11 +1,57 @@ -pub const RESET: &str = "\x1b[0m"; -pub const BLUE: &str = "\x1b[34m"; -pub const CYAN: &str = "\x1b[36m"; -pub const GREEN: &str = "\x1b[32m"; -pub const YELLOW: &str = "\x1b[33m"; -pub const RED: &str = "\x1b[31m"; -pub const MAGENTA: &str = "\x1b[35m"; +use std::env; + +pub struct Colors { + pub reset: &'static str, + pub blue: &'static str, + pub cyan: &'static str, + pub green: &'static str, + pub yellow: &'static str, + pub red: &'static str, + pub magenta: &'static str, +} + +impl Colors { + const fn new(is_no_color: bool) -> Self { + match is_no_color { + true => Self { + reset: "", + blue: "", + cyan: "", + green: "", + yellow: "", + red: "", + magenta: "", + }, + false => Self { + reset: "\x1b[0m", + blue: "\x1b[34m", + cyan: "\x1b[36m", + green: "\x1b[32m", + yellow: "\x1b[33m", + red: "\x1b[31m", + magenta: "\x1b[35m", + }, + } + } +} + +lazy_static::lazy_static! { + pub static ref COLORS: Colors = { + // check for NO_COLOR once at startup + let is_no_color = env::var("NO_COLOR").is_ok(); + Colors::new(is_no_color) + }; +} pub fn print_dots() -> String { - format!("{BLUE} {CYAN} {GREEN} {YELLOW} {RED} {MAGENTA} {RESET}") + format!( + "{} {} {} {} {} {} {}", + COLORS.blue, + COLORS.cyan, + COLORS.green, + COLORS.yellow, + COLORS.red, + COLORS.magenta, + COLORS.reset, + ) } diff --git a/src/main.rs b/src/main.rs index 5cb9904..0ea1672 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,15 +4,13 @@ mod release; mod system; mod uptime; -use std::io::Write; - -use crate::colors::{print_dots, BLUE, CYAN, RESET}; +use crate::colors::print_dots; use crate::desktop::get_desktop_info; use crate::release::{get_os_pretty_name, get_system_info}; use crate::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname}; use crate::uptime::get_current; - use color_eyre::Report; +use std::io::Write; fn main() -> Result<(), Report> { color_eyre::install()?; @@ -56,6 +54,8 @@ struct Fields { } fn print_system_info(fields: &Fields) { + use crate::colors::COLORS; + let Fields { user_info, os_name, @@ -68,16 +68,22 @@ fn print_system_info(fields: &Fields) { colors, } = fields; - let _ = std::io::stdout().write_all(format!( - " - {CYAN} ▟█▖ {BLUE}▝█▙ ▗█▛ {user_info} ~{RESET} - {CYAN} ▗▄▄▟██▄▄▄▄▄{BLUE}▝█▙█▛ {CYAN}▖ {CYAN} {BLUE}System{RESET}  {os_name} - {CYAN} ▀▀▀▀▀▀▀▀▀▀▀▘{BLUE}▝██ {CYAN}▟█▖ {CYAN} {BLUE}Kernel{RESET}  {kernel_version} - {BLUE} ▟█▛ {BLUE}▝█▘{CYAN}▟█▛ {CYAN} {BLUE}Shell{RESET}  {shell} - {BLUE}▟█████▛ {CYAN}▟█████▛ {CYAN} {BLUE}Uptime{RESET}  {uptime} - {BLUE} ▟█▛{CYAN}▗█▖ {CYAN}▟█▛ {CYAN} {BLUE}Desktop{RESET}  {desktop} - {BLUE} ▝█▛ {CYAN}██▖{BLUE}▗▄▄▄▄▄▄▄▄▄▄▄ {CYAN}󰍛 {BLUE}Memory{RESET}  {memory_usage} - {BLUE} ▝ {CYAN}▟█▜█▖{BLUE}▀▀▀▀▀██▛▀▀▘ {CYAN}󱥎 {BLUE}Storage (/){RESET}  {storage} - {CYAN} ▟█▘ ▜█▖ {BLUE}▝█▛ {CYAN} {BLUE}Colors{RESET}  {colors} -").as_bytes()); + let cyan = COLORS.cyan; + let blue = COLORS.blue; + let reset = COLORS.reset; + let system_info = format!(" + {cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset} + {cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset}  {os_name} + {cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset}  {kernel_version} + {blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset}  {shell} + {blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset}  {uptime} + {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} + {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan}󰍛 {blue}Memory{reset}  {memory_usage} + {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} + {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}"); + + std::io::stdout() + .lock() + .write_all(system_info.as_bytes()) + .expect("Failed to write to stdout"); } diff --git a/src/system.rs b/src/system.rs index 74d774f..11a72d9 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,14 +1,12 @@ +use crate::colors::COLORS; use color_eyre::Result; use nix::sys::{statvfs::statvfs, utsname::UtsName}; - use std::{ env, fs::File, io::{self, Read}, }; -use crate::colors::{CYAN, GREEN, RED, RESET, YELLOW}; - pub fn get_username_and_hostname(utsname: &UtsName) -> String { let username = env::var("USER").unwrap_or("unknown_user".to_string()); let hostname = utsname @@ -16,14 +14,18 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { .to_str() .unwrap_or("unknown_host") .to_string(); - - format!("{YELLOW}{username}{RED}@{GREEN}{hostname}") + format!( + "{yellow}{username}{red}@{green}{hostname}{reset}", + yellow = COLORS.yellow, + red = COLORS.red, + green = COLORS.green, + reset = COLORS.reset, + ) } pub fn get_shell() -> String { let shell_path = env::var("SHELL").unwrap_or("unknown_shell".to_string()); let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); - shell_name.to_string() } @@ -32,16 +34,15 @@ pub fn get_root_disk_usage() -> Result { let block_size = vfs.block_size() as u64; let total_blocks = vfs.blocks(); let available_blocks = vfs.blocks_available(); - let total_size = block_size * total_blocks; let used_size = total_size - (block_size * available_blocks); - let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0); let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0); - let usage = (used_size as f64 / total_size as f64) * 100.0; - + let usage = (used_size / total_size) * 100.0; Ok(format!( - "{used_size:.2} GiB / {total_size:.2} GiB ({CYAN}{usage:.0}%{RESET})" + "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})", + cyan = COLORS.cyan, + reset = COLORS.reset, )) } @@ -50,35 +51,31 @@ pub fn get_memory_usage() -> Result { fn parse_memory_info() -> Result<(f64, f64), io::Error> { let mut total_memory_kb = 0.0; let mut available_memory_kb = 0.0; - let mut meminfo = String::with_capacity(2048); File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?; - for line in meminfo.lines() { let mut split = line.split_whitespace(); match split.next().unwrap_or_default() { "MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0), "MemAvailable:" => { available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); - // MemTotal comes before MemAvailable, stop parsing break; } _ => (), } } - let total_memory_gb = total_memory_kb / 1024.0 / 1024.0; let available_memory_gb = available_memory_kb / 1024.0 / 1024.0; let used_memory_gb = total_memory_gb - available_memory_gb; - Ok((used_memory_gb, total_memory_gb)) } let (used_memory, total_memory) = parse_memory_info()?; let percentage_used = (used_memory / total_memory * 100.0).round() as u64; - Ok(format!( - "{used_memory:.2} GiB / {total_memory:.2} GiB ({CYAN}{percentage_used}%{RESET})" + "{used_memory:.2} GiB / {total_memory:.2} GiB ({cyan}{percentage_used}%{reset})", + cyan = COLORS.cyan, + reset = COLORS.reset, )) } From a96effb87542b0f91eb602eb8b00d1356a6b2112 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 17:23:26 +0300 Subject: [PATCH 12/82] bump version --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 980d4ad..b6ae1dc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,7 +332,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.0" +version = "0.4.2" dependencies = [ "color-eyre", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 107d793..d5ced7c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.0" +version = "0.4.2" edition = "2021" [lib] From e19abcedaeef5c640382b2bc67b64419ec5f4acb Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 17:26:37 +0300 Subject: [PATCH 13/82] docs: update readme; mention `NO_COLOR` changes --- README.md | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 804fa36..866293e 100644 --- a/README.md +++ b/README.md @@ -34,23 +34,25 @@ than welcome to use it on your system: it's pretty [fast...](#benchmarks) - Name - Version - Architecture - - Current shell (from $SHELL, trimmed if store path) + - Current shell (from `$SHELL`, trimmed if store path) - Current Desktop (DE/WM/Compositor and display backend) - Memory Usage/Total Memory - Storage Usage/Total Storage (for `/` only) - Shell Colors - Did I mention fast? +- Respects [`NO_COLOR` spec](https://no-color.org/) ## Motivation Fastfetch, as its name indicates, a very fast fetch tool written in C, however, -I am not interested in any of its additional features and I very much dislike -the defaults. Microfetch is a fetch tool that you would normally write in Bash -and put in your `~/.bashrc` but actually _really_ fast because it opts-out of -all customization options provided by Fastfetch. Why? Because I can, and because -I prefer Rust for "structured" Bash scripts. +I am not interested in any of its additional features, such as customization, +and I very much dislike the defaults. Microfetch is my response to this problem, +a _very fast_ fetch tool that you would normally write in Bash and put in your +`~/.bashrc` but actually _really_ fast because it opts-out of all customization +options provided by Fastfetch, and is written in Rust. Why? Because I can, and +because I prefer Rust for "structured" Bash scripts. -I cannot re-iterate it enough, Microfetch is annoyingly fast. +I cannot re-iterate it enough, Microfetch is _annoyingly fast_. ## Benchmarks From fd18e9d24467f197c08a9d3498ce5b611ec8bb80 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 18:27:25 +0300 Subject: [PATCH 14/82] release: conditionally improve performance for `get_os_pretty_name` It is difficult to get completely accurate benchmarks, given how small the numbers we are dealing with are, but this seems to point at an overall trend of *slightly* faster execution. The change minimizes unnecessary memory allocations and string manipulations, to help ensure more performant line reading and immediate return upon finding the PRETTY_NAME without additional, redundant operations. --- src/release.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/release.rs b/src/release.rs index 3f38977..c0036fb 100644 --- a/src/release.rs +++ b/src/release.rs @@ -21,7 +21,13 @@ pub fn get_os_pretty_name() -> Result { for line in reader.lines() { let line = line?; if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { - return Ok(pretty_name.trim_matches('"').to_string()); + if let Some(trimmed) = pretty_name + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + { + return Ok(trimmed.to_string()); + } + return Ok(pretty_name.to_string()); } } From c97fa33aec091242353982062a4ecfa6bcd55679 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 18:47:04 +0300 Subject: [PATCH 15/82] 0.4.3 Moar speed, NO_COLOR support --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b6ae1dc..f2a7cd6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,7 +332,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.2" +version = "0.4.3" dependencies = [ "color-eyre", "criterion", diff --git a/Cargo.toml b/Cargo.toml index d5ced7c..9e1ea00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.2" +version = "0.4.3" edition = "2021" [lib] From 4b7836d572b5bd8b46942e5747437b5bbeeec976 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 19:13:16 +0300 Subject: [PATCH 16/82] append newline to `write_all` output Fixes a small bug that resulted in terminal artifacts. My bad, gang. --- Cargo.lock | 2 +- Cargo.toml | 2 +- src/main.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f2a7cd6..f4d5a19 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -332,7 +332,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.3" +version = "0.4.4" dependencies = [ "color-eyre", "criterion", diff --git a/Cargo.toml b/Cargo.toml index 9e1ea00..2db7a6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.3" +version = "0.4.4" edition = "2021" [lib] diff --git a/src/main.rs b/src/main.rs index 0ea1672..3eee7a0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -84,6 +84,6 @@ fn print_system_info(fields: &Fields) { std::io::stdout() .lock() - .write_all(system_info.as_bytes()) + .write_all(format!("{}\n", system_info).as_bytes()) .expect("Failed to write to stdout"); } From ea8280ef77cf147ff7853f1919ab4325494d411d Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 19 Dec 2024 20:00:37 +0300 Subject: [PATCH 17/82] docs: update benchmarks to reflect recent improvements Faster and faster we go. --- README.md | 53 +++++++++++++++++++++++++++++++++++------------------ 1 file changed, 35 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 866293e..28d6338 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ of maintainability. Runs in a _fraction of a millisecond_ and displays _most_ of the nonsense you'd see posted on r/unixporn or other internet communities. Aims to replace [fastfetch](https://github.com/fastfetch-cli/fastfetch) on my personal system, but [probably not yours](#customizing). Though, you are more -than welcome to use it on your system: it's pretty [fast...](#benchmarks) +than welcome to use it on your system: it's pretty [fast](#benchmarks)...

You will need a Nerdfonts patched font installed, and for your terminal > emulator to support said font. Microfetch uses nerdfonts glyphs by default. -Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). You can -get it through the unstable channel for the time being. The Nix flake can also -be used for bleeding-edge builds. +Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be +installed by adding `pkgs.microfetch` to your `environment.systemPackages`. +Additionally, you can try out Microfetch in a Nix shell. + +```bash +nix shell nixpkgs#microfetch +``` + +Or run it directly with `nix run` + +```bash +nix run nixpkgs#microfetch +``` Non-Nix users will have to build Microfetch with `cargo`. It is not published anywhere but I imagine you can use `cargo install --git` to install it from From 4fff13a51f0e58489d95f43862738c86be5d0147 Mon Sep 17 00:00:00 2001 From: SomeEmptyBox <121939750+SomeEmptyBox@users.noreply.github.com> Date: Thu, 19 Dec 2024 22:32:32 +0530 Subject: [PATCH 18/82] Update memory icon (#15) Makes memory icon bigger by using CPU icon from Nerdfonts instead of the memory icon. --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 3eee7a0..0d70f89 100644 --- a/src/main.rs +++ b/src/main.rs @@ -78,7 +78,7 @@ fn print_system_info(fields: &Fields) { {blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset}  {shell} {blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset}  {uptime} {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} - {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan}󰍛 {blue}Memory{reset}  {memory_usage} + {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}"); From 592fb5847466c2fd03e01da5fbd5125782a43f30 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 26 Jan 2025 00:52:22 +0300 Subject: [PATCH 19/82] get rid of color_eyre Should never have added it. Annoyingly long compile times for no reason... --- Cargo.lock | 104 +------------------------------------------------ Cargo.toml | 5 +-- src/main.rs | 5 +-- src/release.rs | 2 - src/system.rs | 13 +++++-- src/uptime.rs | 20 ++++------ 6 files changed, 21 insertions(+), 128 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f4d5a19..be694c0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -44,21 +29,6 @@ version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" -[[package]] -name = "backtrace" -version = "0.3.71" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] - [[package]] name = "bitflags" version = "2.6.0" @@ -77,12 +47,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" -[[package]] -name = "cc" -version = "1.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" - [[package]] name = "cfg-if" version = "1.0.0" @@ -147,19 +111,6 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" -[[package]] -name = "color-eyre" -version = "0.6.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5" -dependencies = [ - "backtrace", - "eyre", - "indenter", - "once_cell", - "owo-colors", -] - [[package]] name = "criterion" version = "0.5.1" @@ -233,22 +184,6 @@ version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - -[[package]] -name = "gimli" -version = "0.28.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253" - [[package]] name = "half" version = "2.4.1" @@ -265,12 +200,6 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "is-terminal" version = "0.4.13" @@ -332,23 +261,13 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.4" +version = "0.4.5" dependencies = [ - "color-eyre", "criterion", "lazy_static", "nix", ] -[[package]] -name = "miniz_oxide" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" -dependencies = [ - "adler", -] - [[package]] name = "nix" version = "0.29.0" @@ -370,15 +289,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.32.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -391,12 +301,6 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" -[[package]] -name = "owo-colors" -version = "3.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" - [[package]] name = "plotters" version = "0.3.6" @@ -492,12 +396,6 @@ version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "ryu" version = "1.0.18" diff --git a/Cargo.toml b/Cargo.toml index 2db7a6a..5ad758f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.4" +version = "0.4.5" edition = "2021" [lib] @@ -13,7 +13,6 @@ path = "src/main.rs" [dependencies] nix = { version = "0.29", features = ["fs", "hostname", "feature"] } -color-eyre = { version = "0.6", default-features = false } lazy_static = "1.5.0" [dev-dependencies] @@ -28,7 +27,7 @@ opt-level = 3 [profile.release] strip = true -opt-level = "z" +opt-level = "s" lto = true codegen-units = 1 panic = "abort" diff --git a/src/main.rs b/src/main.rs index 0d70f89..05024df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,12 +9,9 @@ use crate::desktop::get_desktop_info; use crate::release::{get_os_pretty_name, get_system_info}; use crate::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname}; use crate::uptime::get_current; -use color_eyre::Report; use std::io::Write; -fn main() -> Result<(), Report> { - color_eyre::install()?; - +fn main() -> Result<(), Box> { let args: Vec = std::env::args().collect(); if args.len() > 1 && args[1] == "--version" { println!("Microfetch {}", env!("CARGO_PKG_VERSION")); diff --git a/src/release.rs b/src/release.rs index c0036fb..08e5454 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,4 +1,3 @@ -use color_eyre::Result; use nix::sys::utsname::UtsName; use std::{ fs::File, @@ -30,6 +29,5 @@ pub fn get_os_pretty_name() -> Result { return Ok(pretty_name.to_string()); } } - Ok("Unknown".to_string()) } diff --git a/src/system.rs b/src/system.rs index 11a72d9..4cc1342 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,5 +1,4 @@ use crate::colors::COLORS; -use color_eyre::Result; use nix::sys::{statvfs::statvfs, utsname::UtsName}; use std::{ env, @@ -8,7 +7,7 @@ use std::{ }; pub fn get_username_and_hostname(utsname: &UtsName) -> String { - let username = env::var("USER").unwrap_or("unknown_user".to_string()); + let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_string()); let hostname = utsname .nodename() .to_str() @@ -24,7 +23,7 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { } pub fn get_shell() -> String { - let shell_path = env::var("SHELL").unwrap_or("unknown_shell".to_string()); + let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_string()); let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); shell_name.to_string() } @@ -34,11 +33,14 @@ pub fn get_root_disk_usage() -> Result { let block_size = vfs.block_size() as u64; let total_blocks = vfs.blocks(); let available_blocks = vfs.blocks_available(); + let total_size = block_size * total_blocks; let used_size = total_size - (block_size * available_blocks); + let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0); let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0); let usage = (used_size / total_size) * 100.0; + Ok(format!( "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})", cyan = COLORS.cyan, @@ -52,7 +54,9 @@ pub fn get_memory_usage() -> Result { let mut total_memory_kb = 0.0; let mut available_memory_kb = 0.0; let mut meminfo = String::with_capacity(2048); + File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?; + for line in meminfo.lines() { let mut split = line.split_whitespace(); match split.next().unwrap_or_default() { @@ -65,14 +69,17 @@ pub fn get_memory_usage() -> Result { _ => (), } } + let total_memory_gb = total_memory_kb / 1024.0 / 1024.0; let available_memory_gb = available_memory_kb / 1024.0 / 1024.0; let used_memory_gb = total_memory_gb - available_memory_gb; + Ok((used_memory_gb, total_memory_gb)) } let (used_memory, total_memory) = parse_memory_info()?; let percentage_used = (used_memory / total_memory * 100.0).round() as u64; + Ok(format!( "{used_memory:.2} GiB / {total_memory:.2} GiB ({cyan}{percentage_used}%{reset})", cyan = COLORS.cyan, diff --git a/src/uptime.rs b/src/uptime.rs index 6c44640..9a0d1f7 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,26 +1,20 @@ -use color_eyre::Result; use nix::sys::sysinfo::sysinfo; use std::io; pub fn get_current() -> Result { let info = sysinfo().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let uptime_seconds = info.uptime().as_secs_f64(); + let uptime_seconds = info.uptime().as_secs(); - let total_minutes = (uptime_seconds / 60.0).round() as u64; + let total_minutes = uptime_seconds / 60; let days = total_minutes / (60 * 24); let hours = (total_minutes % (60 * 24)) / 60; let minutes = total_minutes % 60; - let mut parts = Vec::with_capacity(3); - if days > 0 { - parts.push(format!("{days} days")); - } - if hours > 0 || days > 0 { - parts.push(format!("{hours} hours")); - } - if minutes > 0 || hours > 0 || days > 0 { - parts.push(format!("{minutes} minutes")); - } + let parts = [(days, "day"), (hours, "hour"), (minutes, "minute")] + .iter() + .filter(|&&(value, _)| value > 0) + .map(|&(value, label)| format!("{value} {label}{}", if value > 1 { "s" } else { "" })) + .collect::>(); Ok(parts.join(", ")) } From d0f88b179ccd769f51e012d183df7938272c6730 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 26 Jan 2025 01:06:02 +0300 Subject: [PATCH 20/82] further optimize uptime retrival MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Try and minimize expensive operations (e.g., divisions and allocations) to hopefully get a *consistent* measurable performance improvement. In my testing this brings the get_current function duration from > 1.2600 µs to < 1.2400 µs. --- src/uptime.rs | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/src/uptime.rs b/src/uptime.rs index 9a0d1f7..3a8c828 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -5,16 +5,29 @@ pub fn get_current() -> Result { let info = sysinfo().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; let uptime_seconds = info.uptime().as_secs(); - let total_minutes = uptime_seconds / 60; - let days = total_minutes / (60 * 24); - let hours = (total_minutes % (60 * 24)) / 60; - let minutes = total_minutes % 60; + let days = uptime_seconds / (60 * 60 * 24); + let hours = (uptime_seconds / 3600) % 24; + let minutes = (uptime_seconds / 60) % 60; - let parts = [(days, "day"), (hours, "hour"), (minutes, "minute")] - .iter() - .filter(|&&(value, _)| value > 0) - .map(|&(value, label)| format!("{value} {label}{}", if value > 1 { "s" } else { "" })) - .collect::>(); + let mut result = String::new(); + if days > 0 { + result.push_str(&format!("{days} day{}", if days > 1 { "s" } else { "" })); + } + if hours > 0 { + if !result.is_empty() { + result.push_str(", "); + } + result.push_str(&format!("{hours} hour{}", if hours > 1 { "s" } else { "" })); + } + if minutes > 0 { + if !result.is_empty() { + result.push_str(", "); + } + result.push_str(&format!( + "{minutes} minute{}", + if minutes > 1 { "s" } else { "" } + )); + } - Ok(parts.join(", ")) + Ok(result) } From 774442eb63a6eefc0961a353622e1b9dcbbd7133 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 26 Jan 2025 01:08:44 +0300 Subject: [PATCH 21/82] 0.4.6 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index be694c0..ef584ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -261,7 +261,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.5" +version = "0.4.6" dependencies = [ "criterion", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index 5ad758f..dbccfbb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.5" +version = "0.4.6" edition = "2021" [lib] From 9c897b5960e79856d3046b2e0dd0a8785f55f112 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 26 Jan 2025 01:24:16 +0300 Subject: [PATCH 22/82] nix: enable parallel building; clean up devshell --- nix/package.nix | 1 + nix/shell.nix | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/nix/package.nix b/nix/package.nix index 1a7dde3..506aea3 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -19,6 +19,7 @@ in }; cargoLock.lockFile = ../Cargo.lock; + enableParallelBuilding = true; meta = { description = "A microscopic fetch script in Rust, for NixOS systems"; diff --git a/nix/shell.nix b/nix/shell.nix index 2350dbd..0577a0a 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -5,7 +5,6 @@ clippy, cargo, rustc, - gcc, rustPlatform, gnuplot, }: @@ -15,7 +14,6 @@ mkShell { nativeBuildInputs = [ cargo rustc - gcc rust-analyzer-unwrapped rustfmt From a3b87f8f8345ed01ad1acc97300fd23e2c2cdaea Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Mar 2025 21:48:46 +0300 Subject: [PATCH 23/82] flake: bump nixpkgs --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 77bd438..d831dac 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1722719969, - "narHash": "sha256-E47qbT/mRtBCSZra+9S9208sp/QnNeOAq7EhHX+eMNE=", + "lastModified": 1743359643, + "narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "83a364ced9d5b8a6bdac897bbef6b91e70777b97", + "rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a", "type": "github" }, "original": { From e9aa9a2bbebf9ff3058ab1035057f4c8681c4270 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Mar 2025 22:33:16 +0300 Subject: [PATCH 24/82] uptime: get rid of nix crate --- src/uptime.rs | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/uptime.rs b/src/uptime.rs index 3a8c828..d9c53de 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,32 +1,39 @@ -use nix::sys::sysinfo::sysinfo; use std::io; pub fn get_current() -> Result { - let info = sysinfo().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - let uptime_seconds = info.uptime().as_secs(); + let uptime_seconds = unsafe { + let mut info: libc::sysinfo = std::mem::zeroed(); + if libc::sysinfo(&mut info) != 0 { + return Err(io::Error::last_os_error()); + } + info.uptime as u64 + }; - let days = uptime_seconds / (60 * 60 * 24); + let days = uptime_seconds / 86400; let hours = (uptime_seconds / 3600) % 24; let minutes = (uptime_seconds / 60) % 60; let mut result = String::new(); if days > 0 { - result.push_str(&format!("{days} day{}", if days > 1 { "s" } else { "" })); + result.push_str(&days.to_string()); + result.push_str(if days == 1 { " day" } else { " days" }); } if hours > 0 { if !result.is_empty() { result.push_str(", "); } - result.push_str(&format!("{hours} hour{}", if hours > 1 { "s" } else { "" })); + result.push_str(&hours.to_string()); + result.push_str(if hours == 1 { " hour" } else { " hours" }); } if minutes > 0 { if !result.is_empty() { result.push_str(", "); } - result.push_str(&format!( - "{minutes} minute{}", - if minutes > 1 { "s" } else { "" } - )); + result.push_str(&minutes.to_string()); + result.push_str(if minutes == 1 { " minute" } else { " minutes" }); + } + if result.is_empty() { + result.push_str("less than a minute"); } Ok(result) From 8df950d340d74aa1de92b09deebf369a732a396c Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Thu, 10 Apr 2025 08:12:19 +0300 Subject: [PATCH 25/82] bump deps; add libc crate --- Cargo.lock | 190 +++++++++++++++++++++++++++-------------------------- Cargo.toml | 7 +- 2 files changed, 100 insertions(+), 97 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index ef584ad..9e78d44 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,6 +1,6 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "aho-corasick" @@ -19,27 +19,27 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstyle" -version = "1.0.8" +version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" +checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" [[package]] name = "autocfg" -version = "1.3.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] name = "bumpalo" -version = "3.16.0" +version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" [[package]] name = "cast" @@ -88,18 +88,18 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.15" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11d8838454fda655dafd3accb2b6e2bea645b9e4078abe84a22ceb947235c5cc" +checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", ] [[package]] name = "clap_builder" -version = "4.5.15" +version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "216aec2b177652e3846684cbfe25c9964d18ec45234f0f5da5157b207ed1aab6" +checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ "anstyle", "clap_lex", @@ -107,9 +107,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" +checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" @@ -149,9 +149,9 @@ dependencies = [ [[package]] name = "crossbeam-deque" -version = "0.8.5" +version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" dependencies = [ "crossbeam-epoch", "crossbeam-utils", @@ -168,27 +168,27 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.20" +version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" +checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" [[package]] name = "crunchy" -version = "0.2.2" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" [[package]] name = "either" -version = "1.13.0" +version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" [[package]] name = "half" -version = "2.4.1" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9" dependencies = [ "cfg-if", "crunchy", @@ -196,19 +196,19 @@ dependencies = [ [[package]] name = "hermit-abi" -version = "0.4.0" +version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" [[package]] name = "is-terminal" -version = "0.4.13" +version = "0.4.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" +checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.52.0", + "windows-sys", ] [[package]] @@ -222,16 +222,17 @@ dependencies = [ [[package]] name = "itoa" -version = "1.0.11" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" +checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "js-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" +checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f" dependencies = [ + "once_cell", "wasm-bindgen", ] @@ -243,15 +244,15 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.155" +version = "0.2.171" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" +checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" [[package]] name = "log" -version = "0.4.22" +version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" @@ -261,10 +262,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.6" +version = "0.4.7" dependencies = [ "criterion", "lazy_static", + "libc", "nix", ] @@ -291,21 +293,21 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "oorandom" -version = "11.1.4" +version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" [[package]] name = "plotters" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a15b6eccb8484002195a3e44fe65a4ce8e93a625797a063735536fd59cb01cf3" +checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" dependencies = [ "num-traits", "plotters-backend", @@ -316,33 +318,33 @@ dependencies = [ [[package]] name = "plotters-backend" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "414cec62c6634ae900ea1c56128dfe87cf63e7caece0852ec76aba307cebadb7" +checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" [[package]] name = "plotters-svg" -version = "0.3.6" +version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81b30686a7d9c3e010b84284bdd26a29f2138574f52f5eb6f794fc0ad924e705" +checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" dependencies = [ "plotters-backend", ] [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.36" +version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" +checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] @@ -369,9 +371,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.6" +version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" dependencies = [ "aho-corasick", "memchr", @@ -381,9 +383,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.7" +version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" dependencies = [ "aho-corasick", "memchr", @@ -392,15 +394,21 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.4" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" [[package]] name = "ryu" -version = "1.0.18" +version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "same-file" @@ -413,18 +421,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.207" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5665e14a49a4ea1b91029ba7d3bca9f299e1f7cfa194388ccc20f14743e784f2" +checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.207" +version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6aea2634c86b0e8ef2cfdc0c340baede54ec27b1e46febd7f80dffb2aa44a00e" +checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", @@ -433,9 +441,9 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.125" +version = "1.0.140" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83c8e735a073ccf5be70aa8066aa984eaf2fa000db6c8d0100ae605b366d31ed" +checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373" dependencies = [ "itoa", "memchr", @@ -445,9 +453,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.74" +version = "2.0.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fceb41e3d546d0bd83421d3409b1460cc7444cd389341a4c880fe7a042cb3d7" +checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0" dependencies = [ "proc-macro2", "quote", @@ -466,9 +474,9 @@ dependencies = [ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "walkdir" @@ -482,24 +490,24 @@ dependencies = [ [[package]] name = "wasm-bindgen" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" +checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5" dependencies = [ "cfg-if", "once_cell", + "rustversion", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" +checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6" dependencies = [ "bumpalo", "log", - "once_cell", "proc-macro2", "quote", "syn", @@ -508,9 +516,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" +checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -518,9 +526,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" +checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de" dependencies = [ "proc-macro2", "quote", @@ -531,15 +539,18 @@ dependencies = [ [[package]] name = "wasm-bindgen-shared" -version = "0.2.93" +version = "0.2.100" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" +checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" -version = "0.3.70" +version = "0.3.77" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" +checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2" dependencies = [ "js-sys", "wasm-bindgen", @@ -551,16 +562,7 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys 0.59.0", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets", + "windows-sys", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index dbccfbb..7f00156 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "microfetch" -version = "0.4.6" -edition = "2021" +version = "0.4.7" +edition = "2024" [lib] name = "microfetch_lib" @@ -13,7 +13,8 @@ path = "src/main.rs" [dependencies] nix = { version = "0.29", features = ["fs", "hostname", "feature"] } -lazy_static = "1.5.0" +lazy_static = "1.5" +libc = "0.2" [dev-dependencies] criterion = "0.5" From c13902670424cd5e3e9a664677bdf2f1a28a5aa1 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Tue, 15 Apr 2025 03:19:53 -0400 Subject: [PATCH 26/82] refactor: clean up a few things I've decided to keep lazy_static for now as std::sync::LazyLock seems to have a slightly bigger impact on binary size. --- benches/benchmark.rs | 4 ++-- src/colors.rs | 11 ++++++----- src/main.rs | 11 +++++------ src/release.rs | 6 +++--- src/system.rs | 1 - 5 files changed, 16 insertions(+), 17 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 7706617..03e49f4 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,4 +1,4 @@ -use criterion::{criterion_group, criterion_main, Criterion}; +use criterion::{Criterion, criterion_group, criterion_main}; use microfetch_lib::colors::print_dots; use microfetch_lib::desktop::get_desktop_info; use microfetch_lib::release::{get_os_pretty_name, get_system_info}; @@ -10,7 +10,7 @@ use microfetch_lib::uptime::get_current; fn main_benchmark(c: &mut Criterion) { let utsname = nix::sys::utsname::uname().expect("lol"); c.bench_function("user_info", |b| { - b.iter(|| get_username_and_hostname(&utsname)) + b.iter(|| get_username_and_hostname(&utsname)); }); c.bench_function("os_name", |b| b.iter(get_os_pretty_name)); c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname))); diff --git a/src/colors.rs b/src/colors.rs index 061db15..11a4b9f 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -12,8 +12,8 @@ pub struct Colors { impl Colors { const fn new(is_no_color: bool) -> Self { - match is_no_color { - true => Self { + if is_no_color { + Self { reset: "", blue: "", cyan: "", @@ -21,8 +21,9 @@ impl Colors { yellow: "", red: "", magenta: "", - }, - false => Self { + } + } else { + Self { reset: "\x1b[0m", blue: "\x1b[34m", cyan: "\x1b[36m", @@ -30,7 +31,7 @@ impl Colors { yellow: "\x1b[33m", red: "\x1b[31m", magenta: "\x1b[35m", - }, + } } } } diff --git a/src/main.rs b/src/main.rs index 05024df..3297d90 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,7 +9,7 @@ use crate::desktop::get_desktop_info; use crate::release::{get_os_pretty_name, get_system_info}; use crate::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname}; use crate::uptime::get_current; -use std::io::Write; +use std::io::{Write, stdout}; fn main() -> Result<(), Box> { let args: Vec = std::env::args().collect(); @@ -20,7 +20,7 @@ fn main() -> Result<(), Box> { let fields = Fields { user_info: get_username_and_hostname(&utsname), os_name: get_os_pretty_name()?, - kernel_version: get_system_info(&utsname)?, + kernel_version: get_system_info(&utsname), shell: get_shell(), desktop: get_desktop_info(), uptime: get_current()?, @@ -77,10 +77,9 @@ fn print_system_info(fields: &Fields) { {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} - {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}"); + {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n"); - std::io::stdout() - .lock() - .write_all(format!("{}\n", system_info).as_bytes()) + stdout() + .write_all(system_info.as_bytes()) .expect("Failed to write to stdout"); } diff --git a/src/release.rs b/src/release.rs index 08e5454..6d1b20f 100644 --- a/src/release.rs +++ b/src/release.rs @@ -4,13 +4,13 @@ use std::{ io::{self, BufRead, BufReader}, }; -pub fn get_system_info(utsname: &UtsName) -> nix::Result { - Ok(format!( +pub fn get_system_info(utsname: &UtsName) -> String { + format!( "{} {} ({})", utsname.sysname().to_str().unwrap_or("Unknown"), utsname.release().to_str().unwrap_or("Unknown"), utsname.machine().to_str().unwrap_or("Unknown") - )) + ) } pub fn get_os_pretty_name() -> Result { diff --git a/src/system.rs b/src/system.rs index 4cc1342..aed8752 100644 --- a/src/system.rs +++ b/src/system.rs @@ -49,7 +49,6 @@ pub fn get_root_disk_usage() -> Result { } pub fn get_memory_usage() -> Result { - #[inline(always)] fn parse_memory_info() -> Result<(f64, f64), io::Error> { let mut total_memory_kb = 0.0; let mut available_memory_kb = 0.0; From 9713138e9415b5ce4071d39410446ebac3e7b521 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Tue, 15 Apr 2025 03:32:46 -0400 Subject: [PATCH 27/82] perf: pre-allocate strings --- src/uptime.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/uptime.rs b/src/uptime.rs index d9c53de..8643e24 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -13,7 +13,7 @@ pub fn get_current() -> Result { let hours = (uptime_seconds / 3600) % 24; let minutes = (uptime_seconds / 60) % 60; - let mut result = String::new(); + let mut result = String::with_capacity(32); if days > 0 { result.push_str(&days.to_string()); result.push_str(if days == 1 { " day" } else { " days" }); From 88c9ff5e1311e8e88a1ba67b66aa7ea0cb8d5bcc Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Tue, 15 Apr 2025 03:41:14 -0400 Subject: [PATCH 28/82] perf: don't collect args into a Vec --- src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index 3297d90..47364a9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,8 +12,7 @@ use crate::uptime::get_current; use std::io::{Write, stdout}; fn main() -> Result<(), Box> { - let args: Vec = std::env::args().collect(); - if args.len() > 1 && args[1] == "--version" { + if Some("--version") == std::env::args().nth(1).as_deref() { println!("Microfetch {}", env!("CARGO_PKG_VERSION")); } else { let utsname = nix::sys::utsname::uname()?; From 0233cdc0fc722f1cfaf99898cb13a28502622db6 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Tue, 15 Apr 2025 03:41:38 -0400 Subject: [PATCH 29/82] perf: use MaybeUninit for libc buffers --- src/uptime.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/uptime.rs b/src/uptime.rs index 8643e24..496052a 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,12 +1,12 @@ -use std::io; +use std::{io, mem::MaybeUninit}; pub fn get_current() -> Result { - let uptime_seconds = unsafe { - let mut info: libc::sysinfo = std::mem::zeroed(); - if libc::sysinfo(&mut info) != 0 { + let uptime_seconds = { + let mut info = MaybeUninit::uninit(); + if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 { return Err(io::Error::last_os_error()); } - info.uptime as u64 + unsafe { info.assume_init().uptime as u64 } }; let days = uptime_seconds / 86400; From b814b2bacf829bc9877359cd3dc0727d6de90d75 Mon Sep 17 00:00:00 2001 From: ErrorNoInternet Date: Tue, 15 Apr 2025 03:42:38 -0400 Subject: [PATCH 30/82] refactor: return write error --- src/main.rs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 47364a9..41a1818 100644 --- a/src/main.rs +++ b/src/main.rs @@ -27,7 +27,7 @@ fn main() -> Result<(), Box> { storage: get_root_disk_usage()?, colors: print_dots(), }; - print_system_info(&fields); + print_system_info(&fields)?; } Ok(()) @@ -49,7 +49,7 @@ struct Fields { colors: String, } -fn print_system_info(fields: &Fields) { +fn print_system_info(fields: &Fields) -> Result<(), Box> { use crate::colors::COLORS; let Fields { @@ -78,7 +78,5 @@ fn print_system_info(fields: &Fields) { {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n"); - stdout() - .write_all(system_info.as_bytes()) - .expect("Failed to write to stdout"); + Ok(stdout().write_all(system_info.as_bytes())?) } From f7d7c7307308b49175a342a7c63dc0a5044f0fb5 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Fri, 2 May 2025 12:50:41 +0300 Subject: [PATCH 31/82] chore: bump nix crate; 0.4.8 --- Cargo.lock | 6 +++--- Cargo.toml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 9e78d44..e65dced 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -262,7 +262,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.7" +version = "0.4.8" dependencies = [ "criterion", "lazy_static", @@ -272,9 +272,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46" +checksum = "537bc3c4a347b87fd52ac6c03a02ab1302962cfd93373c5d7a112cdc337854cc" dependencies = [ "bitflags", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index 7f00156..f598dce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.7" +version = "0.4.8" edition = "2024" [lib] @@ -12,7 +12,7 @@ name = "microfetch" path = "src/main.rs" [dependencies] -nix = { version = "0.29", features = ["fs", "hostname", "feature"] } +nix = { version = "0.30", features = ["fs", "hostname", "feature"] } lazy_static = "1.5" libc = "0.2" From 61d4b7377fcd87a3174bc14582e7188aaeb54cc0 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 22 Jun 2025 02:53:21 +0300 Subject: [PATCH 32/82] colors: remove lazy_static dependency and use LazyLock for COLORS --- Cargo.lock | 7 ------- Cargo.toml | 1 - src/colors.rs | 13 ++++++------- 3 files changed, 6 insertions(+), 15 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e65dced..c3d8d07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -236,12 +236,6 @@ dependencies = [ "wasm-bindgen", ] -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - [[package]] name = "libc" version = "0.2.171" @@ -265,7 +259,6 @@ name = "microfetch" version = "0.4.8" dependencies = [ "criterion", - "lazy_static", "libc", "nix", ] diff --git a/Cargo.toml b/Cargo.toml index f598dce..2662682 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,6 @@ path = "src/main.rs" [dependencies] nix = { version = "0.30", features = ["fs", "hostname", "feature"] } -lazy_static = "1.5" libc = "0.2" [dev-dependencies] diff --git a/src/colors.rs b/src/colors.rs index 11a4b9f..da613a5 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,4 +1,5 @@ use std::env; +use std::sync::LazyLock; pub struct Colors { pub reset: &'static str, @@ -36,13 +37,11 @@ impl Colors { } } -lazy_static::lazy_static! { - pub static ref COLORS: Colors = { - // check for NO_COLOR once at startup - let is_no_color = env::var("NO_COLOR").is_ok(); - Colors::new(is_no_color) - }; -} +pub static COLORS: LazyLock = LazyLock::new(|| { + // check for NO_COLOR once at startup + let is_no_color = env::var("NO_COLOR").is_ok(); + Colors::new(is_no_color) +}); pub fn print_dots() -> String { format!( From f20f1e33eb8948b5613138ad2440b71f2fdcfc99 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 22 Jun 2025 03:04:40 +0300 Subject: [PATCH 33/82] chore: bump dependencies --- Cargo.lock | 37 +++++++++++++------------------------ Cargo.toml | 2 +- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c3d8d07..6081b21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,25 +113,22 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" -version = "0.5.1" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2b12d017a929603d80db1831cd3a24082f8137ce19c69e6447f54f5fc8d692f" +checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "is-terminal", - "itertools", + "itertools 0.13.0", "num-traits", - "once_cell", "oorandom", "plotters", "rayon", "regex", "serde", - "serde_derive", "serde_json", "tinytemplate", "walkdir", @@ -144,7 +141,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -194,23 +191,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "hermit-abi" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbd780fe5cc30f81464441920d82ac8740e2e46b29a6fad543ddd075229ce37e" - -[[package]] -name = "is-terminal" -version = "0.4.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e04d7f318608d35d4b61ddd75cbdaee86b023ebe2bd5a66ee0915f0bf93095a9" -dependencies = [ - "hermit-abi", - "libc", - "windows-sys", -] - [[package]] name = "itertools" version = "0.10.5" @@ -220,6 +200,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.15" diff --git a/Cargo.toml b/Cargo.toml index 2662682..3af3192 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ nix = { version = "0.30", features = ["fs", "hostname", "feature"] } libc = "0.2" [dev-dependencies] -criterion = "0.5" +criterion = "0.6" [[bench]] name = "benchmark" From bc03fd73a0010c46f379e5d3b510b415bc4273b4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 22 Jun 2025 03:11:45 +0300 Subject: [PATCH 34/82] 0.4.9 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6081b21..c6cfc69 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -245,7 +245,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.8" +version = "0.4.9" dependencies = [ "criterion", "libc", diff --git a/Cargo.toml b/Cargo.toml index 3af3192..f20ff00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.8" +version = "0.4.9" edition = "2024" [lib] From 7da5fe97ccdac33f21cbcda715817f6a36425fda Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 4 Aug 2025 20:49:23 +0300 Subject: [PATCH 35/82] nix: point to nixos-unstable for the nixpkgs input Oops. Signed-off-by: NotAShelf Change-Id: I6a6a6964eab43b655668d65abe532ed710538670 --- flake.lock | 7 ++++--- flake.nix | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/flake.lock b/flake.lock index d831dac..774c899 100644 --- a/flake.lock +++ b/flake.lock @@ -2,15 +2,16 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1743359643, - "narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=", + "lastModified": 1754214453, + "narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a", + "rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376", "type": "github" }, "original": { "owner": "NixOS", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } diff --git a/flake.nix b/flake.nix index a481c30..ed36872 100644 --- a/flake.nix +++ b/flake.nix @@ -1,6 +1,6 @@ { description = "A microscopic fetch script in Rust, for NixOS systems"; - inputs.nixpkgs.url = "github:NixOS/nixpkgs"; + inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; outputs = { self, From 14d8f9390d44cc8e76686a81b1f4afeb29c6cd33 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 4 Aug 2025 20:49:49 +0300 Subject: [PATCH 36/82] chore: bump criterion Signed-off-by: NotAShelf Change-Id: I6a6a6964e55c9925511d83d6be9b2ba001613983 --- Cargo.lock | 21 ++++++--------------- Cargo.toml | 2 +- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c6cfc69..3b5c132 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -113,16 +113,16 @@ checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" [[package]] name = "criterion" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679" +checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" dependencies = [ "anes", "cast", "ciborium", "clap", "criterion-plot", - "itertools 0.13.0", + "itertools", "num-traits", "oorandom", "plotters", @@ -136,12 +136,12 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" dependencies = [ "cast", - "itertools 0.10.5", + "itertools", ] [[package]] @@ -191,15 +191,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - [[package]] name = "itertools" version = "0.13.0" diff --git a/Cargo.toml b/Cargo.toml index f20ff00..f09c8b3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ nix = { version = "0.30", features = ["fs", "hostname", "feature"] } libc = "0.2" [dev-dependencies] -criterion = "0.6" +criterion = "0.7" [[bench]] name = "benchmark" From 9d8905354efca612a1e9fe87845293a4460e0553 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 4 Aug 2025 21:03:48 +0300 Subject: [PATCH 37/82] expand comment strings 80 chars is good. Signed-off-by: NotAShelf Change-Id: I6a6a696460754266ff5810a73f98ce9e6eb0c35b --- src/main.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 41a1818..5dd99c1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -33,10 +33,9 @@ fn main() -> Result<(), Box> { Ok(()) } -// Struct to hold all the fields we need to print -// helps avoid clippy warnings about argument count -// and makes it easier to pass around, though its -// not like we need to +// Struct to hold all the fields we need in order to print the fetch. This +// helps avoid Clippy warnings about argument count, and makes it slightly +// easier to pass data around. Though, it is not like we really need to. struct Fields { user_info: String, os_name: String, From e355ddc51758b7b8b54e3bf65bc5270a152c94ef Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 17 Aug 2025 15:19:02 +0300 Subject: [PATCH 38/82] flake: bump nixpkgs Signed-off-by: NotAShelf Change-Id: Ica041259225e45e345de08325a0cc75f6a6a6964 --- flake.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/flake.lock b/flake.lock index 774c899..8fd5240 100644 --- a/flake.lock +++ b/flake.lock @@ -2,11 +2,11 @@ "nodes": { "nixpkgs": { "locked": { - "lastModified": 1754214453, - "narHash": "sha256-Q/I2xJn/j1wpkGhWkQnm20nShYnG7TI99foDBpXm1SY=", + "lastModified": 1743359643, + "narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "5b09dc45f24cf32316283e62aec81ffee3c3e376", + "rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a", "type": "github" }, "original": { From 8800b69ef3ccbb35558d7824e2f4dabd055be826 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 17 Aug 2025 15:19:26 +0300 Subject: [PATCH 39/82] chore: bump dependencies Signed-off-by: NotAShelf Change-Id: I6a6a69647ad9cfdd52eec7fa9ae4a184b9c8ea0d --- Cargo.lock | 8 ++++---- Cargo.toml | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3b5c132..25e640c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -218,9 +218,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.171" +version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6" +checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" [[package]] name = "log" @@ -245,9 +245,9 @@ dependencies = [ [[package]] name = "nix" -version = "0.30.0" +version = "0.30.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "537bc3c4a347b87fd52ac6c03a02ab1302962cfd93373c5d7a112cdc337854cc" +checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" dependencies = [ "bitflags", "cfg-if", diff --git a/Cargo.toml b/Cargo.toml index f09c8b3..b7d692f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,8 @@ name = "microfetch" path = "src/main.rs" [dependencies] -nix = { version = "0.30", features = ["fs", "hostname", "feature"] } -libc = "0.2" +nix = { version = "0.30.1", default-features = false, features = ["fs", "hostname", "feature"] } +libc = "0.2.175" [dev-dependencies] criterion = "0.7" From 6150e55ba564907a8d44c53b0a15503874d5c408 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 17 Aug 2025 15:47:24 +0300 Subject: [PATCH 40/82] flake: bump nixpkgs Signed-off-by: NotAShelf Change-Id: I6ba6412f88305b295ffb571313606bd56a6a6964 --- flake.lock | 3 +++ 1 file changed, 3 insertions(+) diff --git a/flake.lock b/flake.lock index 8fd5240..452a532 100644 --- a/flake.lock +++ b/flake.lock @@ -4,9 +4,12 @@ "locked": { "lastModified": 1743359643, "narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=", + "lastModified": 1763381801, + "narHash": "sha256-325fR0JmHW7B74/gHPv/S9w1Rfj/M2HniwQFUwdrZ9k=", "owner": "NixOS", "repo": "nixpkgs", "rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a", + "rev": "46931757ea8bdbba25c076697f8e73b8dc39fef5", "type": "github" }, "original": { From dab8f556af40fbbc05f6cbb2996cba454e866cd4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 15:42:19 +0300 Subject: [PATCH 41/82] meta: set up formatters for Rust and TOML Signed-off-by: NotAShelf Change-Id: I917d66d2af199b96f84aa50071a0ccd56a6a6964 --- .rustfmt.toml | 26 ++++++++++++++++++++++++++ .taplo.toml | 13 +++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 .rustfmt.toml create mode 100644 .taplo.toml diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..9d5c77e --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,26 @@ +condense_wildcard_suffixes = true +doc_comment_code_block_width = 80 +edition = "2024" # Keep in sync with Cargo.toml. +enum_discrim_align_threshold = 60 +force_explicit_abi = false +force_multiline_blocks = true +format_code_in_doc_comments = true +format_macro_matchers = true +format_strings = true +group_imports = "StdExternalCrate" +hex_literal_case = "Upper" +imports_granularity = "Crate" +imports_layout = "HorizontalVertical" +inline_attribute_width = 60 +match_block_trailing_comma = true +max_width = 80 +newline_style = "Unix" +normalize_comments = true +normalize_doc_attributes = true +overflow_delimited_expr = true +struct_field_align_threshold = 60 +tab_spaces = 2 +unstable_features = true +use_field_init_shorthand = true +use_try_shorthand = true +wrap_comments = true diff --git a/.taplo.toml b/.taplo.toml new file mode 100644 index 0000000..5a6456b --- /dev/null +++ b/.taplo.toml @@ -0,0 +1,13 @@ +[formatting] +align_entries = true +column_width = 100 +compact_arrays = false +reorder_inline_tables = true +reorder_keys = true + +[[rule]] +include = [ "**/Cargo.toml" ] +keys = [ "package" ] + +[rule.formatting] +reorder_keys = false From 1d69d3107c6aa63568cf5287476ae0b63f96ea44 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 15:48:02 +0300 Subject: [PATCH 42/82] microfetch: trim fetch screen by one space Signed-off-by: NotAShelf Change-Id: I200c72b5a8249ed3d23754aa3f01aea86a6a6964 --- src/main.rs | 130 ++++++++++++++++++++++++++++------------------------ 1 file changed, 70 insertions(+), 60 deletions(-) diff --git a/src/main.rs b/src/main.rs index 5dd99c1..2138a44 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,78 +4,88 @@ mod release; mod system; mod uptime; -use crate::colors::print_dots; -use crate::desktop::get_desktop_info; -use crate::release::{get_os_pretty_name, get_system_info}; -use crate::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname}; -use crate::uptime::get_current; use std::io::{Write, stdout}; -fn main() -> Result<(), Box> { - if Some("--version") == std::env::args().nth(1).as_deref() { - println!("Microfetch {}", env!("CARGO_PKG_VERSION")); - } else { - let utsname = nix::sys::utsname::uname()?; - let fields = Fields { - user_info: get_username_and_hostname(&utsname), - os_name: get_os_pretty_name()?, - kernel_version: get_system_info(&utsname), - shell: get_shell(), - desktop: get_desktop_info(), - uptime: get_current()?, - memory_usage: get_memory_usage()?, - storage: get_root_disk_usage()?, - colors: print_dots(), - }; - print_system_info(&fields)?; - } +use crate::{ + colors::print_dots, + desktop::get_desktop_info, + release::{get_os_pretty_name, get_system_info}, + system::{ + get_memory_usage, + get_root_disk_usage, + get_shell, + get_username_and_hostname, + }, + uptime::get_current, +}; - Ok(()) +fn main() -> Result<(), Box> { + if Some("--version") == std::env::args().nth(1).as_deref() { + println!("Microfetch {}", env!("CARGO_PKG_VERSION")); + } else { + let utsname = nix::sys::utsname::uname()?; + let fields = Fields { + user_info: get_username_and_hostname(&utsname), + os_name: get_os_pretty_name()?, + kernel_version: get_system_info(&utsname), + shell: get_shell(), + desktop: get_desktop_info(), + uptime: get_current()?, + memory_usage: get_memory_usage()?, + storage: get_root_disk_usage()?, + colors: print_dots(), + }; + print_system_info(&fields)?; + } + + Ok(()) } // Struct to hold all the fields we need in order to print the fetch. This // helps avoid Clippy warnings about argument count, and makes it slightly // easier to pass data around. Though, it is not like we really need to. struct Fields { - user_info: String, - os_name: String, - kernel_version: String, - shell: String, - uptime: String, - desktop: String, - memory_usage: String, - storage: String, - colors: String, + user_info: String, + os_name: String, + kernel_version: String, + shell: String, + uptime: String, + desktop: String, + memory_usage: String, + storage: String, + colors: String, } -fn print_system_info(fields: &Fields) -> Result<(), Box> { - use crate::colors::COLORS; +fn print_system_info( + fields: &Fields, +) -> Result<(), Box> { + use crate::colors::COLORS; - let Fields { - user_info, - os_name, - kernel_version, - shell, - uptime, - desktop, - memory_usage, - storage, - colors, - } = fields; + let Fields { + user_info, + os_name, + kernel_version, + shell, + uptime, + desktop, + memory_usage, + storage, + colors, + } = fields; - let cyan = COLORS.cyan; - let blue = COLORS.blue; - let reset = COLORS.reset; - let system_info = format!(" - {cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset} - {cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset}  {os_name} - {cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset}  {kernel_version} - {blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset}  {shell} - {blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset}  {uptime} - {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} - {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} - {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} - {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n"); + let cyan = COLORS.cyan; + let blue = COLORS.blue; + let reset = COLORS.reset; + let system_info = format!(" + {cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset} + {cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset}  {os_name} + {cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset}  {kernel_version} + {blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset}  {shell} + {blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset}  {uptime} + {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} + {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} + {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} + {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n"); - Ok(stdout().write_all(system_info.as_bytes())?) + Ok(stdout().write_all(system_info.as_bytes())?) } From d43880073812e1d842115c91ae1771a5faaff88a Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 15:51:56 +0300 Subject: [PATCH 43/82] nix: streamline source filter; move RUSTFLAGS to env attrs Signed-off-by: NotAShelf Change-Id: I9761b908d05efd7dc10d53c54ca80fb26a6a6964 --- nix/package.nix | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/nix/package.nix b/nix/package.nix index 506aea3..5bb387c 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -9,23 +9,29 @@ inherit (toml) version; in rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} { - RUSTFLAGS = "-C link-arg=-fuse-ld=mold"; - inherit pname version; - - src = builtins.path { - name = "${pname}-${version}"; - path = lib.sources.cleanSource ../.; - }; + src = let + fs = lib.fileset; + s = ../.; + in + fs.toSource { + root = s; + fileset = fs.unions [ + (fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src)) + (s + /Cargo.lock) + (s + /Cargo.toml) + ]; + }; cargoLock.lockFile = ../Cargo.lock; enableParallelBuilding = true; + env.RUSTFLAGS = "-C link-arg=-fuse-ld=mold"; meta = { - description = "A microscopic fetch script in Rust, for NixOS systems"; + description = "Microscopic fetch script in Rust, for NixOS systems"; homepage = "https://github.com/NotAShelf/microfetch"; license = lib.licenses.gpl3Only; - maintainers = with lib.maintainers; [NotAShelf]; + maintainers = [lib.maintainers.NotAShelf]; mainProgram = "microfetch"; }; } From 4c22cf5d2abcb54923009a1d50828325c028596c Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 15:59:39 +0300 Subject: [PATCH 44/82] nix: use nightly rustfmt; add taplo Signed-off-by: NotAShelf Change-Id: Ie4a1b5a29166931aac006a44874374346a6a6964 --- nix/shell.nix | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/nix/shell.nix b/nix/shell.nix index 0577a0a..111b803 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -4,6 +4,7 @@ rustfmt, clippy, cargo, + taplo, rustc, rustPlatform, gnuplot, @@ -16,8 +17,9 @@ mkShell { rustc rust-analyzer-unwrapped - rustfmt + (rustfmt.override {asNightly = true;}) clippy + taplo gnuplot # For Criterion.rs plots ]; From af8031f9ec869d8c4ccf867453be9d2c000e259f Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 15:59:56 +0300 Subject: [PATCH 45/82] meta: selectively enable Clippy lint-groups projectwide Signed-off-by: NotAShelf Change-Id: Ic6eedd28e1ad48a67c988607dd45d7686a6a6964 --- Cargo.toml | 46 +++++++++++++++++++++++++++++++++++----------- 1 file changed, 35 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b7d692f..5ecade2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "microfetch" +name = "microfetch" version = "0.4.9" edition = "2024" @@ -12,28 +12,52 @@ name = "microfetch" path = "src/main.rs" [dependencies] -nix = { version = "0.30.1", default-features = false, features = ["fs", "hostname", "feature"] } libc = "0.2.175" +nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" } [dev-dependencies] criterion = "0.7" [[bench]] -name = "benchmark" harness = false +name = "benchmark" [profile.dev] -opt-level = 3 +opt-level = 1 [profile.release] -strip = true -opt-level = "s" -lto = true codegen-units = 1 -panic = "abort" +lto = true +opt-level = "s" +panic = "abort" +strip = true [profile.profiler] -inherits = "release" -debug = true +debug = true +inherits = "release" split-debuginfo = "unpacked" -strip = "none" +strip = "none" + +[lints.clippy] +complexity = { level = "warn", priority = -1 } +nursery = { level = "warn", priority = -1 } +pedantic = { level = "warn", priority = -1 } +perf = { level = "warn", priority = -1 } +style = { level = "warn", priority = -1 } + +# The lint groups above enable some less-than-desirable rules, we should manually +# enable those to keep our sanity. +absolute_paths = "allow" +arbitrary_source_item_ordering = "allow" +implicit_return = "allow" +missing_docs_in_private_items = "allow" +non_ascii_literal = "allow" +pattern_type_mismatch = "allow" +print_stdout = "allow" +question_mark_used = "allow" +similar_names = "allow" +single_call_fn = "allow" +std_instead_of_core = "allow" +too_long_first_doc_paragraph = "allow" +too_many_lines = "allow" +unused_trait_names = "allow" From 9bd4c9a70aeb1865ab92da897c6303398816f761 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 16:10:31 +0300 Subject: [PATCH 46/82] treewide: format with nightly rustfmt rules Signed-off-by: NotAShelf Change-Id: Ib8502372dafe2e970024f606b44825af6a6a6964 --- benches/benchmark.rs | 41 +++++++------ src/colors.rs | 88 +++++++++++++-------------- src/desktop.rs | 45 +++++++------- src/release.rs | 48 ++++++++------- src/system.rs | 139 +++++++++++++++++++++++-------------------- src/uptime.rs | 62 +++++++++---------- 6 files changed, 220 insertions(+), 203 deletions(-) diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 03e49f4..33dc1b9 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,26 +1,31 @@ use criterion::{Criterion, criterion_group, criterion_main}; -use microfetch_lib::colors::print_dots; -use microfetch_lib::desktop::get_desktop_info; -use microfetch_lib::release::{get_os_pretty_name, get_system_info}; -use microfetch_lib::system::{ - get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname, +use microfetch_lib::{ + colors::print_dots, + desktop::get_desktop_info, + release::{get_os_pretty_name, get_system_info}, + system::{ + get_memory_usage, + get_root_disk_usage, + get_shell, + get_username_and_hostname, + }, + uptime::get_current, }; -use microfetch_lib::uptime::get_current; fn main_benchmark(c: &mut Criterion) { - let utsname = nix::sys::utsname::uname().expect("lol"); - c.bench_function("user_info", |b| { - b.iter(|| get_username_and_hostname(&utsname)); - }); - c.bench_function("os_name", |b| b.iter(get_os_pretty_name)); - c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname))); - c.bench_function("shell", |b| b.iter(get_shell)); + let utsname = nix::sys::utsname::uname().expect("lol"); + c.bench_function("user_info", |b| { + b.iter(|| get_username_and_hostname(&utsname)); + }); + c.bench_function("os_name", |b| b.iter(get_os_pretty_name)); + c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname))); + c.bench_function("shell", |b| b.iter(get_shell)); - c.bench_function("desktop", |b| b.iter(get_desktop_info)); - c.bench_function("uptime", |b| b.iter(get_current)); - c.bench_function("memory_usage", |b| b.iter(get_memory_usage)); - c.bench_function("storage", |b| b.iter(get_root_disk_usage)); - c.bench_function("colors", |b| b.iter(print_dots)); + c.bench_function("desktop", |b| b.iter(get_desktop_info)); + c.bench_function("uptime", |b| b.iter(get_current)); + c.bench_function("memory_usage", |b| b.iter(get_memory_usage)); + c.bench_function("storage", |b| b.iter(get_root_disk_usage)); + c.bench_function("colors", |b| b.iter(print_dots)); } criterion_group!(benches, main_benchmark); diff --git a/src/colors.rs b/src/colors.rs index da613a5..733ec53 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,57 +1,57 @@ -use std::env; -use std::sync::LazyLock; +use std::{env, sync::LazyLock}; pub struct Colors { - pub reset: &'static str, - pub blue: &'static str, - pub cyan: &'static str, - pub green: &'static str, - pub yellow: &'static str, - pub red: &'static str, - pub magenta: &'static str, + pub reset: &'static str, + pub blue: &'static str, + pub cyan: &'static str, + pub green: &'static str, + pub yellow: &'static str, + pub red: &'static str, + pub magenta: &'static str, } impl Colors { - const fn new(is_no_color: bool) -> Self { - if is_no_color { - Self { - reset: "", - blue: "", - cyan: "", - green: "", - yellow: "", - red: "", - magenta: "", - } - } else { - Self { - reset: "\x1b[0m", - blue: "\x1b[34m", - cyan: "\x1b[36m", - green: "\x1b[32m", - yellow: "\x1b[33m", - red: "\x1b[31m", - magenta: "\x1b[35m", - } - } + const fn new(is_no_color: bool) -> Self { + if is_no_color { + Self { + reset: "", + blue: "", + cyan: "", + green: "", + yellow: "", + red: "", + magenta: "", + } + } else { + Self { + reset: "\x1b[0m", + blue: "\x1b[34m", + cyan: "\x1b[36m", + green: "\x1b[32m", + yellow: "\x1b[33m", + red: "\x1b[31m", + magenta: "\x1b[35m", + } } + } } pub static COLORS: LazyLock = LazyLock::new(|| { - // check for NO_COLOR once at startup - let is_no_color = env::var("NO_COLOR").is_ok(); - Colors::new(is_no_color) + // check for NO_COLOR once at startup + let is_no_color = env::var("NO_COLOR").is_ok(); + Colors::new(is_no_color) }); +#[must_use] pub fn print_dots() -> String { - format!( - "{} {} {} {} {} {} {}", - COLORS.blue, - COLORS.cyan, - COLORS.green, - COLORS.yellow, - COLORS.red, - COLORS.magenta, - COLORS.reset, - ) + format!( + "{} {} {} {} {} {} {}", + COLORS.blue, + COLORS.cyan, + COLORS.green, + COLORS.yellow, + COLORS.red, + COLORS.magenta, + COLORS.reset, + ) } diff --git a/src/desktop.rs b/src/desktop.rs index f35cd2a..e5e6039 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,28 +1,29 @@ +#[must_use] pub fn get_desktop_info() -> String { - // Retrieve the environment variables and handle Result types - let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); - let display_backend_result = std::env::var("XDG_SESSION_TYPE"); + // Retrieve the environment variables and handle Result types + let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); + let display_backend_result = std::env::var("XDG_SESSION_TYPE"); - // Capitalize the first letter of the display backend value - let mut display_backend = display_backend_result.unwrap_or_default(); - if let Some(c) = display_backend.as_mut_str().get_mut(0..1) { - c.make_ascii_uppercase(); - } + // Capitalize the first letter of the display backend value + let mut display_backend = display_backend_result.unwrap_or_default(); + if let Some(c) = display_backend.as_mut_str().get_mut(0..1) { + c.make_ascii_uppercase(); + } - // Trim "none+" from the start of desktop_env if present - // Use "Unknown" if desktop_env is empty or has an error - let desktop_env = match desktop_env { - Err(_) => "Unknown".to_string(), - Ok(s) => s.trim_start_matches("none+").to_string(), - }; + // Trim "none+" from the start of desktop_env if present + // Use "Unknown" if desktop_env is empty or has an error + let desktop_env = match desktop_env { + Err(_) => "Unknown".to_owned(), + Ok(s) => s.trim_start_matches("none+").to_owned(), + }; - // Handle the case where display_backend might be empty after capitalization - let display_backend = if display_backend.is_empty() { - "Unknown" - } else { - &display_backend - } - .to_string(); + // Handle the case where display_backend might be empty after capitalization + let display_backend = if display_backend.is_empty() { + "Unknown" + } else { + &display_backend + } + .to_owned(); - format!("{desktop_env} ({display_backend})") + format!("{desktop_env} ({display_backend})") } diff --git a/src/release.rs b/src/release.rs index 6d1b20f..20a9b81 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,33 +1,35 @@ -use nix::sys::utsname::UtsName; use std::{ - fs::File, - io::{self, BufRead, BufReader}, + fs::File, + io::{self, BufRead, BufReader}, }; +use nix::sys::utsname::UtsName; + +#[must_use] pub fn get_system_info(utsname: &UtsName) -> String { - format!( - "{} {} ({})", - utsname.sysname().to_str().unwrap_or("Unknown"), - utsname.release().to_str().unwrap_or("Unknown"), - utsname.machine().to_str().unwrap_or("Unknown") - ) + format!( + "{} {} ({})", + utsname.sysname().to_str().unwrap_or("Unknown"), + utsname.release().to_str().unwrap_or("Unknown"), + utsname.machine().to_str().unwrap_or("Unknown") + ) } pub fn get_os_pretty_name() -> Result { - let file = File::open("/etc/os-release")?; - let reader = BufReader::new(file); + let file = File::open("/etc/os-release")?; + let reader = BufReader::new(file); - for line in reader.lines() { - let line = line?; - if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { - if let Some(trimmed) = pretty_name - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) - { - return Ok(trimmed.to_string()); - } - return Ok(pretty_name.to_string()); - } + for line in reader.lines() { + let line = line?; + if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { + if let Some(trimmed) = pretty_name + .strip_prefix('"') + .and_then(|s| s.strip_suffix('"')) + { + return Ok(trimmed.to_owned()); + } + return Ok(pretty_name.to_owned()); } - Ok("Unknown".to_string()) + } + Ok("Unknown".to_owned()) } diff --git a/src/system.rs b/src/system.rs index aed8752..1cfd12c 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,87 +1,96 @@ -use crate::colors::COLORS; -use nix::sys::{statvfs::statvfs, utsname::UtsName}; use std::{ - env, - fs::File, - io::{self, Read}, + env, + fs::File, + io::{self, Read}, }; +use nix::sys::{statvfs::statvfs, utsname::UtsName}; + +use crate::colors::COLORS; + +#[must_use] pub fn get_username_and_hostname(utsname: &UtsName) -> String { - let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_string()); - let hostname = utsname - .nodename() - .to_str() - .unwrap_or("unknown_host") - .to_string(); - format!( - "{yellow}{username}{red}@{green}{hostname}{reset}", - yellow = COLORS.yellow, - red = COLORS.red, - green = COLORS.green, - reset = COLORS.reset, - ) + let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned()); + let hostname = utsname + .nodename() + .to_str() + .unwrap_or("unknown_host") + .to_owned(); + format!( + "{yellow}{username}{red}@{green}{hostname}{reset}", + yellow = COLORS.yellow, + red = COLORS.red, + green = COLORS.green, + reset = COLORS.reset, + ) } +#[must_use] pub fn get_shell() -> String { - let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_string()); - let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); - shell_name.to_string() + let shell_path = + env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned()); + let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); + shell_name.to_owned() } pub fn get_root_disk_usage() -> Result { - let vfs = statvfs("/")?; - let block_size = vfs.block_size() as u64; - let total_blocks = vfs.blocks(); - let available_blocks = vfs.blocks_available(); + let vfs = statvfs("/")?; + let block_size = vfs.block_size() as u64; + let total_blocks = vfs.blocks(); + let available_blocks = vfs.blocks_available(); - let total_size = block_size * total_blocks; - let used_size = total_size - (block_size * available_blocks); + let total_size = block_size * total_blocks; + let used_size = total_size - (block_size * available_blocks); - let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0); - let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0); - let usage = (used_size / total_size) * 100.0; + let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0); + let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0); + let usage = (used_size / total_size) * 100.0; - Ok(format!( - "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})", - cyan = COLORS.cyan, - reset = COLORS.reset, - )) + Ok(format!( + "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})", + cyan = COLORS.cyan, + reset = COLORS.reset, + )) } pub fn get_memory_usage() -> Result { - fn parse_memory_info() -> Result<(f64, f64), io::Error> { - let mut total_memory_kb = 0.0; - let mut available_memory_kb = 0.0; - let mut meminfo = String::with_capacity(2048); + fn parse_memory_info() -> Result<(f64, f64), io::Error> { + let mut total_memory_kb = 0.0; + let mut available_memory_kb = 0.0; + let mut meminfo = String::with_capacity(2048); - File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?; + File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?; - for line in meminfo.lines() { - let mut split = line.split_whitespace(); - match split.next().unwrap_or_default() { - "MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0), - "MemAvailable:" => { - available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); - // MemTotal comes before MemAvailable, stop parsing - break; - } - _ => (), - } - } - - let total_memory_gb = total_memory_kb / 1024.0 / 1024.0; - let available_memory_gb = available_memory_kb / 1024.0 / 1024.0; - let used_memory_gb = total_memory_gb - available_memory_gb; - - Ok((used_memory_gb, total_memory_gb)) + for line in meminfo.lines() { + let mut split = line.split_whitespace(); + match split.next().unwrap_or_default() { + "MemTotal:" => { + total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0) + }, + "MemAvailable:" => { + available_memory_kb = + split.next().unwrap_or("0").parse().unwrap_or(0.0); + // MemTotal comes before MemAvailable, stop parsing + break; + }, + _ => (), + } } - let (used_memory, total_memory) = parse_memory_info()?; - let percentage_used = (used_memory / total_memory * 100.0).round() as u64; + let total_memory_gb = total_memory_kb / 1024.0 / 1024.0; + let available_memory_gb = available_memory_kb / 1024.0 / 1024.0; + let used_memory_gb = total_memory_gb - available_memory_gb; - Ok(format!( - "{used_memory:.2} GiB / {total_memory:.2} GiB ({cyan}{percentage_used}%{reset})", - cyan = COLORS.cyan, - reset = COLORS.reset, - )) + Ok((used_memory_gb, total_memory_gb)) + } + + let (used_memory, total_memory) = parse_memory_info()?; + let percentage_used = (used_memory / total_memory * 100.0).round() as u64; + + Ok(format!( + "{used_memory:.2} GiB / {total_memory:.2} GiB \ + ({cyan}{percentage_used}%{reset})", + cyan = COLORS.cyan, + reset = COLORS.reset, + )) } diff --git a/src/uptime.rs b/src/uptime.rs index 496052a..87b95cf 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,40 +1,40 @@ use std::{io, mem::MaybeUninit}; pub fn get_current() -> Result { - let uptime_seconds = { - let mut info = MaybeUninit::uninit(); - if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 { - return Err(io::Error::last_os_error()); - } - unsafe { info.assume_init().uptime as u64 } - }; + let uptime_seconds = { + let mut info = MaybeUninit::uninit(); + if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 { + return Err(io::Error::last_os_error()); + } + unsafe { info.assume_init().uptime as u64 } + }; - let days = uptime_seconds / 86400; - let hours = (uptime_seconds / 3600) % 24; - let minutes = (uptime_seconds / 60) % 60; + let days = uptime_seconds / 86400; + let hours = (uptime_seconds / 3600) % 24; + let minutes = (uptime_seconds / 60) % 60; - let mut result = String::with_capacity(32); - if days > 0 { - result.push_str(&days.to_string()); - result.push_str(if days == 1 { " day" } else { " days" }); + let mut result = String::with_capacity(32); + if days > 0 { + result.push_str(&days.to_string()); + result.push_str(if days == 1 { " day" } else { " days" }); + } + if hours > 0 { + if !result.is_empty() { + result.push_str(", "); } - if hours > 0 { - if !result.is_empty() { - result.push_str(", "); - } - result.push_str(&hours.to_string()); - result.push_str(if hours == 1 { " hour" } else { " hours" }); - } - if minutes > 0 { - if !result.is_empty() { - result.push_str(", "); - } - result.push_str(&minutes.to_string()); - result.push_str(if minutes == 1 { " minute" } else { " minutes" }); - } - if result.is_empty() { - result.push_str("less than a minute"); + result.push_str(&hours.to_string()); + result.push_str(if hours == 1 { " hour" } else { " hours" }); + } + if minutes > 0 { + if !result.is_empty() { + result.push_str(", "); } + result.push_str(&minutes.to_string()); + result.push_str(if minutes == 1 { " minute" } else { " minutes" }); + } + if result.is_empty() { + result.push_str("less than a minute"); + } - Ok(result) + Ok(result) } From 2a6fe2a3f1444fd1eba285bbefe0439e74dd033e Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 17:39:21 +0300 Subject: [PATCH 47/82] treewide: set up Hotpath for benchmarking individual allocations Signed-off-by: NotAShelf Change-Id: I0351e5753996e6d0391fc9e2f329878a6a6a6964 --- Cargo.lock | 1055 +++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 11 +- src/colors.rs | 1 + src/desktop.rs | 1 + src/main.rs | 2 + src/release.rs | 2 + src/system.rs | 5 + src/uptime.rs | 1 + 8 files changed, 1075 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 25e640c..40f74fc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "aho-corasick" version = "1.1.3" @@ -17,18 +23,80 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" +[[package]] +name = "anstream" +version = "0.6.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + [[package]] name = "anstyle" version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" +[[package]] +name = "anstyle-parse" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" +dependencies = [ + "anstyle", + "once_cell_polyfill", + "windows-sys 0.61.2", +] + +[[package]] +name = "arc-swap" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" + +[[package]] +name = "ascii" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16" + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "bitflags" version = "2.9.0" @@ -41,12 +109,34 @@ version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b35204fbdc0b3f4446b89fc1ac2cf84a8a68971995d0bf2e925ec7cd960f9cb3" + [[package]] name = "cast" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" +[[package]] +name = "cc" +version = "1.2.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36" +dependencies = [ + "find-msvc-tools", + "shlex", +] + [[package]] name = "cfg-if" version = "1.0.0" @@ -59,6 +149,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" +[[package]] +name = "chunked_transfer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" + [[package]] name = "ciborium" version = "0.2.2" @@ -93,6 +189,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" dependencies = [ "clap_builder", + "clap_derive", ] [[package]] @@ -101,8 +198,22 @@ version = "4.5.35" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" dependencies = [ + "anstream", "anstyle", "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", ] [[package]] @@ -111,6 +222,59 @@ version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" +[[package]] +name = "colorchoice" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75" + +[[package]] +name = "colored" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e" +dependencies = [ + "windows-sys 0.59.0", +] + +[[package]] +name = "cookie" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ddef33a339a91ea89fb53151bd0a4689cfce27055c291dfa69945475d22c747" +dependencies = [ + "percent-encoding", + "time", + "version_check", +] + +[[package]] +name = "cookie_store" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fc4bff745c9b4c7fb1e97b25d13153da2bc7796260141df62378998d070207f" +dependencies = [ + "cookie", + "document-features", + "idna", + "indexmap", + "log", + "serde", + "serde_derive", + "serde_json", + "time", + "url", +] + +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "criterion" version = "0.7.0" @@ -144,6 +308,15 @@ dependencies = [ "itertools", ] +[[package]] +name = "crossbeam-channel" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-deque" version = "0.8.6" @@ -175,12 +348,126 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" +[[package]] +name = "deranged" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "displaydoc" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "97369cbbc041bc366949bc74d34658d6cda5621039731c6310521892a3a20ae0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "document-features" +version = "0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4b8a88685455ed29a21542a33abd9cb6510b6b129abadabdcef0f4c55bc8f61" +dependencies = [ + "litrs", +] + [[package]] name = "either" version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" +[[package]] +name = "encode_unicode" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844" + +[[package]] +name = "flate2" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfe33edd8e85a12a67454e37f8c75e730830d83e313556ab9ebf9ee7fbeb3bfb" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "getrandom" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + [[package]] name = "half" version = "2.6.0" @@ -191,6 +478,227 @@ dependencies = [ "crunchy", ] +[[package]] +name = "hashbrown" +version = "0.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5419bdc4f6a9207fbeba6d11b604d481addf78ecd10c11ad51e76c2f6482748d" + +[[package]] +name = "hdrhistogram" +version = "7.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" +dependencies = [ + "byteorder", + "num-traits", +] + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "hotpath" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef44238d7007bbd3c41ce7dbf3d7a4fb224bbf70d6cf3162479138dda0cd6a9e" +dependencies = [ + "arc-swap", + "base64", + "cfg-if", + "clap", + "colored", + "crossbeam-channel", + "eyre", + "hdrhistogram", + "hotpath-macros", + "prettytable-rs", + "quanta", + "serde", + "serde_json", + "tiny_http", + "tokio", + "ureq", +] + +[[package]] +name = "hotpath-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e7e3443c6f4e5cc69e6b701f7bfca8a5502f0080e94629413346e7b4518d730" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "http" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4a85d31aea989eead29a3aaf9e1115a180df8282431156e533de47660892565" +dependencies = [ + "bytes", + "fnv", + "itoa", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "icu_collections" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c6b649701667bbe825c3b7e6388cb521c23d88644678e83c0c4d0a621a34b43" +dependencies = [ + "displaydoc", + "potential_utf", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edba7861004dd3714265b4db54a3c390e880ab658fec5f7db895fae2046b5bb6" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f6c8828b67bf8908d82127b2054ea1b4427ff0230ee9141c54251934ab1b599" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7aedcccd01fc5fe81e6b489c15b247b8b0690feb23304303a9e560f37efc560a" + +[[package]] +name = "icu_properties" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e93fcd3157766c0c8da2f8cff6ce651a31f0810eaa1c51ec363ef790bbb5fb99" +dependencies = [ + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02845b3647bb045f1100ecd6480ff52f34c35f82d9880e029d329c21d1054899" + +[[package]] +name = "icu_provider" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85962cf0ce02e1e0a629cc34e7ca3e373ce20dda4c4d7294bbd0bf1fdb59e614" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acae9609540aa318d1bc588455225fb2085b9ed0c4f6bd0d9d5bcd86f1a0344" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indenter" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5" + +[[package]] +name = "indexmap" +version = "2.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6717a8d2a5a929a1a2eb43a12812498ed141a0bcfb7e8f7844fbdbe4303bba9f" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi", + "libc", + "windows-sys 0.59.0", +] + +[[package]] +name = "is_terminal_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695" + [[package]] name = "itertools" version = "0.13.0" @@ -216,12 +724,40 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + [[package]] name = "libc" version = "0.2.175" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +[[package]] +name = "libredox" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb" +dependencies = [ + "bitflags", + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6373607a59f0be73a39b6fe456b8192fcc3585f602af20751600e974dd455e77" + +[[package]] +name = "litrs" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11d3d7f243d5c5a8b9bb5d6dd2b1602c0cb0b9db1621bafc7ed66e35ff9fe092" + [[package]] name = "log" version = "0.4.27" @@ -239,10 +775,21 @@ name = "microfetch" version = "0.4.9" dependencies = [ "criterion", + "hotpath", "libc", "nix", ] +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "nix" version = "0.30.1" @@ -255,6 +802,12 @@ dependencies = [ "libc", ] +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + [[package]] name = "num-traits" version = "0.2.19" @@ -270,12 +823,30 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "once_cell_polyfill" +version = "1.70.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe" + [[package]] name = "oorandom" version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project-lite" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" + [[package]] name = "plotters" version = "0.3.7" @@ -304,6 +875,34 @@ dependencies = [ "plotters-backend", ] +[[package]] +name = "potential_utf" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b73949432f5e2a09657003c25bca5e19a0e9c84f8058ca374f49e0ebe605af77" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "prettytable-rs" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a" +dependencies = [ + "encode_unicode", + "is-terminal", + "lazy_static", + "term", + "unicode-width", +] + [[package]] name = "proc-macro2" version = "1.0.94" @@ -313,6 +912,21 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi", + "web-sys", + "winapi", +] + [[package]] name = "quote" version = "1.0.40" @@ -322,6 +936,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags", +] + [[package]] name = "rayon" version = "1.10.0" @@ -342,6 +965,17 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "redox_users" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" +dependencies = [ + "getrandom", + "libredox", + "thiserror", +] + [[package]] name = "regex" version = "1.11.1" @@ -371,6 +1005,55 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "log", + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.20" @@ -424,6 +1107,42 @@ dependencies = [ "serde", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "simd-adler32" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" + +[[package]] +name = "smallvec" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "syn" version = "2.0.100" @@ -435,6 +1154,101 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "term" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" +dependencies = [ + "dirs-next", + "rustversion", + "winapi", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "time" +version = "0.3.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" + +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tiny_http" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82" +dependencies = [ + "ascii", + "chunked_transfer", + "httpdate", + "log", +] + +[[package]] +name = "tinystr" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42d3e9c45c09de15d06dd8acf5f4e0e399e85927b7f00711024eb7ae10fa4869" +dependencies = [ + "displaydoc", + "zerovec", +] + [[package]] name = "tinytemplate" version = "1.2.1" @@ -445,12 +1259,101 @@ dependencies = [ "serde_json", ] +[[package]] +name = "tokio" +version = "1.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408" +dependencies = [ + "pin-project-lite", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[package]] +name = "unicode-width" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "ureq" +version = "3.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d39cb1dbab692d82a977c0392ffac19e188bd9186a9f32806f0aaa859d75585a" +dependencies = [ + "base64", + "cookie_store", + "flate2", + "log", + "percent-encoding", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "ureq-proto", + "utf-8", + "webpki-roots", +] + +[[package]] +name = "ureq-proto" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b4531c118335662134346048ddb0e54cc86bd7e81866757873055f0e38f5d2" +dependencies = [ + "base64", + "http", + "httparse", + "log", +] + +[[package]] +name = "url" +version = "2.5.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + [[package]] name = "walkdir" version = "2.5.0" @@ -461,6 +1364,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + [[package]] name = "wasm-bindgen" version = "0.2.100" @@ -529,13 +1438,59 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "webpki-roots" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2878ef029c47c6e8cf779119f20fcf52bde7ad42a731b2a304bc221df17571e" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ - "windows-sys", + "windows-sys 0.59.0", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", ] [[package]] @@ -547,6 +1502,15 @@ dependencies = [ "windows-targets", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.52.6" @@ -610,3 +1574,92 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "writeable" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9edde0db4769d2dc68579893f2306b26c6ecfbe0ef499b013d731b7b9247e0b9" + +[[package]] +name = "yoke" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72d6e5c6afb84d73944e5cedb052c4680d5657337201555f9f2a16b7406d4954" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b659052874eb698efe5b9e8cf382204678a0086ebf46982b79d6ca3182927e5d" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50cc42e0333e05660c3587f3bf9d0478688e15d870fab3346451ce7f8c9fbea5" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d71e5d6e06ab090c67b5e44993ec16b72dcbaabc526db883a360057678b48502" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + +[[package]] +name = "zerotrie" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a59c17a5562d507e4b54960e8569ebee33bee890c70aa3fe7b97e85a9fd7851" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6c28719294829477f525be0186d13efa9a3c602f7ec202ca9e353d310fb9a002" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eadce39539ca5cb3985590102671f2567e659fca9666581ad3411d59207951f3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] diff --git a/Cargo.toml b/Cargo.toml index 5ecade2..6af4917 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,12 +12,19 @@ name = "microfetch" path = "src/main.rs" [dependencies] -libc = "0.2.175" -nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" } +hotpath = { optional = true, version = "0.6" } +libc = "0.2.175" +nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" } [dev-dependencies] criterion = "0.7" +[features] +hotpath = [ "dep:hotpath", "hotpath/hotpath" ] +hotpath-alloc-bytes-total = [ "hotpath/hotpath-alloc-bytes-total" ] +hotpath-alloc-count-total = [ "hotpath/hotpath-alloc-count-total" ] +hotpath-off = [ "hotpath/hotpath-off" ] + [[bench]] harness = false name = "benchmark" diff --git a/src/colors.rs b/src/colors.rs index 733ec53..53b12f6 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -43,6 +43,7 @@ pub static COLORS: LazyLock = LazyLock::new(|| { }); #[must_use] +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn print_dots() -> String { format!( "{} {} {} {} {} {} {}", diff --git a/src/desktop.rs b/src/desktop.rs index e5e6039..7d3733a 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,4 +1,5 @@ #[must_use] +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_desktop_info() -> String { // Retrieve the environment variables and handle Result types let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); diff --git a/src/main.rs b/src/main.rs index 2138a44..5ba7810 100644 --- a/src/main.rs +++ b/src/main.rs @@ -19,6 +19,7 @@ use crate::{ uptime::get_current, }; +#[cfg_attr(feature = "hotpath", hotpath::main)] fn main() -> Result<(), Box> { if Some("--version") == std::env::args().nth(1).as_deref() { println!("Microfetch {}", env!("CARGO_PKG_VERSION")); @@ -56,6 +57,7 @@ struct Fields { colors: String, } +#[cfg_attr(feature = "hotpath", hotpath::measure)] fn print_system_info( fields: &Fields, ) -> Result<(), Box> { diff --git a/src/release.rs b/src/release.rs index 20a9b81..2f1338c 100644 --- a/src/release.rs +++ b/src/release.rs @@ -6,6 +6,7 @@ use std::{ use nix::sys::utsname::UtsName; #[must_use] +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_system_info(utsname: &UtsName) -> String { format!( "{} {} ({})", @@ -15,6 +16,7 @@ pub fn get_system_info(utsname: &UtsName) -> String { ) } +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_os_pretty_name() -> Result { let file = File::open("/etc/os-release")?; let reader = BufReader::new(file); diff --git a/src/system.rs b/src/system.rs index 1cfd12c..f90c912 100644 --- a/src/system.rs +++ b/src/system.rs @@ -9,6 +9,7 @@ use nix::sys::{statvfs::statvfs, utsname::UtsName}; use crate::colors::COLORS; #[must_use] +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_username_and_hostname(utsname: &UtsName) -> String { let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned()); let hostname = utsname @@ -26,6 +27,7 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { } #[must_use] +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_shell() -> String { let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned()); @@ -33,6 +35,7 @@ pub fn get_shell() -> String { shell_name.to_owned() } +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_root_disk_usage() -> Result { let vfs = statvfs("/")?; let block_size = vfs.block_size() as u64; @@ -53,7 +56,9 @@ pub fn get_root_disk_usage() -> Result { )) } +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_memory_usage() -> Result { + #[cfg_attr(feature = "hotpath", hotpath::measure)] fn parse_memory_info() -> Result<(f64, f64), io::Error> { let mut total_memory_kb = 0.0; let mut available_memory_kb = 0.0; diff --git a/src/uptime.rs b/src/uptime.rs index 87b95cf..d5253d6 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,5 +1,6 @@ use std::{io, mem::MaybeUninit}; +#[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_current() -> Result { let uptime_seconds = { let mut info = MaybeUninit::uninit(); From 325ec690249438ead86fb2a01253b4761527ccae Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 17:40:59 +0300 Subject: [PATCH 48/82] chore: tag 0.4.10 Signed-off-by: NotAShelf Change-Id: I6a2158a305d5f249b52c8b21dc5aaca86a6a6964 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 40f74fc..c7bf6b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -772,7 +772,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.9" +version = "0.4.10" dependencies = [ "criterion", "hotpath", diff --git a/Cargo.toml b/Cargo.toml index 6af4917..c97b58b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.9" +version = "0.4.10" edition = "2024" [lib] From 2ad765ef988051fd7fb6272c595baee1ed87a00c Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 17:55:10 +0300 Subject: [PATCH 49/82] various: reduce allocations where available Signed-off-by: NotAShelf Change-Id: I517d855b14c015569a325deb64948f3b6a6a6964 --- src/colors.rs | 45 +++++++++++++++++++++++++++--------- src/desktop.rs | 47 ++++++++++++++++++++++---------------- src/release.rs | 28 ++++++++++++++--------- src/system.rs | 62 ++++++++++++++++++++++++++++++++++---------------- src/uptime.rs | 8 +++---- 5 files changed, 125 insertions(+), 65 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index 53b12f6..07ab9bd 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -37,7 +37,7 @@ impl Colors { } pub static COLORS: LazyLock = LazyLock::new(|| { - // check for NO_COLOR once at startup + // Check for NO_COLOR once at startup let is_no_color = env::var("NO_COLOR").is_ok(); Colors::new(is_no_color) }); @@ -45,14 +45,37 @@ pub static COLORS: LazyLock = LazyLock::new(|| { #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn print_dots() -> String { - format!( - "{} {} {} {} {} {} {}", - COLORS.blue, - COLORS.cyan, - COLORS.green, - COLORS.yellow, - COLORS.red, - COLORS.magenta, - COLORS.reset, - ) + // Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color + const GLYPH: &str = ""; + let capacity = COLORS.blue.len() + + COLORS.cyan.len() + + COLORS.green.len() + + COLORS.yellow.len() + + COLORS.red.len() + + COLORS.magenta.len() + + COLORS.reset.len() + + (GLYPH.len() + 2) * 6; + + let mut result = String::with_capacity(capacity); + result.push_str(COLORS.blue); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.cyan); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.green); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.yellow); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.red); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.magenta); + result.push_str(GLYPH); + result.push_str(" "); + result.push_str(COLORS.reset); + + result } diff --git a/src/desktop.rs b/src/desktop.rs index 7d3733a..561be03 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,30 +1,37 @@ +use std::fmt::Write; + #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_desktop_info() -> String { // Retrieve the environment variables and handle Result types let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); - let display_backend_result = std::env::var("XDG_SESSION_TYPE"); + let display_backend = std::env::var("XDG_SESSION_TYPE"); - // Capitalize the first letter of the display backend value - let mut display_backend = display_backend_result.unwrap_or_default(); - if let Some(c) = display_backend.as_mut_str().get_mut(0..1) { - c.make_ascii_uppercase(); - } - - // Trim "none+" from the start of desktop_env if present - // Use "Unknown" if desktop_env is empty or has an error - let desktop_env = match desktop_env { - Err(_) => "Unknown".to_owned(), - Ok(s) => s.trim_start_matches("none+").to_owned(), + let desktop_str = match desktop_env { + Err(_) => "Unknown", + Ok(ref s) if s.starts_with("none+") => &s[5..], + Ok(ref s) => s.as_str(), }; - // Handle the case where display_backend might be empty after capitalization - let display_backend = if display_backend.is_empty() { - "Unknown" - } else { - &display_backend - } - .to_owned(); + let backend_str = match display_backend { + Err(_) => "Unknown", + Ok(ref s) if s.is_empty() => "Unknown", + Ok(ref s) => s.as_str(), + }; - format!("{desktop_env} ({display_backend})") + // Pre-calculate capacity: desktop_len + " (" + backend_len + ")" + // Capitalize first char needs temporary allocation only if backend exists + let mut result = + String::with_capacity(desktop_str.len() + backend_str.len() + 3); + result.push_str(desktop_str); + result.push_str(" ("); + + // Capitalize first character of backend + if let Some(first_char) = backend_str.chars().next() { + let _ = write!(result, "{}", first_char.to_ascii_uppercase()); + result.push_str(&backend_str[first_char.len_utf8()..]); + } + + result.push(')'); + result } diff --git a/src/release.rs b/src/release.rs index 2f1338c..d9ec4c9 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,6 +1,7 @@ use std::{ + fmt::Write as _, fs::File, - io::{self, BufRead, BufReader}, + io::{self, Read}, }; use nix::sys::utsname::UtsName; @@ -8,21 +9,26 @@ use nix::sys::utsname::UtsName; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_system_info(utsname: &UtsName) -> String { - format!( - "{} {} ({})", - utsname.sysname().to_str().unwrap_or("Unknown"), - utsname.release().to_str().unwrap_or("Unknown"), - utsname.machine().to_str().unwrap_or("Unknown") - ) + let sysname = utsname.sysname().to_str().unwrap_or("Unknown"); + let release = utsname.release().to_str().unwrap_or("Unknown"); + let machine = utsname.machine().to_str().unwrap_or("Unknown"); + + // Pre-allocate capacity: sysname + " " + release + " (" + machine + ")" + let capacity = sysname.len() + 1 + release.len() + 2 + machine.len() + 1; + let mut result = String::with_capacity(capacity); + + write!(result, "{sysname} {release} ({machine})").unwrap(); + result } #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_os_pretty_name() -> Result { - let file = File::open("/etc/os-release")?; - let reader = BufReader::new(file); + // We use a stack-allocated buffer here, which seems to perform MUCH better + // than `BufReader`. In hindsight, I should've seen this coming. + let mut buffer = String::with_capacity(1024); + File::open("/etc/os-release")?.read_to_string(&mut buffer)?; - for line in reader.lines() { - let line = line?; + for line in buffer.lines() { if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { if let Some(trimmed) = pretty_name .strip_prefix('"') diff --git a/src/system.rs b/src/system.rs index f90c912..21b2b2f 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,5 +1,6 @@ use std::{ env, + fmt::Write as _, fs::File, io::{self, Read}, }; @@ -12,18 +13,26 @@ use crate::colors::COLORS; #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_username_and_hostname(utsname: &UtsName) -> String { let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned()); - let hostname = utsname - .nodename() - .to_str() - .unwrap_or("unknown_host") - .to_owned(); - format!( - "{yellow}{username}{red}@{green}{hostname}{reset}", - yellow = COLORS.yellow, - red = COLORS.red, - green = COLORS.green, - reset = COLORS.reset, - ) + let hostname = utsname.nodename().to_str().unwrap_or("unknown_host"); + + let capacity = COLORS.yellow.len() + + username.len() + + COLORS.red.len() + + 1 + + COLORS.green.len() + + hostname.len() + + COLORS.reset.len(); + let mut result = String::with_capacity(capacity); + + result.push_str(COLORS.yellow); + result.push_str(&username); + result.push_str(COLORS.red); + result.push('@'); + result.push_str(COLORS.green); + result.push_str(hostname); + result.push_str(COLORS.reset); + + result } #[must_use] @@ -31,8 +40,13 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { pub fn get_shell() -> String { let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned()); - let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); - shell_name.to_owned() + + // Find last '/' and get the part after it, avoiding allocation + shell_path + .rsplit('/') + .next() + .unwrap_or("unknown_shell") + .to_owned() } #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -49,11 +63,16 @@ pub fn get_root_disk_usage() -> Result { let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0); let usage = (used_size / total_size) * 100.0; - Ok(format!( + let mut result = String::with_capacity(64); + write!( + result, "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})", cyan = COLORS.cyan, reset = COLORS.reset, - )) + ) + .unwrap(); + + Ok(result) } #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -70,7 +89,7 @@ pub fn get_memory_usage() -> Result { let mut split = line.split_whitespace(); match split.next().unwrap_or_default() { "MemTotal:" => { - total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0) + total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); }, "MemAvailable:" => { available_memory_kb = @@ -92,10 +111,15 @@ pub fn get_memory_usage() -> Result { let (used_memory, total_memory) = parse_memory_info()?; let percentage_used = (used_memory / total_memory * 100.0).round() as u64; - Ok(format!( + let mut result = String::with_capacity(64); + write!( + result, "{used_memory:.2} GiB / {total_memory:.2} GiB \ ({cyan}{percentage_used}%{reset})", cyan = COLORS.cyan, reset = COLORS.reset, - )) + ) + .unwrap(); + + Ok(result) } diff --git a/src/uptime.rs b/src/uptime.rs index d5253d6..bd2cc39 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,4 +1,4 @@ -use std::{io, mem::MaybeUninit}; +use std::{fmt::Write, io, mem::MaybeUninit}; #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_current() -> Result { @@ -16,21 +16,21 @@ pub fn get_current() -> Result { let mut result = String::with_capacity(32); if days > 0 { - result.push_str(&days.to_string()); + let _ = write!(result, "{days}"); result.push_str(if days == 1 { " day" } else { " days" }); } if hours > 0 { if !result.is_empty() { result.push_str(", "); } - result.push_str(&hours.to_string()); + let _ = write!(result, "{hours}"); result.push_str(if hours == 1 { " hour" } else { " hours" }); } if minutes > 0 { if !result.is_empty() { result.push_str(", "); } - result.push_str(&minutes.to_string()); + let _ = write!(result, "{minutes}"); result.push_str(if minutes == 1 { " minute" } else { " minutes" }); } if result.is_empty() { From f4f3385ff760f6570ae7af93774748e977da30e4 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 18:17:59 +0300 Subject: [PATCH 50/82] various: fix clippy warnings - Adds proper documentation comments with `# Errors` sections for all functions returning `Result` - `cast_precision_loss` on `u64` -> `f64` for disk sizes is acceptable since disk sizes won't exceed f64's precision limit in practice. Thus, we can suppress those. - `cast_sign_loss` and `cast_possible_truncation` on the percentage calculation is safe since percentages are always 0-100. Once again, it's safe to suppress. Signed-off-by: NotAShelf Change-Id: Id4dd7ebc9674407d2be4f38ff4de24bc6a6a6964 --- src/release.rs | 5 +++++ src/system.rs | 12 ++++++++++++ src/uptime.rs | 6 ++++++ 3 files changed, 23 insertions(+) diff --git a/src/release.rs b/src/release.rs index d9ec4c9..1b820b8 100644 --- a/src/release.rs +++ b/src/release.rs @@ -21,6 +21,11 @@ pub fn get_system_info(utsname: &UtsName) -> String { result } +/// Gets the pretty name of the OS from `/etc/os-release`. +/// +/// # Errors +/// +/// Returns an error if `/etc/os-release` cannot be read. #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_os_pretty_name() -> Result { // We use a stack-allocated buffer here, which seems to perform MUCH better diff --git a/src/system.rs b/src/system.rs index 21b2b2f..ba90b27 100644 --- a/src/system.rs +++ b/src/system.rs @@ -49,7 +49,13 @@ pub fn get_shell() -> String { .to_owned() } +/// Gets the root disk usage information. +/// +/// # Errors +/// +/// Returns an error if the filesystem information cannot be retrieved. #[cfg_attr(feature = "hotpath", hotpath::measure)] +#[allow(clippy::cast_precision_loss)] pub fn get_root_disk_usage() -> Result { let vfs = statvfs("/")?; let block_size = vfs.block_size() as u64; @@ -75,6 +81,11 @@ pub fn get_root_disk_usage() -> Result { Ok(result) } +/// Gets the system memory usage information. +/// +/// # Errors +/// +/// Returns an error if `/proc/meminfo` cannot be read. #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_memory_usage() -> Result { #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -109,6 +120,7 @@ pub fn get_memory_usage() -> Result { } let (used_memory, total_memory) = parse_memory_info()?; + #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)] let percentage_used = (used_memory / total_memory * 100.0).round() as u64; let mut result = String::with_capacity(64); diff --git a/src/uptime.rs b/src/uptime.rs index bd2cc39..72af399 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,5 +1,10 @@ use std::{fmt::Write, io, mem::MaybeUninit}; +/// Gets the current system uptime. +/// +/// # Errors +/// +/// Returns an error if the system uptime cannot be retrieved. #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_current() -> Result { let uptime_seconds = { @@ -7,6 +12,7 @@ pub fn get_current() -> Result { if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 { return Err(io::Error::last_os_error()); } + #[allow(clippy::cast_sign_loss)] unsafe { info.assume_init().uptime as u64 } }; From 789ece866b0480f57a365f6c1509a51c8b90b005 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 18:30:06 +0300 Subject: [PATCH 51/82] ci: initial benchmarking workflows Signed-off-by: NotAShelf Change-Id: I367444097eafbd1020c02707c42351bf6a6a6964 --- .github/workflows/hotpath-comment.yml | 57 ++++++++++++++++++++++++ .github/workflows/hotpath-profile.yml | 63 +++++++++++++++++++++++++++ 2 files changed, 120 insertions(+) create mode 100644 .github/workflows/hotpath-comment.yml create mode 100644 .github/workflows/hotpath-profile.yml diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml new file mode 100644 index 0000000..395a533 --- /dev/null +++ b/.github/workflows/hotpath-comment.yml @@ -0,0 +1,57 @@ +name: Hotpath Comment + +on: + workflow_run: + workflows: ["Hotpath Profile"] + types: + - completed + +permissions: + pull-requests: write + +jobs: + comment: + runs-on: ubuntu-latest + if: ${{ github.event.workflow_run.conclusion == 'success' }} + + steps: + - name: Download profiling results + uses: actions/download-artifact@v4 + with: + name: hotpath-results + github-token: ${{ secrets.GITHUB_TOKEN }} + run-id: ${{ github.event.workflow_run.id }} + + - name: Read PR number + id: pr + run: echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Install hotpath CLI + run: cargo install hotpath + + - name: Post timing comparison comment + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + hotpath profile-pr \ + --repo ${{ github.repository }} \ + --pr-number ${{ steps.pr.outputs.number }} \ + --head-json head-timing.json \ + --base-json base-timing.json \ + --mode timing \ + --title "⏱️ Hotpath Timing Profile" + + - name: Post allocation comparison comment + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + hotpath profile-pr \ + --repo ${{ github.repository }} \ + --pr-number ${{ steps.pr.outputs.number }} \ + --head-json head-alloc.json \ + --base-json base-alloc.json \ + --mode alloc \ + --title "📊 Hotpath Allocation Profile" diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml new file mode 100644 index 0000000..b367ca2 --- /dev/null +++ b/.github/workflows/hotpath-profile.yml @@ -0,0 +1,63 @@ +name: Hotpath Profile + +on: + pull_request: + branches: [ "main" ] + +env: + CARGO_TERM_COLOR: always + +jobs: + profile: + runs-on: ubuntu-latest + + steps: + - name: Checkout PR HEAD + uses: actions/checkout@v4 + + - name: Setup Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + + - name: Run timing profiling on HEAD + env: + HOTPATH_JSON: "true" + run: | + cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > head-timing.json + + - name: Run allocation profiling on HEAD + env: + HOTPATH_JSON: "true" + run: | + cargo run --features='hotpath,hotpath-alloc-count-total' 2>&1 | grep '^{"hotpath_profiling_mode"' > head-alloc.json + + - name: Checkout base branch + uses: actions/checkout@v4 + with: + ref: ${{ github.event.pull_request.base.sha }} + + - name: Run timing profiling on base + env: + HOTPATH_JSON: "true" + run: | + cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > base-timing.json + + - name: Run allocation profiling on base + env: + HOTPATH_JSON: "true" + run: | + cargo run --features='hotpath,hotpath-alloc-count-total' 2>&1 | grep '^{"hotpath_profiling_mode"' > base-alloc.json + + - name: Save PR number + run: echo "${{ github.event.number }}" > pr_number.txt + + - name: Upload profiling results + uses: actions/upload-artifact@v4 + with: + name: hotpath-results + path: | + head-timing.json + head-alloc.json + base-timing.json + base-alloc.json + pr_number.txt + retention-days: 1 From 00159d64545b6afab624c1905b66421f32a0eca1 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 19:17:44 +0300 Subject: [PATCH 52/82] docs: README makeover; update benchmarks Signed-off-by: NotAShelf Change-Id: Ic1ca95ab7e0b2ff78ed7967c604739546a6a6964 --- README.md | 141 ++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 105 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 28d6338..5ae0f72 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,34 @@ + +

https://deps.rs/repo/github/notashelf/microfetch - - + stars
-

Microfetch

+
+

+ Microfetch +

+

+ Microscopic fetch tool in Rust, for NixOS systems, with special emphasis on speed +

+
+ Synopsis
+ Features | Motivation
+ Installation +
+
-Stupidly simple, laughably fast fetch tool. Written in Rust for speed and ease -of maintainability. Runs in a _fraction of a millisecond_ and displays _most_ of -the nonsense you'd see posted on r/unixporn or other internet communities. Aims -to replace [fastfetch](https://github.com/fastfetch-cli/fastfetch) on my -personal system, but [probably not yours](#customizing). Though, you are more -than welcome to use it on your system: it's pretty [fast](#benchmarks)... +## Synopsis + +[fastfetch]: https://github.com/fastfetch-cli/fastfetch + +Stupidly small and simple, laughably fast and pretty fetch tool. Written in Rust +for speed and ease of maintainability. Runs in a _fraction of a millisecond_ and +displays _most_ of the nonsense you'd see posted on r/unixporn or other internet +communities. Aims to replace [fastfetch] on my personal system, but +[probably not yours](#customizing). Though, you are more than welcome to use it +on your system: it is pretty _[fast](#benchmarks)_...

[!NOTE] From ca76a6e1bd532f6c9424f0530ad194c32f37c744 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 19:17:53 +0300 Subject: [PATCH 53/82] chore: format with rustfmt nightly Signed-off-by: NotAShelf Change-Id: I9c9e4e010b09d37f0e0994a5407f3ce56a6a6964 --- src/uptime.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/uptime.rs b/src/uptime.rs index 72af399..52188c2 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -13,7 +13,9 @@ pub fn get_current() -> Result { return Err(io::Error::last_os_error()); } #[allow(clippy::cast_sign_loss)] - unsafe { info.assume_init().uptime as u64 } + unsafe { + info.assume_init().uptime as u64 + } }; let days = uptime_seconds / 86400; From b24e720dd81d52ef4e71ede470d7b611d5474a6d Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 19:18:35 +0300 Subject: [PATCH 54/82] chore: tag 0.4.11 Signed-off-by: NotAShelf Change-Id: I42b59d68cdac17ff60d52a4c25bef4686a6a6964 --- Cargo.lock | 8 ++++---- Cargo.toml | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c7bf6b7..c9ed3f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -690,7 +690,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" dependencies = [ "hermit-abi", "libc", - "windows-sys 0.59.0", + "windows-sys 0.61.2", ] [[package]] @@ -732,9 +732,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.175" +version = "0.2.177" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543" +checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" [[package]] name = "libredox" @@ -772,7 +772,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.10" +version = "0.4.11" dependencies = [ "criterion", "hotpath", diff --git a/Cargo.toml b/Cargo.toml index c97b58b..7e83f9f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.10" +version = "0.4.11" edition = "2024" [lib] @@ -12,8 +12,8 @@ name = "microfetch" path = "src/main.rs" [dependencies] -hotpath = { optional = true, version = "0.6" } -libc = "0.2.175" +hotpath = { optional = true, version = "0.6.0" } +libc = "0.2.177" nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" } [dev-dependencies] From f8a0070986dda8c7554dcf769fda99cd129e039f Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 19:19:28 +0300 Subject: [PATCH 55/82] uptime: optimize uptime calculation w/ inline assembly and custom itoa lol, lmao even. Signed-off-by: NotAShelf Change-Id: Ie22fbd2e9c2be5740b493bdc81caafb36a6a6964 --- src/uptime.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 69 insertions(+), 5 deletions(-) diff --git a/src/uptime.rs b/src/uptime.rs index 52188c2..98c9207 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,4 +1,66 @@ -use std::{fmt::Write, io, mem::MaybeUninit}; +use std::{io, mem::MaybeUninit}; + +/// Fast integer to string conversion (no formatting overhead) +#[inline] +fn itoa(mut n: u64, buf: &mut [u8]) -> &str { + if n == 0 { + return "0"; + } + + let mut i = buf.len(); + while n > 0 { + i -= 1; + buf[i] = b'0' + (n % 10) as u8; + n /= 10; + } + + unsafe { std::str::from_utf8_unchecked(&buf[i..]) } +} + +/// Direct sysinfo syscall using inline assembly +/// +/// # Safety +/// This function uses inline assembly to make a direct syscall. +/// The caller must ensure the sysinfo pointer is valid. +#[inline] +unsafe fn sys_sysinfo(info: *mut libc::sysinfo) -> i64 { + #[cfg(target_arch = "x86_64")] + { + let ret: i64; + unsafe { + std::arch::asm!( + "syscall", + in("rax") 99_i64, // __NR_sysinfo + in("rdi") info, + out("rcx") _, + out("r11") _, + lateout("rax") ret, + options(nostack) + ); + } + ret + } + + #[cfg(target_arch = "aarch64")] + { + let ret: i64; + unsafe { + std::arch::asm!( + "svc #0", + in("x8") 179_i64, // __NR_sysinfo + in("x0") info, + lateout("x0") ret, + options(nostack) + ); + } + ret + } + + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + unsafe { libc::sysinfo(info) as i64 } + } +} /// Gets the current system uptime. /// @@ -9,7 +71,7 @@ use std::{fmt::Write, io, mem::MaybeUninit}; pub fn get_current() -> Result { let uptime_seconds = { let mut info = MaybeUninit::uninit(); - if unsafe { libc::sysinfo(info.as_mut_ptr()) } != 0 { + if unsafe { sys_sysinfo(info.as_mut_ptr()) } != 0 { return Err(io::Error::last_os_error()); } #[allow(clippy::cast_sign_loss)] @@ -23,22 +85,24 @@ pub fn get_current() -> Result { let minutes = (uptime_seconds / 60) % 60; let mut result = String::with_capacity(32); + let mut buf = [0u8; 20]; // Enough for u64::MAX + if days > 0 { - let _ = write!(result, "{days}"); + result.push_str(itoa(days, &mut buf)); result.push_str(if days == 1 { " day" } else { " days" }); } if hours > 0 { if !result.is_empty() { result.push_str(", "); } - let _ = write!(result, "{hours}"); + result.push_str(itoa(hours, &mut buf)); result.push_str(if hours == 1 { " hour" } else { " hours" }); } if minutes > 0 { if !result.is_empty() { result.push_str(", "); } - let _ = write!(result, "{minutes}"); + result.push_str(itoa(minutes, &mut buf)); result.push_str(if minutes == 1 { " minute" } else { " minutes" }); } if result.is_empty() { From 75132ff17239b2778ee1e36308a6b011579400e6 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 21:27:28 +0300 Subject: [PATCH 56/82] treewide: remove nix dependency, add custom syscall wrappers & `UtsName` Signed-off-by: NotAShelf Change-Id: Ib880f4bafe9d3bbc944af4b9125256366a6a6964 --- Cargo.lock | 19 ---- Cargo.toml | 1 - benches/benchmark.rs | 3 +- src/lib.rs | 40 +++++++++ src/main.rs | 5 +- src/release.rs | 57 ++++++++---- src/syscall.rs | 203 +++++++++++++++++++++++++++++++++++++++++++ src/system.rs | 102 +++++++++++++++------- 8 files changed, 358 insertions(+), 72 deletions(-) create mode 100644 src/syscall.rs diff --git a/Cargo.lock b/Cargo.lock index c9ed3f9..0147746 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -143,12 +143,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "cfg_aliases" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" - [[package]] name = "chunked_transfer" version = "1.5.0" @@ -777,7 +771,6 @@ dependencies = [ "criterion", "hotpath", "libc", - "nix", ] [[package]] @@ -790,18 +783,6 @@ dependencies = [ "simd-adler32", ] -[[package]] -name = "nix" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74523f3a35e05aba87a1d978330aef40f67b0304ac79c1c00b294c9830543db6" -dependencies = [ - "bitflags", - "cfg-if", - "cfg_aliases", - "libc", -] - [[package]] name = "num-conv" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 7e83f9f..e95ef5c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,6 @@ path = "src/main.rs" [dependencies] hotpath = { optional = true, version = "0.6.0" } libc = "0.2.177" -nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" } [dev-dependencies] criterion = "0.7" diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 33dc1b9..8d62082 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -1,5 +1,6 @@ use criterion::{Criterion, criterion_group, criterion_main}; use microfetch_lib::{ + UtsName, colors::print_dots, desktop::get_desktop_info, release::{get_os_pretty_name, get_system_info}, @@ -13,7 +14,7 @@ use microfetch_lib::{ }; fn main_benchmark(c: &mut Criterion) { - let utsname = nix::sys::utsname::uname().expect("lol"); + let utsname = UtsName::uname().expect("Failed to get uname"); c.bench_function("user_info", |b| { b.iter(|| get_username_and_hostname(&utsname)); }); diff --git a/src/lib.rs b/src/lib.rs index 4ba33f8..8c8a6ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,45 @@ pub mod colors; pub mod desktop; pub mod release; +pub mod syscall; pub mod system; pub mod uptime; + +use std::mem::MaybeUninit; + +/// Wrapper for `libc::utsname` with safe accessor methods +pub struct UtsName(libc::utsname); + +impl UtsName { + /// Calls `uname` syscall and returns a `UtsName` wrapper + /// + /// # Errors + /// Returns an error if the uname syscall fails + pub fn uname() -> Result { + let mut uts = MaybeUninit::uninit(); + if unsafe { libc::uname(uts.as_mut_ptr()) } != 0 { + return Err(std::io::Error::last_os_error()); + } + Ok(Self(unsafe { uts.assume_init() })) + } + + #[must_use] + pub const fn nodename(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(self.0.nodename.as_ptr()) } + } + + #[must_use] + pub const fn sysname(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(self.0.sysname.as_ptr()) } + } + + #[must_use] + pub const fn release(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(self.0.release.as_ptr()) } + } + + #[must_use] + pub const fn machine(&self) -> &std::ffi::CStr { + unsafe { std::ffi::CStr::from_ptr(self.0.machine.as_ptr()) } + } +} diff --git a/src/main.rs b/src/main.rs index 5ba7810..4e664bb 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,11 +1,14 @@ mod colors; mod desktop; mod release; +mod syscall; mod system; mod uptime; use std::io::{Write, stdout}; +pub use microfetch_lib::UtsName; + use crate::{ colors::print_dots, desktop::get_desktop_info, @@ -24,7 +27,7 @@ fn main() -> Result<(), Box> { if Some("--version") == std::env::args().nth(1).as_deref() { println!("Microfetch {}", env!("CARGO_PKG_VERSION")); } else { - let utsname = nix::sys::utsname::uname()?; + let utsname = UtsName::uname()?; let fields = Fields { user_info: get_username_and_hostname(&utsname), os_name: get_os_pretty_name()?, diff --git a/src/release.rs b/src/release.rs index 1b820b8..8d3232e 100644 --- a/src/release.rs +++ b/src/release.rs @@ -1,10 +1,6 @@ -use std::{ - fmt::Write as _, - fs::File, - io::{self, Read}, -}; +use std::{fmt::Write as _, io}; -use nix::sys::utsname::UtsName; +use crate::{UtsName, syscall::read_file_fast}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -28,21 +24,46 @@ pub fn get_system_info(utsname: &UtsName) -> String { /// Returns an error if `/etc/os-release` cannot be read. #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_os_pretty_name() -> Result { - // We use a stack-allocated buffer here, which seems to perform MUCH better - // than `BufReader`. In hindsight, I should've seen this coming. - let mut buffer = String::with_capacity(1024); - File::open("/etc/os-release")?.read_to_string(&mut buffer)?; + // Fast byte-level scanning for PRETTY_NAME= + const PREFIX: &[u8] = b"PRETTY_NAME="; - for line in buffer.lines() { - if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { - if let Some(trimmed) = pretty_name - .strip_prefix('"') - .and_then(|s| s.strip_suffix('"')) + let mut buffer = [0u8; 1024]; + + // Use fast syscall-based file reading + let bytes_read = read_file_fast("/etc/os-release", &mut buffer)?; + let content = &buffer[..bytes_read]; + + let mut offset = 0; + + while offset < content.len() { + let remaining = &content[offset..]; + + // Find newline or end + let line_end = remaining + .iter() + .position(|&b| b == b'\n') + .unwrap_or(remaining.len()); + let line = &remaining[..line_end]; + + if line.starts_with(PREFIX) { + let value = &line[PREFIX.len()..]; + + // Strip quotes if present + let trimmed = if value.len() >= 2 + && value[0] == b'"' + && value[value.len() - 1] == b'"' { - return Ok(trimmed.to_owned()); - } - return Ok(pretty_name.to_owned()); + &value[1..value.len() - 1] + } else { + value + }; + + // Convert to String - should be valid UTF-8 + return Ok(String::from_utf8_lossy(trimmed).into_owned()); } + + offset += line_end + 1; } + Ok("Unknown".to_owned()) } diff --git a/src/syscall.rs b/src/syscall.rs new file mode 100644 index 0000000..2776fe7 --- /dev/null +++ b/src/syscall.rs @@ -0,0 +1,203 @@ +//! Incredibly fast syscall wrappers for using inline assembly. Serves the +//! purposes of completely bypassing Rust's standard library in favor of +//! handwritten Assembly. Is this a good idea? No. Is it fast? Yeah, but only +//! marginally. Either way it serves a purpose and I will NOT accept criticism. +//! What do you mean I wasted two whole hours to make the program only 100µs +//! faster? +//! +//! Supports `x86_64` and `aarch64` architectures. Riscv support will be +//! implemented when and ONLY WHEN I can be bothered to work on it. + +use std::io; + +/// Direct syscall to open a file +/// Returns file descriptor or -1 on error +/// +/// # Safety +/// +/// The caller must ensure: +/// - `path` points to a valid null-terminated C string +/// - The pointer remains valid for the duration of the syscall +#[inline] +#[must_use] +pub unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { + #[cfg(target_arch = "x86_64")] + unsafe { + let fd: i64; + std::arch::asm!( + "syscall", + in("rax") 2i64, // SYS_open + in("rdi") path, + in("rsi") flags, + in("rdx") 0i32, // mode (not used for reading) + lateout("rax") fd, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } + } + #[cfg(target_arch = "aarch64")] + unsafe { + let fd: i64; + std::arch::asm!( + "svc #0", + in("x8") 56i64, // SYS_openat + in("x0") -100i32, // AT_FDCWD + in("x1") path, + in("x2") flags, + in("x3") 0i32, // mode + lateout("x0") fd, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + fd as i32 + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + compile_error!("Unsupported architecture for inline assembly syscalls"); + } +} + +/// Direct syscall to read from a file descriptor +/// Returns number of bytes read or -1 on error +/// +/// # Safety +/// +/// The caller must ensure: +/// - `buf` points to a valid writable buffer of at least `count` bytes +/// - `fd` is a valid open file descriptor +#[inline] +pub unsafe fn sys_read(fd: i32, buf: *mut u8, count: usize) -> isize { + #[cfg(target_arch = "x86_64")] + unsafe { + let ret: i64; + std::arch::asm!( + "syscall", + in("rax") 0i64, // SYS_read + in("rdi") fd, + in("rsi") buf, + in("rdx") count, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } + #[cfg(target_arch = "aarch64")] + unsafe { + let ret: i64; + std::arch::asm!( + "svc #0", + in("x8") 63i64, // SYS_read + in("x0") fd, + in("x1") buf, + in("x2") count, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as isize + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + compile_error!("Unsupported architecture for inline assembly syscalls"); + } +} + +/// Direct syscall to close a file descriptor +/// +/// # Safety +/// +/// The caller must ensure `fd` is a valid open file descriptor +#[inline] +#[must_use] +pub unsafe fn sys_close(fd: i32) -> i32 { + #[cfg(target_arch = "x86_64")] + unsafe { + let ret: i64; + std::arch::asm!( + "syscall", + in("rax") 3i64, // SYS_close + in("rdi") fd, + lateout("rax") ret, + lateout("rcx") _, + lateout("r11") _, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } + #[cfg(target_arch = "aarch64")] + unsafe { + let ret: i64; + std::arch::asm!( + "svc #0", + in("x8") 57i64, // SYS_close + in("x0") fd, + lateout("x0") ret, + options(nostack) + ); + #[allow(clippy::cast_possible_truncation)] + { + ret as i32 + } + } + #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] + { + compile_error!("Unsupported architecture for inline assembly syscalls"); + } +} + +/// Read entire file using direct syscalls. This avoids libc overhead and can be +/// significantly faster for small files. +/// +/// # Errors +/// +/// Returns an error if the file cannot be opened or read +#[inline] +pub fn read_file_fast(path: &str, buffer: &mut [u8]) -> io::Result { + const O_RDONLY: i32 = 0; + + // Use stack-allocated buffer for null-terminated path (max 256 bytes) + let path_bytes = path.as_bytes(); + if path_bytes.len() >= 256 { + return Err(io::Error::new(io::ErrorKind::InvalidInput, "Path too long")); + } + + let mut path_buf = [0u8; 256]; + path_buf[..path_bytes.len()].copy_from_slice(path_bytes); + // XXX: Already zero-terminated since array is initialized to zeros + + unsafe { + let fd = sys_open(path_buf.as_ptr(), O_RDONLY); + if fd < 0 { + return Err(io::Error::last_os_error()); + } + + let bytes_read = sys_read(fd, buffer.as_mut_ptr(), buffer.len()); + let _ = sys_close(fd); + + if bytes_read < 0 { + return Err(io::Error::last_os_error()); + } + + #[allow(clippy::cast_sign_loss)] + { + Ok(bytes_read as usize) + } + } +} diff --git a/src/system.rs b/src/system.rs index ba90b27..24406ec 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,13 +1,6 @@ -use std::{ - env, - fmt::Write as _, - fs::File, - io::{self, Read}, -}; +use std::{env, fmt::Write as _, io, mem::MaybeUninit}; -use nix::sys::{statvfs::statvfs, utsname::UtsName}; - -use crate::colors::COLORS; +use crate::{UtsName, colors::COLORS, syscall::read_file_fast}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] @@ -57,10 +50,17 @@ pub fn get_shell() -> String { #[cfg_attr(feature = "hotpath", hotpath::measure)] #[allow(clippy::cast_precision_loss)] pub fn get_root_disk_usage() -> Result { - let vfs = statvfs("/")?; - let block_size = vfs.block_size() as u64; - let total_blocks = vfs.blocks(); - let available_blocks = vfs.blocks_available(); + let mut vfs = MaybeUninit::uninit(); + let path = b"/\0"; + + if unsafe { libc::statvfs(path.as_ptr().cast(), vfs.as_mut_ptr()) } != 0 { + return Err(io::Error::last_os_error()); + } + + let vfs = unsafe { vfs.assume_init() }; + let block_size = vfs.f_bsize; + let total_blocks = vfs.f_blocks; + let available_blocks = vfs.f_bavail; let total_size = block_size * total_blocks; let used_size = total_size - (block_size * available_blocks); @@ -81,6 +81,20 @@ pub fn get_root_disk_usage() -> Result { Ok(result) } +/// Fast integer parsing without stdlib overhead +#[inline] +fn parse_u64_fast(s: &[u8]) -> u64 { + let mut result = 0u64; + for &byte in s { + if byte.is_ascii_digit() { + result = result * 10 + u64::from(byte - b'0'); + } else { + break; + } + } + result +} + /// Gets the system memory usage information. /// /// # Errors @@ -90,30 +104,54 @@ pub fn get_root_disk_usage() -> Result { pub fn get_memory_usage() -> Result { #[cfg_attr(feature = "hotpath", hotpath::measure)] fn parse_memory_info() -> Result<(f64, f64), io::Error> { - let mut total_memory_kb = 0.0; - let mut available_memory_kb = 0.0; - let mut meminfo = String::with_capacity(2048); + let mut total_memory_kb = 0u64; + let mut available_memory_kb = 0u64; + let mut buffer = [0u8; 2048]; - File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?; + // Use fast syscall-based file reading + let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)?; + let meminfo = &buffer[..bytes_read]; - for line in meminfo.lines() { - let mut split = line.split_whitespace(); - match split.next().unwrap_or_default() { - "MemTotal:" => { - total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); - }, - "MemAvailable:" => { - available_memory_kb = - split.next().unwrap_or("0").parse().unwrap_or(0.0); - // MemTotal comes before MemAvailable, stop parsing - break; - }, - _ => (), + // Fast scanning for MemTotal and MemAvailable + let mut offset = 0; + let mut found_total = false; + let mut found_available = false; + + while offset < meminfo.len() && (!found_total || !found_available) { + let remaining = &meminfo[offset..]; + + // Find newline or end + let line_end = remaining + .iter() + .position(|&b| b == b'\n') + .unwrap_or(remaining.len()); + let line = &remaining[..line_end]; + + if line.starts_with(b"MemTotal:") { + // Skip "MemTotal:" and whitespace + let mut pos = 9; + while pos < line.len() && line[pos].is_ascii_whitespace() { + pos += 1; + } + total_memory_kb = parse_u64_fast(&line[pos..]); + found_total = true; + } else if line.starts_with(b"MemAvailable:") { + // Skip "MemAvailable:" and whitespace + let mut pos = 13; + while pos < line.len() && line[pos].is_ascii_whitespace() { + pos += 1; + } + available_memory_kb = parse_u64_fast(&line[pos..]); + found_available = true; } + + offset += line_end + 1; } - let total_memory_gb = total_memory_kb / 1024.0 / 1024.0; - let available_memory_gb = available_memory_kb / 1024.0 / 1024.0; + #[allow(clippy::cast_precision_loss)] + let total_memory_gb = total_memory_kb as f64 / 1024.0 / 1024.0; + #[allow(clippy::cast_precision_loss)] + let available_memory_gb = available_memory_kb as f64 / 1024.0 / 1024.0; let used_memory_gb = total_memory_gb - available_memory_gb; Ok((used_memory_gb, total_memory_gb)) From 07afedd0cc4a49630b23ea830fe56c847eff06da Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 21:42:38 +0300 Subject: [PATCH 57/82] docs: update binary size Signed-off-by: NotAShelf Change-Id: I1a9189f90666d7efc010a7255c287bd86a6a6964 --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 5ae0f72..a40e444 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ on your system: it is pretty _[fast](#benchmarks)_... - Fast - Really fast - Minimal dependencies -- Tiny binary (~410kb) +- Tiny binary (~370kb) - Actually really fast - Cool NixOS logo (other, inferior, distros are not supported) - Reliable detection of following info: @@ -160,7 +160,8 @@ performance regressions. > [!NOTE] > You will need a Nerdfonts patched font installed, and for your terminal -> emulator to support said font. Microfetch uses nerdfonts glyphs by default. +> emulator to support said font. Microfetch uses nerdfonts glyphs by default, +> but this can be changed by [patching the program](#customizing). Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be installed by adding `pkgs.microfetch` to your `environment.systemPackages`. From 6f8d1ffa83fec677001e48de51c3a930885d647d Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Mon, 17 Nov 2025 21:48:15 +0300 Subject: [PATCH 58/82] nix: include benches in source filter; fix build Signed-off-by: NotAShelf Change-Id: Id78ee5f62a5168feef09b5f8713b107c6a6a6964 --- nix/package.nix | 1 + 1 file changed, 1 insertion(+) diff --git a/nix/package.nix b/nix/package.nix index 5bb387c..dca3cc2 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -20,6 +20,7 @@ in (fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src)) (s + /Cargo.lock) (s + /Cargo.toml) + (s + /benches) ]; }; From 353b78e688ddffd86540bf336b52568499f980ec Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Tue, 18 Nov 2025 00:07:26 +0300 Subject: [PATCH 59/82] ci: fix hotpath-comment usage Signed-off-by: NotAShelf Change-Id: Idc163948f012efb07fc7a6a952af54b36a6a6964 --- .github/workflows/hotpath-comment.yml | 46 +++++++++++---------------- 1 file changed, 19 insertions(+), 27 deletions(-) diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml index 395a533..8f83d00 100644 --- a/.github/workflows/hotpath-comment.yml +++ b/.github/workflows/hotpath-comment.yml @@ -13,7 +13,7 @@ jobs: comment: runs-on: ubuntu-latest if: ${{ github.event.workflow_run.conclusion == 'success' }} - + steps: - name: Download profiling results uses: actions/download-artifact@v4 @@ -21,37 +21,29 @@ jobs: name: hotpath-results github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - + - name: Read PR number id: pr run: echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT - + - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 - + - name: Install hotpath CLI run: cargo install hotpath - + - name: Post timing comparison comment - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - hotpath profile-pr \ - --repo ${{ github.repository }} \ - --pr-number ${{ steps.pr.outputs.number }} \ - --head-json head-timing.json \ - --base-json base-timing.json \ - --mode timing \ - --title "⏱️ Hotpath Timing Profile" - - - name: Post allocation comparison comment - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - hotpath profile-pr \ - --repo ${{ github.repository }} \ - --pr-number ${{ steps.pr.outputs.number }} \ - --head-json head-alloc.json \ - --base-json base-alloc.json \ - --mode alloc \ - --title "📊 Hotpath Allocation Profile" + run: | + hotpath profile-pr \ + --head-metrics head-timing.json \ + --base-metrics base-timing.json \ + --github-token ${{ secrets.GITHUB_TOKEN }} \ + --pr-number ${{ steps.pr.outputs.number }} + + - name: Post allocation comparison comment + run: | + hotpath profile-pr \ + --head-metrics head-alloc.json \ + --base-metrics base-alloc.json \ + --github-token ${{ secrets.GITHUB_TOKEN }} \ + --pr-number ${{ steps.pr.outputs.number }} From 6d6ee838bfb514920a71c0e09f9eb524935eb616 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:04:34 +0300 Subject: [PATCH 60/82] chore: bump criterion Signed-off-by: NotAShelf Change-Id: I5623347364c5ed71c99676029f4261026a6a6964 --- Cargo.lock | 29 +++++++++++++++++++++++++---- Cargo.toml | 2 +- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0147746..423e558 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "alloca" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4" +dependencies = [ + "cc", +] + [[package]] name = "anes" version = "0.1.6" @@ -271,10 +280,11 @@ dependencies = [ [[package]] name = "criterion" -version = "0.7.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1c047a62b0cc3e145fa84415a3191f628e980b194c2755aa12300a4e6cbd928" +checksum = "a0dfe5e9e71bdcf4e4954f7d14da74d1cdb92a3a07686452d1509652684b1aab" dependencies = [ + "alloca", "anes", "cast", "ciborium", @@ -283,6 +293,7 @@ dependencies = [ "itertools", "num-traits", "oorandom", + "page_size", "plotters", "rayon", "regex", @@ -294,9 +305,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.6.0" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b1bcc0dc7dfae599d84ad0b1a55f80cde8af3725da8313b528da95ef783e338" +checksum = "5de36c2bee19fba779808f92bf5d9b0fa5a40095c277aba10c458a12b35d21d6" dependencies = [ "cast", "itertools", @@ -816,6 +827,16 @@ version = "11.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" +[[package]] +name = "page_size" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da" +dependencies = [ + "libc", + "winapi", +] + [[package]] name = "percent-encoding" version = "2.3.2" diff --git a/Cargo.toml b/Cargo.toml index e95ef5c..076441c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ hotpath = { optional = true, version = "0.6.0" } libc = "0.2.177" [dev-dependencies] -criterion = "0.7" +criterion = "0.8.0" [features] hotpath = [ "dep:hotpath", "hotpath/hotpath" ] From c3a6d1c93cfa22f21a45fc085eb589576e1fdafd Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:06:33 +0300 Subject: [PATCH 61/82] treewide: fix commonmark formatting in Rustdoc Signed-off-by: NotAShelf Change-Id: Ie1a13221aded56f903156fdb35abe2ac6a6a6964 --- src/lib.rs | 3 ++- src/syscall.rs | 11 +++++++++-- src/uptime.rs | 5 +++-- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 8c8a6ba..1e0f9f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -14,7 +14,8 @@ impl UtsName { /// Calls `uname` syscall and returns a `UtsName` wrapper /// /// # Errors - /// Returns an error if the uname syscall fails + /// + /// Returns an error if the `uname` syscall fails pub fn uname() -> Result { let mut uts = MaybeUninit::uninit(); if unsafe { libc::uname(uts.as_mut_ptr()) } != 0 { diff --git a/src/syscall.rs b/src/syscall.rs index 2776fe7..0c8634b 100644 --- a/src/syscall.rs +++ b/src/syscall.rs @@ -11,11 +11,15 @@ use std::io; /// Direct syscall to open a file -/// Returns file descriptor or -1 on error +/// +/// # Returns +/// +/// File descriptor or -1 on error /// /// # Safety /// /// The caller must ensure: +/// /// - `path` points to a valid null-terminated C string /// - The pointer remains valid for the duration of the syscall #[inline] @@ -65,7 +69,10 @@ pub unsafe fn sys_open(path: *const u8, flags: i32) -> i32 { } /// Direct syscall to read from a file descriptor -/// Returns number of bytes read or -1 on error +/// +/// # Returns n +/// +/// Number of bytes read or -1 on error /// /// # Safety /// diff --git a/src/uptime.rs b/src/uptime.rs index 98c9207..095af7d 100644 --- a/src/uptime.rs +++ b/src/uptime.rs @@ -1,6 +1,6 @@ use std::{io, mem::MaybeUninit}; -/// Fast integer to string conversion (no formatting overhead) +/// Faster integer to string conversion without the formatting overhead. #[inline] fn itoa(mut n: u64, buf: &mut [u8]) -> &str { if n == 0 { @@ -17,9 +17,10 @@ fn itoa(mut n: u64, buf: &mut [u8]) -> &str { unsafe { std::str::from_utf8_unchecked(&buf[i..]) } } -/// Direct sysinfo syscall using inline assembly +/// Direct `sysinfo` syscall using inline assembly /// /// # Safety +/// /// This function uses inline assembly to make a direct syscall. /// The caller must ensure the sysinfo pointer is valid. #[inline] From c1a4bc24f46420336d3aabf7edd2d56d1f6fc143 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:06:48 +0300 Subject: [PATCH 62/82] docs: add note about handwritten assembly Signed-off-by: NotAShelf Change-Id: I6920a416e6169a84469514bae0f207426a6a6964 --- README.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index a40e444..1bd7124 100644 --- a/README.md +++ b/README.md @@ -82,11 +82,14 @@ Nixpkgs, you are recommended to use it to utilize the binary cache properly. The usage of Rust _is_ nice, however, since it provides us with incredible tooling and a very powerful language that allows for Microfetch to be as fast as possible. Sure C could've been used here as well, but do you think I hate -myself? [^1] +myself? -[^1]: Okay, maybe a little bit. One of the future goals of Microfetch is to - defer to inline Assembly for the costliest functions, but that's for a - future date and until I do that I can pretend to be sane. +> [!IMPORTANT] +> **Update as of November 30th, 2025**: +> +> Microfetch now inlines handwritten assembly for even better performance. I +> know I previously said I do not hate myself but I'm beginning to suspect this +> is no longer the case. Enjoy the performance benefits! ## Benchmarks From 09da1c27d21cea01bca93224726ddb3ed95d2441 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:13:14 +0300 Subject: [PATCH 63/82] chore: bump hotpath Signed-off-by: NotAShelf Change-Id: I1ae1d9b3d25ff220bee51660efc598b76a6a6964 --- Cargo.lock | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++--- Cargo.toml | 9 +++---- 2 files changed, 75 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 423e558..88deaeb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -462,6 +462,50 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futures-core" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" + +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-sink" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" + +[[package]] +name = "futures-task" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" + +[[package]] +name = "futures-util" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" +dependencies = [ + "futures-core", + "futures-macro", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + [[package]] name = "getrandom" version = "0.2.16" @@ -513,9 +557,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hotpath" -version = "0.6.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef44238d7007bbd3c41ce7dbf3d7a4fb224bbf70d6cf3162479138dda0cd6a9e" +checksum = "08382b985a19a79d95d35e2e201b02cc4b99efe2f47d82f3fd4301bb0005bb68" dependencies = [ "arc-swap", "base64", @@ -524,10 +568,15 @@ dependencies = [ "colored", "crossbeam-channel", "eyre", + "futures-util", "hdrhistogram", "hotpath-macros", + "libc", + "mach2", + "pin-project-lite", "prettytable-rs", "quanta", + "regex", "serde", "serde_json", "tiny_http", @@ -537,9 +586,9 @@ dependencies = [ [[package]] name = "hotpath-macros" -version = "0.6.0" +version = "0.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e7e3443c6f4e5cc69e6b701f7bfca8a5502f0080e94629413346e7b4518d730" +checksum = "7d618063f89423ebe079a69f5435a13d4909219d4e359757118b75fd05ae65d0" dependencies = [ "proc-macro2", "quote", @@ -769,6 +818,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "mach2" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b" + [[package]] name = "memchr" version = "2.7.4" @@ -849,6 +904,12 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "plotters" version = "0.3.7" @@ -1121,6 +1182,12 @@ version = "0.3.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d66dc143e6b11c1eddc06d5c423cfc97062865baf299914ab64caa38182078fe" +[[package]] +name = "slab" +version = "0.4.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589" + [[package]] name = "smallvec" version = "1.15.1" diff --git a/Cargo.toml b/Cargo.toml index 076441c..f7a2f81 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,17 +12,16 @@ name = "microfetch" path = "src/main.rs" [dependencies] -hotpath = { optional = true, version = "0.6.0" } +hotpath = { optional = true, version = "0.7.5" } libc = "0.2.177" [dev-dependencies] criterion = "0.8.0" [features] -hotpath = [ "dep:hotpath", "hotpath/hotpath" ] -hotpath-alloc-bytes-total = [ "hotpath/hotpath-alloc-bytes-total" ] -hotpath-alloc-count-total = [ "hotpath/hotpath-alloc-count-total" ] -hotpath-off = [ "hotpath/hotpath-off" ] +hotpath = [ "dep:hotpath", "hotpath/hotpath" ] +hotpath-alloc = [ "hotpath/hotpath-alloc" ] +hotpath-off = [ "hotpath/hotpath-off" ] [[bench]] harness = false From 48c3807148892b5888b342cc4a64601c8cf15800 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:14:53 +0300 Subject: [PATCH 64/82] docs: update hotpath usage instructions Signed-off-by: NotAShelf Change-Id: I8a056b6814bef36e74b4aca56d4aa2686a6a6964 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1bd7124..aa299ce 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ HOTPATH_JSON=true cargo run --features=hotpath To profile allocations: ```bash -HOTPATH_JSON=true cargo run --features=hotpath,hotpath-alloc-count-total +HOTPATH_JSON=true cargo run --features=hotpath,hotpath-alloc ``` The JSON output can be analyzed with the `hotpath` CLI tool for detailed From 8d97a9e8ec543611c4fde53c688a13ebd3b9a552 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:32:40 +0300 Subject: [PATCH 65/82] ci: streamline workflows; remove tagged release workflow Signed-off-by: NotAShelf Change-Id: I1432980533dee11b64a53d2ad2f2094d6a6a6964 --- .github/workflows/hotpath-comment.yml | 51 +++++++++++++++++---------- .github/workflows/hotpath-profile.yml | 30 ++++++++-------- 2 files changed, 48 insertions(+), 33 deletions(-) diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml index 8f83d00..4e072fe 100644 --- a/.github/workflows/hotpath-comment.yml +++ b/.github/workflows/hotpath-comment.yml @@ -1,12 +1,13 @@ -name: Hotpath Comment +name: hotpath-comment on: workflow_run: - workflows: ["Hotpath Profile"] + workflows: ["hotpath-profile"] types: - completed permissions: + contents: read pull-requests: write jobs: @@ -15,17 +16,17 @@ jobs: if: ${{ github.event.workflow_run.conclusion == 'success' }} steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Download profiling results uses: actions/download-artifact@v4 with: name: hotpath-results + path: /tmp/ github-token: ${{ secrets.GITHUB_TOKEN }} run-id: ${{ github.event.workflow_run.id }} - - name: Read PR number - id: pr - run: echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT - - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -33,17 +34,29 @@ jobs: run: cargo install hotpath - name: Post timing comparison comment - run: | - hotpath profile-pr \ - --head-metrics head-timing.json \ - --base-metrics base-timing.json \ - --github-token ${{ secrets.GITHUB_TOKEN }} \ - --pr-number ${{ steps.pr.outputs.number }} + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + HEAD_METRICS=$(cat /tmp/head_timing.json) + BASE_METRICS=$(cat /tmp/base_timing.json) + PR_NUMBER=$(cat /tmp/pr_number.txt) + hotpath profile-pr \ + --head-metrics "$HEAD_METRICS" \ + --base-metrics "$BASE_METRICS" \ + --github-token "$GH_TOKEN" \ + --pr-number "$PR_NUMBER" - - name: Post allocation comparison comment - run: | - hotpath profile-pr \ - --head-metrics head-alloc.json \ - --base-metrics base-alloc.json \ - --github-token ${{ secrets.GITHUB_TOKEN }} \ - --pr-number ${{ steps.pr.outputs.number }} + - name: Post allocation comparison comment + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + HEAD_METRICS=$(cat /tmp/head_alloc.json) + BASE_METRICS=$(cat /tmp/base_alloc.json) + PR_NUMBER=$(cat /tmp/pr_number.txt) + hotpath profile-pr \ + --head-metrics "$HEAD_METRICS" \ + --base-metrics "$BASE_METRICS" \ + --github-token "$GH_TOKEN" \ + --pr-number "$PR_NUMBER" diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml index b367ca2..1b4ce26 100644 --- a/.github/workflows/hotpath-profile.yml +++ b/.github/workflows/hotpath-profile.yml @@ -1,4 +1,4 @@ -name: Hotpath Profile +name: hotpath-profile on: pull_request: @@ -14,6 +14,8 @@ jobs: steps: - name: Checkout PR HEAD uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Setup Rust uses: actions-rust-lang/setup-rust-toolchain@v1 @@ -22,42 +24,42 @@ jobs: env: HOTPATH_JSON: "true" run: | - cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > head-timing.json + cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > /tmp/head_timing.json - name: Run allocation profiling on HEAD env: HOTPATH_JSON: "true" run: | - cargo run --features='hotpath,hotpath-alloc-count-total' 2>&1 | grep '^{"hotpath_profiling_mode"' > head-alloc.json + cargo run --features='hotpath,hotpath-alloc' 2>&1 | grep '^{"hotpath_profiling_mode"' > /tmp/head_alloc.json - name: Checkout base branch - uses: actions/checkout@v4 - with: - ref: ${{ github.event.pull_request.base.sha }} + run: | + git checkout ${{ github.event.pull_request.base.sha }} - name: Run timing profiling on base env: HOTPATH_JSON: "true" run: | - cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > base-timing.json + cargo run --features='hotpath' 2>&1 | grep '^{"hotpath_profiling_mode"' > /tmp/base_timing.json || echo '{}' > /tmp/base_timing.json - name: Run allocation profiling on base env: HOTPATH_JSON: "true" run: | - cargo run --features='hotpath,hotpath-alloc-count-total' 2>&1 | grep '^{"hotpath_profiling_mode"' > base-alloc.json + cargo run --features='hotpath,hotpath-alloc' 2>&1 | grep '^{"hotpath_profiling_mode"' > /tmp/base_alloc.json || echo '{}' > /tmp/base_alloc.json - name: Save PR number - run: echo "${{ github.event.number }}" > pr_number.txt + run: | + echo '${{ github.event.pull_request.number }}' > /tmp/pr_number.txt - name: Upload profiling results uses: actions/upload-artifact@v4 with: name: hotpath-results path: | - head-timing.json - head-alloc.json - base-timing.json - base-alloc.json - pr_number.txt + /tmp/head_timing.json + /tmp/head_alloc.json + /tmp/base_timing.json + /tmp/base_alloc.json + /tmp/pr_number.txt retention-days: 1 From 16a1d5fe3f8e2d2740f3eae326664621533edcfb Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 15:38:32 +0300 Subject: [PATCH 66/82] ci: set up dependabot for GH actions Signed-off-by: NotAShelf Change-Id: I890272e62db5824a3d866748375d1a9f6a6a6964 --- .github/dependabot.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..a1067c4 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + # Update Cargo deps + - package-ecosystem: cargo + directory: "/" + schedule: + interval: "weekly" + + # Update used workflows + - package-ecosystem: github-actions + directory: "/" + schedule: + interval: daily + From fa8b6b9d689d8bd7bef306b07de03b555d573018 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 16:54:35 +0300 Subject: [PATCH 67/82] ci: set up cross comp for Rust builds Signed-off-by: NotAShelf Change-Id: I154d2afb05088e22882985bcd4536f026a6a6964 --- .github/workflows/rust.yml | 43 +++++++++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 5 deletions(-) diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index e87e1da..b742a24 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -10,12 +10,45 @@ env: CARGO_TERM_COLOR: always jobs: - build: - - runs-on: ubuntu-latest + test: + name: Test on ${{ matrix.target }} + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + target: x86_64-unknown-linux-gnu + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu steps: - name: "Checkout" uses: actions/checkout@v4 - - name: "Build with Cargo" - run: cargo build --verbose + + - name: "Setup Rust toolchain" + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + target: ${{ matrix.target }} + + - name: "Install cross-compilation tools" + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + sudo apt-get update + sudo apt-get install -y gcc-aarch64-linux-gnu + + - name: "Configure linker for aarch64" + if: matrix.target == 'aarch64-unknown-linux-gnu' + run: | + mkdir -p .cargo + cat >> .cargo/config.toml << EOF + [target.aarch64-unknown-linux-gnu] + linker = "aarch64-linux-gnu-gcc" + EOF + + - name: "Build" + run: cargo build --verbose --target ${{ matrix.target }} + + - name: "Run tests" + if: matrix.target == 'x86_64-unknown-linux-gnu' + run: cargo test --verbose --target ${{ matrix.target }} From 5196ced4874a6ee4e104ca9ab8d9d4ec34d8dbb4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:01:24 +0000 Subject: [PATCH 68/82] chore(deps): bump actions/download-artifact from 4 to 6 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4 to 6. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/hotpath-comment.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml index 4e072fe..cdaa27d 100644 --- a/.github/workflows/hotpath-comment.yml +++ b/.github/workflows/hotpath-comment.yml @@ -20,7 +20,7 @@ jobs: uses: actions/checkout@v4 - name: Download profiling results - uses: actions/download-artifact@v4 + uses: actions/download-artifact@v6 with: name: hotpath-results path: /tmp/ From 5c23cb266527b07f06acfaad36f7b13dc46c9219 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:01:27 +0000 Subject: [PATCH 69/82] chore(deps): bump actions/upload-artifact from 4 to 5 Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 4 to 5. - [Release notes](https://github.com/actions/upload-artifact/releases) - [Commits](https://github.com/actions/upload-artifact/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/upload-artifact dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/hotpath-profile.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml index 1b4ce26..84f0e27 100644 --- a/.github/workflows/hotpath-profile.yml +++ b/.github/workflows/hotpath-profile.yml @@ -53,7 +53,7 @@ jobs: echo '${{ github.event.pull_request.number }}' > /tmp/pr_number.txt - name: Upload profiling results - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@v5 with: name: hotpath-results path: | From d2a981b0705efdf05b69b45cc88e1f290ff36e2f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 30 Nov 2025 14:01:31 +0000 Subject: [PATCH 70/82] chore(deps): bump actions/checkout from 4 to 6 Bumps [actions/checkout](https://github.com/actions/checkout) from 4 to 6. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v4...v6) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '6' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/hotpath-comment.yml | 2 +- .github/workflows/hotpath-profile.yml | 2 +- .github/workflows/rust.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml index 4e072fe..b22b541 100644 --- a/.github/workflows/hotpath-comment.yml +++ b/.github/workflows/hotpath-comment.yml @@ -17,7 +17,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Download profiling results uses: actions/download-artifact@v4 diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml index 1b4ce26..4fd42a9 100644 --- a/.github/workflows/hotpath-profile.yml +++ b/.github/workflows/hotpath-profile.yml @@ -13,7 +13,7 @@ jobs: steps: - name: Checkout PR HEAD - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: fetch-depth: 0 diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index b742a24..5f435fb 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -24,7 +24,7 @@ jobs: steps: - name: "Checkout" - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: "Setup Rust toolchain" uses: actions-rust-lang/setup-rust-toolchain@v1 From ff8af87747f67e9cdd17bd0dfb7dcdd987836381 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 17:01:51 +0300 Subject: [PATCH 71/82] chore: tag 0.4.12 Signed-off-by: NotAShelf Change-Id: I54a5f2181efdd42af2fda7cebb88484f6a6a6964 --- Cargo.lock | 2 +- Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 88deaeb..3a23f75 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -832,7 +832,7 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "microfetch" -version = "0.4.11" +version = "0.4.12" dependencies = [ "criterion", "hotpath", diff --git a/Cargo.toml b/Cargo.toml index f7a2f81..c245c00 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "microfetch" -version = "0.4.11" +version = "0.4.12" edition = "2024" [lib] From 3ad14a95a650f94b9fb4621b6c19d5ffd572cfc5 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 17:23:57 +0300 Subject: [PATCH 72/82] meta: build with Mold linker on x86_64 Linux Signed-off-by: NotAShelf Change-Id: Id51e62dda1ec2ba895ffdbbd25c2f1256a6a6964 --- .cargo/config.toml | 4 ++++ nix/shell.nix | 12 ++++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) create mode 100644 .cargo/config.toml diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..b7efcee --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,4 @@ +# https://github.com/rui314/mold?tab=readme-ov-file#how-to-use +[target.'cfg(target_os = "linux")'] +linker = "clang" +rustflags = ["-C", "link-arg=-fuse-ld=mold"] diff --git a/nix/shell.nix b/nix/shell.nix index 111b803..ae65e10 100644 --- a/nix/shell.nix +++ b/nix/shell.nix @@ -1,27 +1,31 @@ { mkShell, + cargo, + rustc, + mold, + clang, rust-analyzer-unwrapped, rustfmt, clippy, - cargo, taplo, - rustc, rustPlatform, gnuplot, }: mkShell { + name = "microfetch"; strictDeps = true; - nativeBuildInputs = [ cargo rustc + mold + clang rust-analyzer-unwrapped (rustfmt.override {asNightly = true;}) clippy taplo - gnuplot # For Criterion.rs plots + gnuplot # for Criterion.rs plots ]; env.RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; From 8c32f5f4089c862eb1dea153ac41682e33a30bea Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Sun, 30 Nov 2025 17:24:19 +0300 Subject: [PATCH 73/82] nix: build with Mold linker on x86_64-linux Signed-off-by: NotAShelf Change-Id: I771c36297577aba189058a2183ec2b4a6a6a6964 --- README.md | 5 ++++- flake.nix | 7 +++++-- nix/package.nix | 21 ++++++++++++++++++--- 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index aa299ce..f148a61 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ on your system: it is pretty _[fast](#benchmarks)_... - Fast - Really fast - Minimal dependencies -- Tiny binary (~370kb) +- Tiny binary (~370kb [^1]) - Actually really fast - Cool NixOS logo (other, inferior, distros are not supported) - Reliable detection of following info: @@ -60,6 +60,9 @@ on your system: it is pretty _[fast](#benchmarks)_... - Did I mention fast? - Respects [`NO_COLOR` spec](https://no-color.org/) +[^1]: With the Mold linker, which is enabled by default in the Flake package, + the binary size is roughly 350kb. That's nearly 20kb reduction in size :) + ## Motivation Fastfetch, as its name probably hinted, is a very fast fetch tool written in C. diff --git a/flake.nix b/flake.nix index ed36872..90978a2 100644 --- a/flake.nix +++ b/flake.nix @@ -10,9 +10,12 @@ forEachSystem = nixpkgs.lib.genAttrs systems; pkgsForEach = nixpkgs.legacyPackages; in { - packages = forEachSystem (system: { + packages = forEachSystem (system: let + pkgs = pkgsForEach.${system}; + in { default = self.packages.${system}.microfetch; - microfetch = pkgsForEach.${system}.callPackage ./nix/package.nix {}; + microfetch = pkgs.callPackage ./nix/package.nix {}; + microfetch-mold = pkgs.callPackage ./nix/package.nix {useMold = true;}; }); devShells = forEachSystem (system: { diff --git a/nix/package.nix b/nix/package.nix index dca3cc2..052f576 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -1,14 +1,22 @@ { lib, - rustPlatform, + stdenv, stdenvAdapters, + rustPlatform, llvm, + useMold ? stdenv.isLinux && !stdenv.hostPlatform.isAarch, }: let toml = (lib.importTOML ../Cargo.toml).package; pname = toml.name; inherit (toml) version; + + # Select stdenv based on useMold flag + stdenv = + if useMold + then stdenvAdapters.useMoldLinker llvm.stdenv + else llvm.stdenv; in - rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} { + rustPlatform.buildRustPackage.override {inherit stdenv;} { inherit pname version; src = let fs = lib.fileset; @@ -26,7 +34,14 @@ in cargoLock.lockFile = ../Cargo.lock; enableParallelBuilding = true; - env.RUSTFLAGS = "-C link-arg=-fuse-ld=mold"; + buildNoDefaultFeatures = true; + doCheck = false; + + # Only set RUSTFLAGS for mold if useMold is enabled + env = lib.optionalAttrs useMold { + CARGO_LINKER = "clang"; + RUSTFLAGS = "-C link-arg=-fuse-ld=mold"; + }; meta = { description = "Microscopic fetch script in Rust, for NixOS systems"; From 8376e9d32341c44f5bbf07e5020340326093f4b1 Mon Sep 17 00:00:00 2001 From: Uzair Aftab Date: Sun, 30 Nov 2025 18:00:55 +0100 Subject: [PATCH 74/82] perf: use libc to fetch env vars --- src/colors.rs | 6 +++--- src/desktop.rs | 29 +++++++++++++++++------------ src/system.rs | 33 +++++++++++++++++++++------------ 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/src/colors.rs b/src/colors.rs index 07ab9bd..7c65944 100644 --- a/src/colors.rs +++ b/src/colors.rs @@ -1,4 +1,4 @@ -use std::{env, sync::LazyLock}; +use std::sync::LazyLock; pub struct Colors { pub reset: &'static str, @@ -37,8 +37,8 @@ impl Colors { } pub static COLORS: LazyLock = LazyLock::new(|| { - // Check for NO_COLOR once at startup - let is_no_color = env::var("NO_COLOR").is_ok(); + const NO_COLOR: *const libc::c_char = c"NO_COLOR".as_ptr(); + let is_no_color = unsafe { !libc::getenv(NO_COLOR).is_null() }; Colors::new(is_no_color) }); diff --git a/src/desktop.rs b/src/desktop.rs index 561be03..501e967 100644 --- a/src/desktop.rs +++ b/src/desktop.rs @@ -1,22 +1,27 @@ -use std::fmt::Write; +use std::{ffi::CStr, fmt::Write}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_desktop_info() -> String { // Retrieve the environment variables and handle Result types - let desktop_env = std::env::var("XDG_CURRENT_DESKTOP"); - let display_backend = std::env::var("XDG_SESSION_TYPE"); - - let desktop_str = match desktop_env { - Err(_) => "Unknown", - Ok(ref s) if s.starts_with("none+") => &s[5..], - Ok(ref s) => s.as_str(), + let desktop_str = unsafe { + let ptr = libc::getenv(c"XDG_CURRENT_DESKTOP".as_ptr()); + if ptr.is_null() { + "Unknown" + } else { + let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); + s.strip_prefix("none+").unwrap_or(s) + } }; - let backend_str = match display_backend { - Err(_) => "Unknown", - Ok(ref s) if s.is_empty() => "Unknown", - Ok(ref s) => s.as_str(), + let backend_str = unsafe { + let ptr = libc::getenv(c"XDG_SESSION_TYPE".as_ptr()); + if ptr.is_null() { + "Unknown" + } else { + let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); + if s.is_empty() { "Unknown" } else { s } + } }; // Pre-calculate capacity: desktop_len + " (" + backend_len + ")" diff --git a/src/system.rs b/src/system.rs index 24406ec..ba8fe79 100644 --- a/src/system.rs +++ b/src/system.rs @@ -1,11 +1,18 @@ -use std::{env, fmt::Write as _, io, mem::MaybeUninit}; +use std::{ffi::CStr, fmt::Write as _, io, mem::MaybeUninit}; use crate::{UtsName, colors::COLORS, syscall::read_file_fast}; #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_username_and_hostname(utsname: &UtsName) -> String { - let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_owned()); + let username = unsafe { + let ptr = libc::getenv(c"USER".as_ptr()); + if ptr.is_null() { + "unknown_user" + } else { + CStr::from_ptr(ptr).to_str().unwrap_or("unknown_user") + } + }; let hostname = utsname.nodename().to_str().unwrap_or("unknown_host"); let capacity = COLORS.yellow.len() @@ -18,7 +25,7 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { let mut result = String::with_capacity(capacity); result.push_str(COLORS.yellow); - result.push_str(&username); + result.push_str(username); result.push_str(COLORS.red); result.push('@'); result.push_str(COLORS.green); @@ -31,15 +38,17 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String { #[must_use] #[cfg_attr(feature = "hotpath", hotpath::measure)] pub fn get_shell() -> String { - let shell_path = - env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_owned()); + unsafe { + let ptr = libc::getenv(c"SHELL".as_ptr()); + if ptr.is_null() { + return "unknown_shell".into(); + } - // Find last '/' and get the part after it, avoiding allocation - shell_path - .rsplit('/') - .next() - .unwrap_or("unknown_shell") - .to_owned() + let bytes = CStr::from_ptr(ptr).to_bytes(); + let start = bytes.iter().rposition(|&b| b == b'/').map_or(0, |i| i + 1); + let name = std::str::from_utf8_unchecked(&bytes[start..]); + name.into() + } } /// Gets the root disk usage information. @@ -106,7 +115,7 @@ pub fn get_memory_usage() -> Result { fn parse_memory_info() -> Result<(f64, f64), io::Error> { let mut total_memory_kb = 0u64; let mut available_memory_kb = 0u64; - let mut buffer = [0u8; 2048]; + let mut buffer = [0u8; 1024]; // Use fast syscall-based file reading let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)?; From 9da87b933dd3a5b4d92860aef958e8660d8d2529 Mon Sep 17 00:00:00 2001 From: Uzair Aftab Date: Sun, 30 Nov 2025 19:05:05 +0100 Subject: [PATCH 75/82] perf: use stack buffer and direct write syscall in print_system_info Eliminates ~1KB stdout buffering allocation by using Cursor<&mut [u8]> and libc::write instead of format!() + stdout().write_all() --- src/main.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main.rs b/src/main.rs index 4e664bb..d02f16e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -5,7 +5,7 @@ mod syscall; mod system; mod uptime; -use std::io::{Write, stdout}; +use std::io::{self, Cursor, Write}; pub use microfetch_lib::UtsName; @@ -81,7 +81,13 @@ fn print_system_info( let cyan = COLORS.cyan; let blue = COLORS.blue; let reset = COLORS.reset; - let system_info = format!(" + + let mut buf = [0u8; 2048]; + let mut cursor = Cursor::new(&mut buf[..]); + + write!( + cursor, + " {cyan} ▟█▖ {blue}▝█▙ ▗█▛ {user_info} ~{reset} {cyan} ▗▄▄▟██▄▄▄▄▄{blue}▝█▙█▛ {cyan}▖ {cyan} {blue}System{reset}  {os_name} {cyan} ▀▀▀▀▀▀▀▀▀▀▀▘{blue}▝██ {cyan}▟█▖ {cyan} {blue}Kernel{reset}  {kernel_version} @@ -90,7 +96,16 @@ fn print_system_info( {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} - {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n"); + {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n" + )?; - Ok(stdout().write_all(system_info.as_bytes())?) + #[allow(clippy::cast_possible_truncation)] + let len = cursor.position() as usize; + + // Direct syscall to avoid stdout buffering allocation + let written = unsafe { libc::write(libc::STDOUT_FILENO, buf.as_ptr().cast(), len) }; + if written < 0 { + return Err(io::Error::last_os_error().into()); + } + Ok(()) } From 80e64a1c70202cbe1110ebd43a43562d43bd468a Mon Sep 17 00:00:00 2001 From: poz Date: Mon, 1 Dec 2025 20:36:31 +0100 Subject: [PATCH 76/82] add .direnv to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 8ea0ee8..8799178 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target result* +/.direnv From e4880661ae5453b61ca40ebff90aed8affc24c31 Mon Sep 17 00:00:00 2001 From: poz Date: Mon, 1 Dec 2025 20:37:01 +0100 Subject: [PATCH 77/82] print extra newline after fetch for better looks --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index 4e664bb..ab93580 100644 --- a/src/main.rs +++ b/src/main.rs @@ -90,7 +90,7 @@ fn print_system_info( {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} - {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n"); + {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n\n"); Ok(stdout().write_all(system_info.as_bytes())?) } From f0cf18dba708e03d3878d26ced25c00daec4a128 Mon Sep 17 00:00:00 2001 From: Uzair Aftab Date: Tue, 9 Dec 2025 19:36:29 +0100 Subject: [PATCH 78/82] fix: handle partial writes from libc::write --- src/main.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.rs b/src/main.rs index d02f16e..5e37d85 100644 --- a/src/main.rs +++ b/src/main.rs @@ -107,5 +107,8 @@ fn print_system_info( if written < 0 { return Err(io::Error::last_os_error().into()); } + if written as usize != len { + return Err(io::Error::new(io::ErrorKind::WriteZero, "partial write to stdout").into()); + } Ok(()) } From 7b8e736ff74c4b183a4f129e7f6884e9140aa360 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 10 Dec 2025 11:06:58 +0300 Subject: [PATCH 79/82] microfetch: update memory icon Fixes #34 Signed-off-by: NotAShelf Change-Id: Iff9da39a11eb4060eb314343efa2674b6a6a6964 --- src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main.rs b/src/main.rs index c3e7c69..e29d6d1 100644 --- a/src/main.rs +++ b/src/main.rs @@ -94,7 +94,7 @@ fn print_system_info( {blue} ▟█▛ {blue}▝█▘{cyan}▟█▛ {cyan} {blue}Shell{reset}  {shell} {blue}▟█████▛ {cyan}▟█████▛ {cyan} {blue}Uptime{reset}  {uptime} {blue} ▟█▛{cyan}▗█▖ {cyan}▟█▛ {cyan} {blue}Desktop{reset}  {desktop} - {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan} {blue}Memory{reset}  {memory_usage} + {blue} ▝█▛ {cyan}██▖{blue}▗▄▄▄▄▄▄▄▄▄▄▄ {cyan}󰍛 {blue}Memory{reset}  {memory_usage} {blue} ▝ {cyan}▟█▜█▖{blue}▀▀▀▀▀██▛▀▀▘ {cyan}󱥎 {blue}Storage (/){reset}  {storage} {cyan} ▟█▘ ▜█▖ {blue}▝█▛ {cyan} {blue}Colors{reset}  {colors}\n\n" )?; From cf3321b17ac6e865ef0ee17e930589226caf1610 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 10 Dec 2025 12:06:13 +0300 Subject: [PATCH 80/82] chore: bump dependencies Signed-off-by: NotAShelf Change-Id: I95094ead2493107a2d4bdd3f797fee246a6a6964 --- Cargo.lock | 12 ++++++------ Cargo.toml | 4 ++-- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3a23f75..e2397ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -557,9 +557,9 @@ checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" [[package]] name = "hotpath" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "08382b985a19a79d95d35e2e201b02cc4b99efe2f47d82f3fd4301bb0005bb68" +checksum = "4b0a2c66c081fe3684a54a7e5d059c9d9ad6b3ee5ccea14f6e4f056dbd77becf" dependencies = [ "arc-swap", "base64", @@ -586,9 +586,9 @@ dependencies = [ [[package]] name = "hotpath-macros" -version = "0.7.5" +version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d618063f89423ebe079a69f5435a13d4909219d4e359757118b75fd05ae65d0" +checksum = "a38fa43ca80cf906cd05127e490d740a51abb38316db7bce9d95e89724a81761" dependencies = [ "proc-macro2", "quote", @@ -786,9 +786,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.177" +version = "0.2.178" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976" +checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" [[package]] name = "libredox" diff --git a/Cargo.toml b/Cargo.toml index c245c00..9f6cc4a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,8 +12,8 @@ name = "microfetch" path = "src/main.rs" [dependencies] -hotpath = { optional = true, version = "0.7.5" } -libc = "0.2.177" +hotpath = { optional = true, version = "0.8.0" } +libc = "0.2.178" [dev-dependencies] criterion = "0.8.0" From 6640fdd559afebbe4c05fb4d5b054206d30d1d05 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 10 Dec 2025 09:08:02 +0000 Subject: [PATCH 81/82] chore(deps): bump criterion from 0.8.0 to 0.8.1 Bumps [criterion](https://github.com/criterion-rs/criterion.rs) from 0.8.0 to 0.8.1. - [Release notes](https://github.com/criterion-rs/criterion.rs/releases) - [Changelog](https://github.com/criterion-rs/criterion.rs/blob/master/CHANGELOG.md) - [Commits](https://github.com/criterion-rs/criterion.rs/compare/criterion-v0.8.0...criterion-v0.8.1) --- updated-dependencies: - dependency-name: criterion dependency-version: 0.8.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- Cargo.lock | 8 ++++---- Cargo.toml | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e2397ab..49e7cf4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,9 +280,9 @@ dependencies = [ [[package]] name = "criterion" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a0dfe5e9e71bdcf4e4954f7d14da74d1cdb92a3a07686452d1509652684b1aab" +checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" dependencies = [ "alloca", "anes", @@ -305,9 +305,9 @@ dependencies = [ [[package]] name = "criterion-plot" -version = "0.8.0" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de36c2bee19fba779808f92bf5d9b0fa5a40095c277aba10c458a12b35d21d6" +checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" dependencies = [ "cast", "itertools", diff --git a/Cargo.toml b/Cargo.toml index 9f6cc4a..0c70446 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ hotpath = { optional = true, version = "0.8.0" } libc = "0.2.178" [dev-dependencies] -criterion = "0.8.0" +criterion = "0.8.1" [features] hotpath = [ "dep:hotpath", "hotpath/hotpath" ] From 2066d781fe95c7c131c8d5006145b2a4b63682a7 Mon Sep 17 00:00:00 2001 From: NotAShelf Date: Wed, 10 Dec 2025 12:34:05 +0300 Subject: [PATCH 82/82] docs: update motivation section a little God redditors are insufferable Signed-off-by: NotAShelf Change-Id: I468d0fe0c1aaaa8d08d41373cfc8993c6a6a6964 --- README.md | 114 ++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index f148a61..8f59640 100644 --- a/README.md +++ b/README.md @@ -6,15 +6,11 @@

-

- Microfetch -

-

- Microscopic fetch tool in Rust, for NixOS systems, with special emphasis on speed -

+

Microfetch

+

Microscopic fetch tool in Rust, for NixOS systems, with special emphasis on speed


Synopsis
- Features | Motivation
+ Features | Motivation
| Benchmarks
Installation
@@ -65,34 +61,42 @@ on your system: it is pretty _[fast](#benchmarks)_... ## Motivation -Fastfetch, as its name probably hinted, is a very fast fetch tool written in C. -However, I am not interested in _any_ of its additional features, and I'm not -interested in its configuration options. Sure I can _configure_ it when I -dislike the defaults, but how often would I really change the configuration... +[Rube-Goldmark Machine]: https://en.wikipedia.org/wiki/Rube_Goldberg_machine -Microfetch is my response to this problem. It is an _even faster_ fetch tool -that I would've written in Bash and put in my `~/.bashrc` but is _actually_ -incredibly fast because it opts out of all the customization options provided by -tools such as Fastfetch. Ultimately, it's a small, opinionated binary with a -nice size that doesn't bother me, and incredible speed. Customization? No thank -you. I cannot re-iterate it enough, Microfetch is _annoyingly fast_. +Fastfetch, as its name _probably_ already hinted, is a very fast fetch tool +written in C. I used to use Fastfetch on my systems, but I eventually came to +the realization that I am _not interested in any of its additional features_. I +don't use Sixel, I don't change my configuration more than maybe once a year and +I don't even display most of the fields that it does. Sure the configurability +is nice and I can configure the defaults that I do not like but how often do I +really do that? -The project is written in Rust, which comes at the cost of "bloated" dependency -trees and the increased build times, but we make an extended effort to keep the -dependencies minimal and build times managable. The latter is also very easily -mitigated with Nix's binary cache systems. Since Microfetch is already in -Nixpkgs, you are recommended to use it to utilize the binary cache properly. The -usage of Rust _is_ nice, however, since it provides us with incredible tooling -and a very powerful language that allows for Microfetch to be as fast as -possible. Sure C could've been used here as well, but do you think I hate -myself? +Since I already enjoy programming challenges, and don't use a fetch program that +often, I eventually came to try and answer the question _how fast can I make my +fetch script?_ It is an _even faster_ fetch tool that I would've written in Bash +and put in my `~/.bashrc` but is _actually_ incredibly fast because it opts out +of all the customization options provided by tools such as Fastfetch. Since +Fetch scripts are kind of a coming-of-age ritual for most Linux users, I've +decided to use it on my system. You also might be interested if you like the +defaults and like speed. -> [!IMPORTANT] -> **Update as of November 30th, 2025**: -> -> Microfetch now inlines handwritten assembly for even better performance. I -> know I previously said I do not hate myself but I'm beginning to suspect this -> is no longer the case. Enjoy the performance benefits! +Ultimately, it's a small, opinionated binary with a nice size that doesn't +bother me, and incredible speed. Customization? No thank you. I cannot +re-iterate it enough, Microfetch is _annoyingly fast_. It does not, however, +solve a technical problem. The "problem" Microfetch solves is entirely +self-imposed. On the matter of _size_, the project is written in Rust, which +comes at the cost of "bloated" dependency trees and the increased build times, +but we make an extended effort to keep the dependencies minimal and build times +managable. The latter is also very easily mitigated with Nix's binary cache +systems. Since Microfetch is already in Nixpkgs, you are recommended to use it +to utilize the binary cache properly. The usage of Rust _is_ nice, however, +since it provides us with incredible tooling and a very powerful language that +allows for Microfetch to be as fast as possible. ~~Sure C could've been used +here as well, but do you think I hate myself?~~ Microfetch now features +handwritten assembly to unsafely optimize some areas. In hindsight you all +should have seen this coming. Is it faster? Yes. + +Also see: [Rube-Goldmark Machine] ## Benchmarks @@ -200,17 +204,31 @@ You can't. ### Why? -Customization, of any kind, is expensive: I could try reading environment -variables, parse command-line arguments or read a configuration file but all of -those increment execution time and resource consumption by a lot. +Customization, of most kinds, are expensive: I could try reading environment +variables, parse command-line arguments or read a configuration file to allow +configuring various fields but those inflate execution time and the resource +consumption by a lot. Since Microfetch is closer to a code golf challenge than a +program that attempts to fill a gap, I have elected not to make this trade. ### Really? -To be fair, you _can_ customize Microfetch by, well, patching it. It's not the -best way per se, but it will be the only way that does not compromise on speed. +[main module]: ./src/main.rs +[discussions tab]: https://github.com/NotAShelf/microfetch/discussions + +To be fair, you _can_ customize Microfetch by, well, patching it. It is +certainly not the easiest way of doing so but if you are planning to change +something in Microfetch, patching is the best way to go. It will also the only +way that does not compromise on speed, unless you patch in bad code. Various +users have adapted Microfetch to their distribution by patching the +[main module] and inserting the logo of their choice. This is also the best way +to go if you plan to make small changes. If your changes are not small, you +might want to look for a program that is designed to be customizable; Microfetch +is built for maximum performance. The Nix package allows passing patches in a streamlined manner by passing -`.overrideAttrs` to the derivation. +`.overrideAttrs` to the derivation. You can apply your patches in `patches` and +share your derivations with people. Feel free to use the [discussions tab] to +share your own variants of Microfetch! ## Contributing @@ -222,13 +240,22 @@ Contributions that help improve performance in specific areas of Microfetch are welcome. Though, prepare to be bombarded with questions if your changes are large. -## Hacking +### Hacking -A Nix flake is provided. `nix develop` to get started. Direnv users may simply -run `direnv allow` to get started. +A Nix flake is provided. You may use `nix develop` to get started. Direnv users +may instead run `direnv allow` to get a complete environment with shell +integration. -Non-nix users will need `cargo` and `gcc` installed on their system, see -`Cargo.toml` for available release profiles. +Non-Nix user will need `cargo`, `clang` and `mold` installed on their system to +build Microfetch. As Mold seems to yield _slightly_ better results than the +default linker, it has been set as the default in `.cargo/config.toml` for +x86-64 Linux. You may override those defaults using the `RUSTFLAGS` environment +variable. For example: + +```sh +# Use ld instead of Mold +$ RUSTFLAGS="-C linker=/path/to/ld.lld" cargo build +``` ## Thanks @@ -245,6 +272,7 @@ person about current issues. To list a few, special thanks to: - [@sioodmy](https://github.com/sioodmy) - Being cute - [@mewoocat](https://github.com/mewoocat) - The awesome NixOS logo ASCII used in Microfetch +- [@uzaaft](https://github.com/uzaaft) - Helping me going faster Additionally a big thank you to everyone who used, talked about or criticized Microfetch. I might have missed your name here, but you have my thanks.