mirror of
https://github.com/NotAShelf/microfetch.git
synced 2025-11-25 16:52:50 +00:00
treewide: remove nix dependency, add custom syscall wrappers & UtsName
Signed-off-by: NotAShelf <raf@notashelf.dev> Change-Id: Ib880f4bafe9d3bbc944af4b9125256366a6a6964
This commit is contained in:
parent
f8a0070986
commit
75132ff172
8 changed files with 358 additions and 72 deletions
19
Cargo.lock
generated
19
Cargo.lock
generated
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
|
|
|
|||
40
src/lib.rs
40
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<Self, std::io::Error> {
|
||||
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()) }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<dyn std::error::Error>> {
|
|||
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()?,
|
||||
|
|
|
|||
|
|
@ -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<String, io::Error> {
|
||||
// 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())
|
||||
}
|
||||
|
|
|
|||
203
src/syscall.rs
Normal file
203
src/syscall.rs
Normal file
|
|
@ -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<usize> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
102
src/system.rs
102
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<String, io::Error> {
|
||||
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<String, io::Error> {
|
|||
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<String, io::Error> {
|
|||
pub fn get_memory_usage() -> Result<String, io::Error> {
|
||||
#[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;
|
||||
}
|
||||
|
||||
let total_memory_gb = total_memory_kb / 1024.0 / 1024.0;
|
||||
let available_memory_gb = available_memory_kb / 1024.0 / 1024.0;
|
||||
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 used_memory_gb = total_memory_gb - available_memory_gb;
|
||||
|
||||
Ok((used_memory_gb, total_memory_gb))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue