Compare commits

..

No commits in common. "4fff13a51f0e58489d95f43862738c86be5d0147" and "1b0d15a24fbcbb0fa17b81479bb868c9a30ddf70" have entirely different histories.

7 changed files with 70 additions and 152 deletions

9
Cargo.lock generated
View file

@ -306,12 +306,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.155" version = "0.2.155"
@ -332,11 +326,10 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "microfetch" name = "microfetch"
version = "0.4.4" version = "0.4.0"
dependencies = [ dependencies = [
"color-eyre", "color-eyre",
"criterion", "criterion",
"lazy_static",
"nix", "nix",
] ]

View file

@ -1,6 +1,6 @@
[package] [package]
name = "microfetch" name = "microfetch"
version = "0.4.4" version = "0.4.0"
edition = "2021" edition = "2021"
[lib] [lib]
@ -14,7 +14,6 @@ path = "src/main.rs"
[dependencies] [dependencies]
nix = { version = "0.29", features = ["fs", "hostname", "feature"] } nix = { version = "0.29", features = ["fs", "hostname", "feature"] }
color-eyre = { version = "0.6", default-features = false } color-eyre = { version = "0.6", default-features = false }
lazy_static = "1.5.0"
[dev-dependencies] [dev-dependencies]
criterion = "0.5" 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 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 to replace [fastfetch](https://github.com/fastfetch-cli/fastfetch) on my
personal system, but [probably not yours](#customizing). Though, you are more 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"> <p align="center">
<img <img
@ -34,51 +34,42 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
- Name - Name
- Version - Version
- Architecture - 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) - Current Desktop (DE/WM/Compositor and display backend)
- Memory Usage/Total Memory - Memory Usage/Total Memory
- Storage Usage/Total Storage (for `/` only) - Storage Usage/Total Storage (for `/` only)
- Shell Colors - Shell Colors
- Did I mention fast? - Did I mention fast?
- Respects [`NO_COLOR` spec](https://no-color.org/)
## Motivation ## Motivation
Fastfetch, as its name indicates, a very fast fetch tool written in C, however, Fastfetch, as its name indicates, a very fast fetch tool written in C, however,
I am not interested in any of its additional features, such as customization, I am not interested in any of its additional features and I very much dislike
and I very much dislike the defaults. Microfetch is my response to this problem, the defaults. Microfetch is a fetch tool that you would normally write in Bash
a _very fast_ fetch tool that you would normally write in Bash and put in your and put in your `~/.bashrc` but actually _really_ fast because it opts-out of
`~/.bashrc` but actually _really_ fast because it opts-out of all customization all customization options provided by Fastfetch. Why? Because I can, and because
options provided by Fastfetch, and is written in Rust. Why? Because I can, and I prefer Rust for "structured" Bash scripts.
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 ## Benchmarks
The performance may be sometimes influenced by hardware-specific race Microfetch's performance is capped by hardware-specific race conditions, meaning
conditions, or even your kernel configuration meaning it may (at times) depend it may (at times) depend on your hardware. However, the overall trend seems to
on your hardware. However, the overall trend appears to be less than 1.3ms on be < 2ms on any modern (2015 and after) CPU. Below are the benchmarks with
any modern (2015 and after) CPU that I own. Below are the benchmarks with Hyperfine on my desktop system.
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? | | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? |
| :----------- | -----------: | -------: | -------: | -------------: | --------------: | | :----------- | ----------: | -------: | -------: | -------------: | --------------: |
| `microfetch` | 1.0 ± 0.1 | 0.9 | 1.7 | 1.00 | yes | | `microfetch` | 1.3 ± 0.0 | 1.3 | 1.4 | 1.00 | yes |
| `fastfetch` | 48.6 ± 1.6 | 45.8 | 61.3 | 46.65 ± 4.75 | no | | `fastfetch` | 31.9 ± 0.8 | 30.8 | 33.8 | 24.08 ± 0.98 | no |
| `pfetch` | 206.0 ± 4.5 | 198.0 | 241.4 | 197.50 ± 19.53 | no | | `pfetch` | 254.2 ± 4.8 | 246.7 | 264.9 | 191.97 ± 7.10 | no |
| `neofetch` | 689.1 ± 29.1 | 637.7 | 811.2 | 660.79 ± 69.56 | no | | `neofetch` | 735.4 ± 9.5 | 721.1 | 752.8 | 555.48 ± 19.08 | no |
As far as I'm concerned, Microfetch is significantly faster than every other _As far as I'm concerned, Microfetch is faster than almost every fetch tool
fetch tool that I have tried. The only downsides of using Rust for the project there is. The only downsides of using Rust are introducing more "bloated"
(in exchange for speed and maintainability) is the slightly "bloated" dependency dependency trees and increasing build times. The latter is easily mitigated with
trees, and the increased build times. The latter is very easily mitigated with Nix's binary cache, though._
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 [Criterion.rs]: https://github.com/bheisler/criterion.rs
[Getting Started Guide]: https://bheisler.github.io/criterion.rs/book/getting_started.html [Getting Started Guide]: https://bheisler.github.io/criterion.rs/book/getting_started.html
@ -93,19 +84,9 @@ features of Microfetch.
> You will need a Nerdfonts patched font installed, and for your terminal > 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.
Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). You can
installed by adding `pkgs.microfetch` to your `environment.systemPackages`. get it through the unstable channel for the time being. The Nix flake can also
Additionally, you can try out Microfetch in a Nix shell. be used for bleeding-edge builds.
```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 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 anywhere but I imagine you can use `cargo install --git` to install it from

View file

@ -1,57 +1,11 @@
use std::env; pub const RESET: &str = "\x1b[0m";
pub const BLUE: &str = "\x1b[34m";
pub struct Colors { pub const CYAN: &str = "\x1b[36m";
pub reset: &'static str, pub const GREEN: &str = "\x1b[32m";
pub blue: &'static str, pub const YELLOW: &str = "\x1b[33m";
pub cyan: &'static str, pub const RED: &str = "\x1b[31m";
pub green: &'static str, pub const MAGENTA: &str = "\x1b[35m";
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 { pub fn print_dots() -> String {
format!( format!("{BLUE}{CYAN}{GREEN}{YELLOW}{RED}{MAGENTA}{RESET}")
"{} {} {} {} {} {} {}",
COLORS.blue,
COLORS.cyan,
COLORS.green,
COLORS.yellow,
COLORS.red,
COLORS.magenta,
COLORS.reset,
)
} }

View file

@ -4,13 +4,15 @@ mod release;
mod system; mod system;
mod uptime; mod uptime;
use crate::colors::print_dots; use std::io::Write;
use crate::colors::{print_dots, BLUE, CYAN, RESET};
use crate::desktop::get_desktop_info; use crate::desktop::get_desktop_info;
use crate::release::{get_os_pretty_name, get_system_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::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname};
use crate::uptime::get_current; use crate::uptime::get_current;
use color_eyre::Report; use color_eyre::Report;
use std::io::Write;
fn main() -> Result<(), Report> { fn main() -> Result<(), Report> {
color_eyre::install()?; color_eyre::install()?;
@ -54,8 +56,6 @@ struct Fields {
} }
fn print_system_info(fields: &Fields) { fn print_system_info(fields: &Fields) {
use crate::colors::COLORS;
let Fields { let Fields {
user_info, user_info,
os_name, os_name,
@ -68,22 +68,16 @@ fn print_system_info(fields: &Fields) {
colors, colors,
} = fields; } = fields;
let cyan = COLORS.cyan; let _ = std::io::stdout().write_all(format!(
let blue = COLORS.blue; "
let reset = COLORS.reset; {CYAN} {BLUE} {user_info} ~{RESET}
let system_info = format!(" {CYAN} {BLUE} {CYAN} {CYAN} {BLUE}System{RESET} {os_name}
{cyan} {blue} {user_info} ~{reset} {CYAN} {BLUE} {CYAN} {CYAN} {BLUE}Kernel{RESET} {kernel_version}
{cyan} {blue} {cyan} {cyan} {blue}System{reset} {os_name} {BLUE} {BLUE}{CYAN} {CYAN} {BLUE}Shell{RESET} {shell}
{cyan} {blue} {cyan} {cyan} {blue}Kernel{reset} {kernel_version} {BLUE} {CYAN} {CYAN} {BLUE}Uptime{RESET} {uptime}
{blue} {blue}{cyan} {cyan} {blue}Shell{reset} {shell} {BLUE} {CYAN} {CYAN} {CYAN} {BLUE}Desktop{RESET} {desktop}
{blue} {cyan} {cyan} {blue}Uptime{reset} {uptime} {BLUE} {CYAN}{BLUE} {CYAN}󰍛 {BLUE}Memory{RESET} {memory_usage}
{blue} {cyan} {cyan} {cyan} {blue}Desktop{reset} {desktop} {BLUE} {CYAN}{BLUE} {CYAN}󱥎 {BLUE}Storage (/){RESET} {storage}
{blue} {cyan}{blue} {cyan} {blue}Memory{reset} {memory_usage} {CYAN} {BLUE} {CYAN} {BLUE}Colors{RESET} {colors}
{blue} {cyan}{blue} {cyan}󱥎 {blue}Storage (/){reset} {storage} ").as_bytes());
{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,13 +21,7 @@ pub fn get_os_pretty_name() -> Result<String, io::Error> {
for line in reader.lines() { for line in reader.lines() {
let line = line?; let line = line?;
if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") { if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
if let Some(trimmed) = pretty_name return Ok(pretty_name.trim_matches('"').to_string());
.strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
{
return Ok(trimmed.to_string());
}
return Ok(pretty_name.to_string());
} }
} }

View file

@ -1,12 +1,14 @@
use crate::colors::COLORS;
use color_eyre::Result; use color_eyre::Result;
use nix::sys::{statvfs::statvfs, utsname::UtsName}; use nix::sys::{statvfs::statvfs, utsname::UtsName};
use std::{ use std::{
env, env,
fs::File, fs::File,
io::{self, Read}, io::{self, Read},
}; };
use crate::colors::{CYAN, GREEN, RED, RESET, YELLOW};
pub fn get_username_and_hostname(utsname: &UtsName) -> String { 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("unknown_user".to_string());
let hostname = utsname let hostname = utsname
@ -14,18 +16,14 @@ pub fn get_username_and_hostname(utsname: &UtsName) -> String {
.to_str() .to_str()
.unwrap_or("unknown_host") .unwrap_or("unknown_host")
.to_string(); .to_string();
format!(
"{yellow}{username}{red}@{green}{hostname}{reset}", format!("{YELLOW}{username}{RED}@{GREEN}{hostname}")
yellow = COLORS.yellow,
red = COLORS.red,
green = COLORS.green,
reset = COLORS.reset,
)
} }
pub fn get_shell() -> 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("unknown_shell".to_string());
let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell"); let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
shell_name.to_string() shell_name.to_string()
} }
@ -34,15 +32,16 @@ pub fn get_root_disk_usage() -> Result<String, io::Error> {
let block_size = vfs.block_size() as u64; let block_size = vfs.block_size() as u64;
let total_blocks = vfs.blocks(); let total_blocks = vfs.blocks();
let available_blocks = vfs.blocks_available(); let available_blocks = vfs.blocks_available();
let total_size = block_size * total_blocks; let total_size = block_size * total_blocks;
let used_size = total_size - (block_size * available_blocks); let used_size = total_size - (block_size * available_blocks);
let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.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 used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
let usage = (used_size / total_size) * 100.0; let usage = (used_size as f64 / total_size as f64) * 100.0;
Ok(format!( 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,
)) ))
} }
@ -51,31 +50,35 @@ pub fn get_memory_usage() -> Result<String, io::Error> {
fn parse_memory_info() -> Result<(f64, f64), io::Error> { fn parse_memory_info() -> Result<(f64, f64), io::Error> {
let mut total_memory_kb = 0.0; let mut total_memory_kb = 0.0;
let mut available_memory_kb = 0.0; let mut available_memory_kb = 0.0;
let mut meminfo = String::with_capacity(2048); 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() { for line in meminfo.lines() {
let mut split = line.split_whitespace(); let mut split = line.split_whitespace();
match split.next().unwrap_or_default() { match split.next().unwrap_or_default() {
"MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0), "MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0),
"MemAvailable:" => { "MemAvailable:" => {
available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0); available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
// MemTotal comes before MemAvailable, stop parsing // MemTotal comes before MemAvailable, stop parsing
break; break;
} }
_ => (), _ => (),
} }
} }
let total_memory_gb = total_memory_kb / 1024.0 / 1024.0; let total_memory_gb = total_memory_kb / 1024.0 / 1024.0;
let available_memory_gb = available_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; let used_memory_gb = total_memory_gb - available_memory_gb;
Ok((used_memory_gb, total_memory_gb)) Ok((used_memory_gb, total_memory_gb))
} }
let (used_memory, total_memory) = parse_memory_info()?; let (used_memory, total_memory) = parse_memory_info()?;
let percentage_used = (used_memory / total_memory * 100.0).round() as u64; let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
Ok(format!( 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,
)) ))
} }