diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml
deleted file mode 100644
index 8f83d00..0000000
--- a/.github/workflows/hotpath-comment.yml
+++ /dev/null
@@ -1,49 +0,0 @@
-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
- 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 }}
diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml
deleted file mode 100644
index b367ca2..0000000
--- a/.github/workflows/hotpath-profile.yml
+++ /dev/null
@@ -1,63 +0,0 @@
-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
diff --git a/Cargo.lock b/Cargo.lock
index 0147746..c7bf6b7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -143,6 +143,12 @@ 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"
@@ -684,7 +690,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
- "windows-sys 0.61.2",
+ "windows-sys 0.59.0",
]
[[package]]
@@ -726,9 +732,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.177"
+version = "0.2.175"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
+checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
[[package]]
name = "libredox"
@@ -766,11 +772,12 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "microfetch"
-version = "0.4.11"
+version = "0.4.10"
dependencies = [
"criterion",
"hotpath",
"libc",
+ "nix",
]
[[package]]
@@ -783,6 +790,18 @@ 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 e95ef5c..c97b58b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "microfetch"
-version = "0.4.11"
+version = "0.4.10"
edition = "2024"
[lib]
@@ -12,8 +12,9 @@ name = "microfetch"
path = "src/main.rs"
[dependencies]
-hotpath = { optional = true, version = "0.6.0" }
-libc = "0.2.177"
+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"
diff --git a/README.md b/README.md
index a40e444..28d6338 100644
--- a/README.md
+++ b/README.md
@@ -1,34 +1,17 @@
-
-
-
+Microfetch
-## 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)_...
+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)...
[!NOTE]
> You will need a Nerdfonts patched font installed, and for your terminal
-> emulator to support said font. Microfetch uses nerdfonts glyphs by default,
-> but this can be changed by [patching the program](#customizing).
+> emulator to support said font. Microfetch uses nerdfonts glyphs by default.
Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be
installed by adding `pkgs.microfetch` to your `environment.systemPackages`.
diff --git a/benches/benchmark.rs b/benches/benchmark.rs
index 8d62082..33dc1b9 100644
--- a/benches/benchmark.rs
+++ b/benches/benchmark.rs
@@ -1,6 +1,5 @@
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},
@@ -14,7 +13,7 @@ use microfetch_lib::{
};
fn main_benchmark(c: &mut Criterion) {
- let utsname = UtsName::uname().expect("Failed to get uname");
+ let utsname = nix::sys::utsname::uname().expect("lol");
c.bench_function("user_info", |b| {
b.iter(|| get_username_and_hostname(&utsname));
});
diff --git a/nix/package.nix b/nix/package.nix
index dca3cc2..5bb387c 100644
--- a/nix/package.nix
+++ b/nix/package.nix
@@ -20,7 +20,6 @@ in
(fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src))
(s + /Cargo.lock)
(s + /Cargo.toml)
- (s + /benches)
];
};
diff --git a/src/colors.rs b/src/colors.rs
index 07ab9bd..53b12f6 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,37 +45,14 @@ pub static COLORS: LazyLock = LazyLock::new(|| {
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn print_dots() -> String {
- // 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
+ 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 561be03..7d3733a 100644
--- a/src/desktop.rs
+++ b/src/desktop.rs
@@ -1,37 +1,30 @@
-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 = std::env::var("XDG_SESSION_TYPE");
+ let display_backend_result = 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 backend_str = match display_backend {
- Err(_) => "Unknown",
- Ok(ref s) if s.is_empty() => "Unknown",
- Ok(ref s) => s.as_str(),
- };
-
- // 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()..]);
+ // 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();
}
- result.push(')');
- result
+ // 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_owned();
+
+ format!("{desktop_env} ({display_backend})")
}
diff --git a/src/lib.rs b/src/lib.rs
index 8c8a6ba..4ba33f8 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -1,45 +1,5 @@
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 4e664bb..5ba7810 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,14 +1,11 @@
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,
@@ -27,7 +24,7 @@ fn main() -> Result<(), Box> {
if Some("--version") == std::env::args().nth(1).as_deref() {
println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
} else {
- let utsname = UtsName::uname()?;
+ let utsname = nix::sys::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 8d3232e..2f1338c 100644
--- a/src/release.rs
+++ b/src/release.rs
@@ -1,69 +1,37 @@
-use std::{fmt::Write as _, io};
+use std::{
+ fs::File,
+ io::{self, BufRead, BufReader},
+};
-use crate::{UtsName, syscall::read_file_fast};
+use nix::sys::utsname::UtsName;
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_system_info(utsname: &UtsName) -> String {
- 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
+ format!(
+ "{} {} ({})",
+ utsname.sysname().to_str().unwrap_or("Unknown"),
+ utsname.release().to_str().unwrap_or("Unknown"),
+ utsname.machine().to_str().unwrap_or("Unknown")
+ )
}
-/// 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 {
- // Fast byte-level scanning for PRETTY_NAME=
- const PREFIX: &[u8] = b"PRETTY_NAME=";
+ let file = File::open("/etc/os-release")?;
+ let reader = BufReader::new(file);
- 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'"'
+ 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('"'))
{
- &value[1..value.len() - 1]
- } else {
- value
- };
-
- // Convert to String - should be valid UTF-8
- return Ok(String::from_utf8_lossy(trimmed).into_owned());
+ return Ok(trimmed.to_owned());
+ }
+ return Ok(pretty_name.to_owned());
}
-
- offset += line_end + 1;
}
-
Ok("Unknown".to_owned())
}
diff --git a/src/syscall.rs b/src/syscall.rs
deleted file mode 100644
index 2776fe7..0000000
--- a/src/syscall.rs
+++ /dev/null
@@ -1,203 +0,0 @@
-//! 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 24406ec..f90c912 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -1,31 +1,29 @@
-use std::{env, fmt::Write as _, io, mem::MaybeUninit};
+use std::{
+ env,
+ fs::File,
+ io::{self, Read},
+};
-use crate::{UtsName, colors::COLORS, syscall::read_file_fast};
+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.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
+ 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]
@@ -33,34 +31,16 @@ 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());
-
- // Find last '/' and get the part after it, avoiding allocation
- shell_path
- .rsplit('/')
- .next()
- .unwrap_or("unknown_shell")
- .to_owned()
+ let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
+ shell_name.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 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 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);
@@ -69,107 +49,53 @@ 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;
- let mut result = String::with_capacity(64);
- write!(
- result,
+ Ok(format!(
"{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
cyan = COLORS.cyan,
reset = COLORS.reset,
- )
- .unwrap();
-
- 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
-///
-/// 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)]
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 total_memory_kb = 0.0;
+ let mut available_memory_kb = 0.0;
+ let mut meminfo = String::with_capacity(2048);
- // Use fast syscall-based file reading
- let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)?;
- let meminfo = &buffer[..bytes_read];
+ File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?;
- // 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;
+ 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;
+ },
+ _ => (),
}
-
- offset += line_end + 1;
}
- #[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 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()?;
- #[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);
- write!(
- result,
+ Ok(format!(
"{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 98c9207..d5253d6 100644
--- a/src/uptime.rs
+++ b/src/uptime.rs
@@ -1,83 +1,13 @@
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.
-///
-/// # 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 = {
let mut info = MaybeUninit::uninit();
- if unsafe { sys_sysinfo(info.as_mut_ptr()) } != 0 {
+ 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
- }
+ unsafe { info.assume_init().uptime as u64 }
};
let days = uptime_seconds / 86400;
@@ -85,24 +15,22 @@ 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 {
- result.push_str(itoa(days, &mut buf));
+ 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(itoa(hours, &mut buf));
+ 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(itoa(minutes, &mut buf));
+ result.push_str(&minutes.to_string());
result.push_str(if minutes == 1 { " minute" } else { " minutes" });
}
if result.is_empty() {