Merge pull request #23 from NotAShelf/notashelf/push-orrzkvopklqm
Some checks failed
hotpath-comment.yml / Merge pull request #23 from NotAShelf/notashelf/push-orrzkvopklqm (push) Failing after 0s
Rust / build (push) Has been cancelled

treewide: more speed = more good
This commit is contained in:
raf 2025-11-18 00:11:28 +03:00 committed by GitHub
commit 5480d2d824
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 450 additions and 106 deletions

View file

@ -13,7 +13,7 @@ jobs:
comment: comment:
runs-on: ubuntu-latest runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }} if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps: steps:
- name: Download profiling results - name: Download profiling results
uses: actions/download-artifact@v4 uses: actions/download-artifact@v4
@ -21,37 +21,29 @@ jobs:
name: hotpath-results name: hotpath-results
github-token: ${{ secrets.GITHUB_TOKEN }} github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }} run-id: ${{ github.event.workflow_run.id }}
- name: Read PR number - name: Read PR number
id: pr id: pr
run: echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT run: echo "number=$(cat pr_number.txt)" >> $GITHUB_OUTPUT
- name: Setup Rust - name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1 uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install hotpath CLI - name: Install hotpath CLI
run: cargo install hotpath run: cargo install hotpath
- name: Post timing comparison comment - name: Post timing comparison comment
env: run: |
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} hotpath profile-pr \
run: | --head-metrics head-timing.json \
hotpath profile-pr \ --base-metrics base-timing.json \
--repo ${{ github.repository }} \ --github-token ${{ secrets.GITHUB_TOKEN }} \
--pr-number ${{ steps.pr.outputs.number }} \ --pr-number ${{ steps.pr.outputs.number }}
--head-json head-timing.json \
--base-json base-timing.json \ - name: Post allocation comparison comment
--mode timing \ run: |
--title "⏱️ Hotpath Timing Profile" hotpath profile-pr \
--head-metrics head-alloc.json \
- name: Post allocation comparison comment --base-metrics base-alloc.json \
env: --github-token ${{ secrets.GITHUB_TOKEN }} \
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} --pr-number ${{ steps.pr.outputs.number }}
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"

19
Cargo.lock generated
View file

@ -143,12 +143,6 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "cfg_aliases"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "chunked_transfer" name = "chunked_transfer"
version = "1.5.0" version = "1.5.0"
@ -777,7 +771,6 @@ dependencies = [
"criterion", "criterion",
"hotpath", "hotpath",
"libc", "libc",
"nix",
] ]
[[package]] [[package]]
@ -790,18 +783,6 @@ dependencies = [
"simd-adler32", "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]] [[package]]
name = "num-conv" name = "num-conv"
version = "0.1.0" version = "0.1.0"

View file

@ -14,7 +14,6 @@ path = "src/main.rs"
[dependencies] [dependencies]
hotpath = { optional = true, version = "0.6.0" } hotpath = { optional = true, version = "0.6.0" }
libc = "0.2.177" libc = "0.2.177"
nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" }
[dev-dependencies] [dev-dependencies]
criterion = "0.7" criterion = "0.7"

View file

@ -43,7 +43,7 @@ on your system: it is pretty _[fast](#benchmarks)_...
- Fast - Fast
- Really fast - Really fast
- Minimal dependencies - Minimal dependencies
- Tiny binary (~410kb) - Tiny binary (~370kb)
- Actually really fast - Actually really fast
- Cool NixOS logo (other, inferior, distros are not supported) - Cool NixOS logo (other, inferior, distros are not supported)
- Reliable detection of following info: - Reliable detection of following info:
@ -160,7 +160,8 @@ performance regressions.
> [!NOTE] > [!NOTE]
> 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,
> but this can be changed by [patching the program](#customizing).
Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be Microfetch is packaged in [nixpkgs](https://github.com/nixos/nixpkgs). It can be
installed by adding `pkgs.microfetch` to your `environment.systemPackages`. installed by adding `pkgs.microfetch` to your `environment.systemPackages`.

View file

@ -1,5 +1,6 @@
use criterion::{Criterion, criterion_group, criterion_main}; use criterion::{Criterion, criterion_group, criterion_main};
use microfetch_lib::{ use microfetch_lib::{
UtsName,
colors::print_dots, colors::print_dots,
desktop::get_desktop_info, desktop::get_desktop_info,
release::{get_os_pretty_name, get_system_info}, release::{get_os_pretty_name, get_system_info},
@ -13,7 +14,7 @@ use microfetch_lib::{
}; };
fn main_benchmark(c: &mut Criterion) { 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| { c.bench_function("user_info", |b| {
b.iter(|| get_username_and_hostname(&utsname)); b.iter(|| get_username_and_hostname(&utsname));
}); });

View file

@ -20,6 +20,7 @@ in
(fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src)) (fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src))
(s + /Cargo.lock) (s + /Cargo.lock)
(s + /Cargo.toml) (s + /Cargo.toml)
(s + /benches)
]; ];
}; };

View file

@ -1,5 +1,45 @@
pub mod colors; pub mod colors;
pub mod desktop; pub mod desktop;
pub mod release; pub mod release;
pub mod syscall;
pub mod system; pub mod system;
pub mod uptime; 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()) }
}
}

View file

@ -1,11 +1,14 @@
mod colors; mod colors;
mod desktop; mod desktop;
mod release; mod release;
mod syscall;
mod system; mod system;
mod uptime; mod uptime;
use std::io::{Write, stdout}; use std::io::{Write, stdout};
pub use microfetch_lib::UtsName;
use crate::{ use crate::{
colors::print_dots, colors::print_dots,
desktop::get_desktop_info, 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() { if Some("--version") == std::env::args().nth(1).as_deref() {
println!("Microfetch {}", env!("CARGO_PKG_VERSION")); println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
} else { } else {
let utsname = nix::sys::utsname::uname()?; let utsname = UtsName::uname()?;
let fields = Fields { let fields = Fields {
user_info: get_username_and_hostname(&utsname), user_info: get_username_and_hostname(&utsname),
os_name: get_os_pretty_name()?, os_name: get_os_pretty_name()?,

View file

@ -1,10 +1,6 @@
use std::{ use std::{fmt::Write as _, io};
fmt::Write as _,
fs::File,
io::{self, Read},
};
use nix::sys::utsname::UtsName; use crate::{UtsName, syscall::read_file_fast};
#[must_use] #[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)] #[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. /// Returns an error if `/etc/os-release` cannot be read.
#[cfg_attr(feature = "hotpath", hotpath::measure)] #[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_os_pretty_name() -> Result<String, io::Error> { pub fn get_os_pretty_name() -> Result<String, io::Error> {
// We use a stack-allocated buffer here, which seems to perform MUCH better // Fast byte-level scanning for PRETTY_NAME=
// than `BufReader`. In hindsight, I should've seen this coming. const PREFIX: &[u8] = b"PRETTY_NAME=";
let mut buffer = String::with_capacity(1024);
File::open("/etc/os-release")?.read_to_string(&mut buffer)?;
for line in buffer.lines() { let mut buffer = [0u8; 1024];
if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
if let Some(trimmed) = pretty_name // Use fast syscall-based file reading
.strip_prefix('"') let bytes_read = read_file_fast("/etc/os-release", &mut buffer)?;
.and_then(|s| s.strip_suffix('"')) 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()); &value[1..value.len() - 1]
} } else {
return Ok(pretty_name.to_owned()); 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()) Ok("Unknown".to_owned())
} }

203
src/syscall.rs Normal file
View 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)
}
}
}

View file

@ -1,13 +1,6 @@
use std::{ use std::{env, fmt::Write as _, io, mem::MaybeUninit};
env,
fmt::Write as _,
fs::File,
io::{self, Read},
};
use nix::sys::{statvfs::statvfs, utsname::UtsName}; use crate::{UtsName, colors::COLORS, syscall::read_file_fast};
use crate::colors::COLORS;
#[must_use] #[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)] #[cfg_attr(feature = "hotpath", hotpath::measure)]
@ -57,10 +50,17 @@ pub fn get_shell() -> String {
#[cfg_attr(feature = "hotpath", hotpath::measure)] #[cfg_attr(feature = "hotpath", hotpath::measure)]
#[allow(clippy::cast_precision_loss)] #[allow(clippy::cast_precision_loss)]
pub fn get_root_disk_usage() -> Result<String, io::Error> { pub fn get_root_disk_usage() -> Result<String, io::Error> {
let vfs = statvfs("/")?; let mut vfs = MaybeUninit::uninit();
let block_size = vfs.block_size() as u64; let path = b"/\0";
let total_blocks = vfs.blocks();
let available_blocks = vfs.blocks_available(); 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 total_size = block_size * total_blocks;
let used_size = total_size - (block_size * available_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) 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. /// Gets the system memory usage information.
/// ///
/// # Errors /// # Errors
@ -90,30 +104,54 @@ pub fn get_root_disk_usage() -> Result<String, io::Error> {
pub fn get_memory_usage() -> Result<String, io::Error> { pub fn get_memory_usage() -> Result<String, io::Error> {
#[cfg_attr(feature = "hotpath", hotpath::measure)] #[cfg_attr(feature = "hotpath", hotpath::measure)]
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 = 0u64;
let mut available_memory_kb = 0.0; let mut available_memory_kb = 0u64;
let mut meminfo = String::with_capacity(2048); 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() { // Fast scanning for MemTotal and MemAvailable
let mut split = line.split_whitespace(); let mut offset = 0;
match split.next().unwrap_or_default() { let mut found_total = false;
"MemTotal:" => { let mut found_available = false;
total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
}, while offset < meminfo.len() && (!found_total || !found_available) {
"MemAvailable:" => { let remaining = &meminfo[offset..];
available_memory_kb =
split.next().unwrap_or("0").parse().unwrap_or(0.0); // Find newline or end
// MemTotal comes before MemAvailable, stop parsing let line_end = remaining
break; .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; #[allow(clippy::cast_precision_loss)]
let available_memory_gb = available_memory_kb / 1024.0 / 1024.0; 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; let used_memory_gb = total_memory_gb - available_memory_gb;
Ok((used_memory_gb, total_memory_gb)) Ok((used_memory_gb, total_memory_gb))

View file

@ -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. /// Gets the current system uptime.
/// ///
@ -9,7 +71,7 @@ use std::{fmt::Write, io, mem::MaybeUninit};
pub fn get_current() -> Result<String, io::Error> { pub fn get_current() -> Result<String, io::Error> {
let uptime_seconds = { let uptime_seconds = {
let mut info = MaybeUninit::uninit(); 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()); return Err(io::Error::last_os_error());
} }
#[allow(clippy::cast_sign_loss)] #[allow(clippy::cast_sign_loss)]
@ -23,22 +85,24 @@ pub fn get_current() -> Result<String, io::Error> {
let minutes = (uptime_seconds / 60) % 60; let minutes = (uptime_seconds / 60) % 60;
let mut result = String::with_capacity(32); let mut result = String::with_capacity(32);
let mut buf = [0u8; 20]; // Enough for u64::MAX
if days > 0 { if days > 0 {
let _ = write!(result, "{days}"); result.push_str(itoa(days, &mut buf));
result.push_str(if days == 1 { " day" } else { " days" }); result.push_str(if days == 1 { " day" } else { " days" });
} }
if hours > 0 { if hours > 0 {
if !result.is_empty() { if !result.is_empty() {
result.push_str(", "); result.push_str(", ");
} }
let _ = write!(result, "{hours}"); result.push_str(itoa(hours, &mut buf));
result.push_str(if hours == 1 { " hour" } else { " hours" }); result.push_str(if hours == 1 { " hour" } else { " hours" });
} }
if minutes > 0 { if minutes > 0 {
if !result.is_empty() { if !result.is_empty() {
result.push_str(", "); result.push_str(", ");
} }
let _ = write!(result, "{minutes}"); result.push_str(itoa(minutes, &mut buf));
result.push_str(if minutes == 1 { " minute" } else { " minutes" }); result.push_str(if minutes == 1 { " minute" } else { " minutes" });
} }
if result.is_empty() { if result.is_empty() {