Compare commits

...

8 commits

Author SHA1 Message Date
SomeEmptyBox
4fff13a51f
Update memory icon (#15)
Some checks failed
Rust / build (push) Has been cancelled
Makes memory icon bigger by using CPU icon from Nerdfonts instead of the memory icon.
2024-12-19 17:02:32 +00:00
ea8280ef77
docs: update benchmarks to reflect recent improvements
Faster and faster we go.
2024-12-19 20:00:37 +03:00
4b7836d572
append newline to write_all output
Fixes a small bug that resulted in terminal artifacts. My bad, gang.
2024-12-19 19:13:16 +03:00
c97fa33aec
0.4.3
Moar speed, NO_COLOR support
2024-12-19 18:47:04 +03:00
fd18e9d244
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.
2024-12-19 18:30:33 +03:00
e19abcedae
docs: update readme; mention NO_COLOR changes 2024-12-19 18:14:23 +03:00
a96effb875
bump version 2024-12-19 18:14:15 +03:00
065216af7c
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.
2024-12-19 17:21:56 +03:00
7 changed files with 152 additions and 70 deletions

9
Cargo.lock generated
View file

@ -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"
@ -326,10 +332,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "microfetch"
version = "0.4.0"
version = "0.4.4"
dependencies = [
"color-eyre",
"criterion",
"lazy_static",
"nix",
]

View file

@ -1,6 +1,6 @@
[package]
name = "microfetch"
version = "0.4.0"
version = "0.4.4"
edition = "2021"
[lib]
@ -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"

View file

@ -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)...
<p align="center">
<img
@ -34,42 +34,51 @@ 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
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.
The performance may be sometimes influenced by hardware-specific race
conditions, or even your kernel configuration meaning it may (at times) depend
on your hardware. However, the overall trend appears to be less than 1.3ms on
any modern (2015 and after) CPU that I own. Below are the benchmarks with
Hyperfine on my desktop system. Please note that those benchmarks will not be
always kept up to date, but I will try to update the numbers as I make
Microfetch faster.
| 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 |
| Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? |
| :----------- | -----------: | -------: | -------: | -------------: | --------------: |
| `microfetch` | 1.0 ± 0.1 | 0.9 | 1.7 | 1.00 | yes |
| `fastfetch` | 48.6 ± 1.6 | 45.8 | 61.3 | 46.65 ± 4.75 | no |
| `pfetch` | 206.0 ± 4.5 | 198.0 | 241.4 | 197.50 ± 19.53 | no |
| `neofetch` | 689.1 ± 29.1 | 637.7 | 811.2 | 660.79 ± 69.56 | no |
_As far as I'm concerned, Microfetch is faster than almost every fetch tool
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._
As far as I'm concerned, Microfetch is significantly faster than every other
fetch tool that I have tried. The only downsides of using Rust for the project
(in exchange for speed and maintainability) is the slightly "bloated" dependency
trees, and the increased build times. The latter is very easily mitigated with
Nix's binary cache. Since Microfetch is already in Nixpkgs, you are recommended
to use it to utilize the binary cache properly
### Benchmarking Individual Functions
[Criterion.rs]: https://github.com/bheisler/criterion.rs
[Getting Started Guide]: https://bheisler.github.io/criterion.rs/book/getting_started.html
@ -84,9 +93,19 @@ features of Microfetch.
> 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

View file

@ -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,
)
}

View file

@ -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(format!("{}\n", system_info).as_bytes())
.expect("Failed to write to stdout");
}

View file

@ -21,7 +21,13 @@ pub fn get_os_pretty_name() -> Result<String, io::Error> {
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());
}
}

View file

@ -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<String, io::Error> {
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<String, io::Error> {
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,
))
}