mirror of
https://github.com/NotAShelf/microfetch.git
synced 2025-11-25 16:52:50 +00:00
Compare commits
14 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
5480d2d824 |
|||
|
353b78e688 |
|||
|
6f8d1ffa83 |
|||
|
07afedd0cc |
|||
|
75132ff172 |
|||
|
f8a0070986 |
|||
|
b24e720dd8 |
|||
|
ca76a6e1bd |
|||
|
00159d6454 |
|||
|
11a726428b |
|||
|
789ece866b |
|||
|
f4f3385ff7 |
|||
|
2ad765ef98 |
|||
|
325ec69024 |
15 changed files with 791 additions and 173 deletions
49
.github/workflows/hotpath-comment.yml
vendored
Normal file
49
.github/workflows/hotpath-comment.yml
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
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 }}
|
||||
63
.github/workflows/hotpath-profile.yml
vendored
Normal file
63
.github/workflows/hotpath-profile.yml
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
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
|
||||
27
Cargo.lock
generated
27
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"
|
||||
|
|
@ -690,7 +684,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
|
|||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
"windows-sys 0.59.0",
|
||||
"windows-sys 0.61.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
@ -732,9 +726,9 @@ checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
|
|||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.175"
|
||||
version = "0.2.177"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a82ae493e598baaea5209805c49bbf2ea7de956d50d7da0da1164f9c6d28543"
|
||||
checksum = "2874a2af47a2325c2001a6e6fad9b16a53b802102b528163885171cf92b15976"
|
||||
|
||||
[[package]]
|
||||
name = "libredox"
|
||||
|
|
@ -772,12 +766,11 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
|
|||
|
||||
[[package]]
|
||||
name = "microfetch"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
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"
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "microfetch"
|
||||
version = "0.4.10"
|
||||
version = "0.4.11"
|
||||
edition = "2024"
|
||||
|
||||
[lib]
|
||||
|
|
@ -12,9 +12,8 @@ name = "microfetch"
|
|||
path = "src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
hotpath = { optional = true, version = "0.6" }
|
||||
libc = "0.2.175"
|
||||
nix = { default-features = false, features = [ "fs", "hostname", "feature" ], version = "0.30.1" }
|
||||
hotpath = { optional = true, version = "0.6.0" }
|
||||
libc = "0.2.177"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.7"
|
||||
|
|
|
|||
144
README.md
144
README.md
|
|
@ -1,17 +1,34 @@
|
|||
<!-- markdownlint-disable MD013 MD033 MD041 -->
|
||||
|
||||
<div align="center">
|
||||
<img src="https://deps.rs/repo/github/notashelf/microfetch/status.svg" alt="https://deps.rs/repo/github/notashelf/microfetch">
|
||||
<!-- <img src="https://img.shields.io/github/v/release/notashelf/microfetch?display_name=tag&color=DEA584"> -->
|
||||
<img src="https://img.shields.io/github/stars/notashelf/microfetch?label=stars&color=DEA584">
|
||||
<img src="https://img.shields.io/github/stars/notashelf/microfetch?label=stars&color=DEA584" alt="stars">
|
||||
</div>
|
||||
|
||||
<h1 align="center">Microfetch</h1>
|
||||
<div id="doc-begin" align="center">
|
||||
<h1 id="header">
|
||||
Microfetch
|
||||
</h1>
|
||||
<p>
|
||||
Microscopic fetch tool in Rust, for NixOS systems, with special emphasis on speed
|
||||
</p>
|
||||
<br/>
|
||||
<a href="#synopsis">Synopsis</a><br/>
|
||||
<a href="#features">Features</a> | <a href="#motivation">Motivation</a><br/>
|
||||
<a href="#installation">Installation</a>
|
||||
<br/>
|
||||
</div>
|
||||
|
||||
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)...
|
||||
## 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)_...
|
||||
|
||||
<p align="center">
|
||||
<img
|
||||
|
|
@ -26,6 +43,7 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
|||
- Fast
|
||||
- Really fast
|
||||
- Minimal dependencies
|
||||
- Tiny binary (~370kb)
|
||||
- Actually really fast
|
||||
- Cool NixOS logo (other, inferior, distros are not supported)
|
||||
- Reliable detection of following info:
|
||||
|
|
@ -44,39 +62,64 @@ than welcome to use it on your system: it's pretty [fast](#benchmarks)...
|
|||
|
||||
## 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, 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.
|
||||
Fastfetch, as its name probably hinted, is a very fast fetch tool written in C.
|
||||
However, I am not interested in _any_ of its additional features, and I'm not
|
||||
interested in its configuration options. Sure I can _configure_ it when I
|
||||
dislike the defaults, but how often would I really change the configuration...
|
||||
|
||||
I cannot re-iterate it enough, Microfetch is _annoyingly fast_.
|
||||
Microfetch is my response to this problem. It is an _even faster_ fetch tool
|
||||
that I would've written in Bash and put in my `~/.bashrc` but is _actually_
|
||||
incredibly fast because it opts out of all the customization options provided by
|
||||
tools such as Fastfetch. Ultimately, it's a small, opinionated binary with a
|
||||
nice size that doesn't bother me, and incredible speed. Customization? No thank
|
||||
you. I cannot re-iterate it enough, Microfetch is _annoyingly fast_.
|
||||
|
||||
The project is written in Rust, which comes at the cost of "bloated" dependency
|
||||
trees and the increased build times, but we make an extended effort to keep the
|
||||
dependencies minimal and build times managable. The latter is also very easily
|
||||
mitigated with Nix's binary cache systems. Since Microfetch is already in
|
||||
Nixpkgs, you are recommended to use it to utilize the binary cache properly. The
|
||||
usage of Rust _is_ nice, however, since it provides us with incredible tooling
|
||||
and a very powerful language that allows for Microfetch to be as fast as
|
||||
possible. Sure C could've been used here as well, but do you think I hate
|
||||
myself? [^1]
|
||||
|
||||
[^1]: Okay, maybe a little bit. One of the future goals of Microfetch is to
|
||||
defer to inline Assembly for the costliest functions, but that's for a
|
||||
future date and until I do that I can pretend to be sane.
|
||||
|
||||
## Benchmarks
|
||||
|
||||
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.
|
||||
Below are the benchmarks that I've used to back up my claims of Microfetch's
|
||||
speed. It is fast, it is _very_ fast and that is the point of its existence. It
|
||||
_could_ be faster, and it will be. Eventually.
|
||||
|
||||
| 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 |
|
||||
At this point in time, the performance may be sometimes influenced by
|
||||
hardware-specific race conditions or even your kernel configuration. Which means
|
||||
that Microfetch's speed may (at times) depend on your hardware setup. However,
|
||||
even with the worst possible hardware I could find in my house, I've achieved a
|
||||
nice less-than-1ms invocation time. Which is pretty good. While Microfetch
|
||||
_could_ be made faster, we're in the territory of environmental bottlenecks
|
||||
given how little Microfetch actually allocates.
|
||||
|
||||
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
|
||||
Below are the actual benchmarks with Hyperfine measured on my Desktop system.
|
||||
The benchmarks were performed under medium system load, and may not be the same
|
||||
on your system. Please _also_ 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 [µs] | Min [µs] | Max [µs] | Relative | Written by raf? |
|
||||
| :----------- | ----------------: | -------: | -------: | -------------: | --------------: |
|
||||
| `microfetch` | 604.4 ± 64.2 | 516.0 | 1184.6 | 1.00 | Yes |
|
||||
| `fastfetch` | 140836.6 ± 1258.6 | 139204.7 | 143299.4 | 233.00 ± 24.82 | No |
|
||||
| `pfetch` | 177036.6 ± 1614.3 | 174199.3 | 180830.2 | 292.89 ± 31.20 | No |
|
||||
| `neofetch` | 406309.9 ± 1810.0 | 402757.3 | 409526.3 | 672.20 ± 71.40 | No |
|
||||
| `nitch` | 127743.7 ± 1391.7 | 123933.5 | 130451.2 | 211.34 ± 22.55 | No |
|
||||
| `macchina` | 13603.7 ± 339.7 | 12642.9 | 14701.4 | 22.51 ± 2.45 | No |
|
||||
|
||||
The point stands that Microfetch is significantly faster than every other fetch
|
||||
tool I have tried. This is to be expected, of course, since Microfetch is
|
||||
designed _explicitly_ for speed and makes some tradeoffs to achieve it's
|
||||
signature speed.
|
||||
|
||||
### Benchmarking Individual Functions
|
||||
|
||||
|
|
@ -87,11 +130,38 @@ To benchmark individual functions, [Criterion.rs] is used. See Criterion's
|
|||
[Getting Started Guide] for details or just run `cargo bench` to benchmark all
|
||||
features of Microfetch.
|
||||
|
||||
### Profiling Allocations and Timing
|
||||
|
||||
[Hotpath]: https://github.com/pawurb/hotpath
|
||||
|
||||
Microfetch uses [Hotpath] for profiling function execution timing and heap
|
||||
allocations. This helps identify performance bottlenecks and track optimization
|
||||
progress. It is so effective that thanks to Hotpath, Microfetch has seen a 60%
|
||||
reduction in the number of allocations.
|
||||
|
||||
To profile timing:
|
||||
|
||||
```bash
|
||||
HOTPATH_JSON=true cargo run --features=hotpath
|
||||
```
|
||||
|
||||
To profile allocations:
|
||||
|
||||
```bash
|
||||
HOTPATH_JSON=true cargo run --features=hotpath,hotpath-alloc-count-total
|
||||
```
|
||||
|
||||
The JSON output can be analyzed with the `hotpath` CLI tool for detailed
|
||||
performance metrics. On pull requests, GitHub Actions automatically profiles
|
||||
both timing and allocations, posting comparison comments to help catch
|
||||
performance regressions.
|
||||
|
||||
## Installation
|
||||
|
||||
> [!NOTE]
|
||||
> 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
|
||||
installed by adding `pkgs.microfetch` to your `environment.systemPackages`.
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
});
|
||||
|
|
|
|||
|
|
@ -20,6 +20,7 @@ in
|
|||
(fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src))
|
||||
(s + /Cargo.lock)
|
||||
(s + /Cargo.toml)
|
||||
(s + /benches)
|
||||
];
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ impl Colors {
|
|||
}
|
||||
|
||||
pub static COLORS: LazyLock<Colors> = 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,14 +45,37 @@ pub static COLORS: LazyLock<Colors> = LazyLock::new(|| {
|
|||
#[must_use]
|
||||
#[cfg_attr(feature = "hotpath", hotpath::measure)]
|
||||
pub fn print_dots() -> String {
|
||||
format!(
|
||||
"{} {} {} {} {} {} {}",
|
||||
COLORS.blue,
|
||||
COLORS.cyan,
|
||||
COLORS.green,
|
||||
COLORS.yellow,
|
||||
COLORS.red,
|
||||
COLORS.magenta,
|
||||
COLORS.reset,
|
||||
)
|
||||
// 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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,30 +1,37 @@
|
|||
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_result = std::env::var("XDG_SESSION_TYPE");
|
||||
let display_backend = std::env::var("XDG_SESSION_TYPE");
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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(),
|
||||
let desktop_str = match desktop_env {
|
||||
Err(_) => "Unknown",
|
||||
Ok(ref s) if s.starts_with("none+") => &s[5..],
|
||||
Ok(ref s) => s.as_str(),
|
||||
};
|
||||
|
||||
// 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();
|
||||
let backend_str = match display_backend {
|
||||
Err(_) => "Unknown",
|
||||
Ok(ref s) if s.is_empty() => "Unknown",
|
||||
Ok(ref s) => s.as_str(),
|
||||
};
|
||||
|
||||
format!("{desktop_env} ({display_backend})")
|
||||
// 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()..]);
|
||||
}
|
||||
|
||||
result.push(')');
|
||||
result
|
||||
}
|
||||
|
|
|
|||
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,37 +1,69 @@
|
|||
use std::{
|
||||
fs::File,
|
||||
io::{self, BufRead, BufReader},
|
||||
};
|
||||
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)]
|
||||
pub fn get_system_info(utsname: &UtsName) -> String {
|
||||
format!(
|
||||
"{} {} ({})",
|
||||
utsname.sysname().to_str().unwrap_or("Unknown"),
|
||||
utsname.release().to_str().unwrap_or("Unknown"),
|
||||
utsname.machine().to_str().unwrap_or("Unknown")
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
/// 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<String, io::Error> {
|
||||
let file = File::open("/etc/os-release")?;
|
||||
let reader = BufReader::new(file);
|
||||
// Fast byte-level scanning for PRETTY_NAME=
|
||||
const PREFIX: &[u8] = b"PRETTY_NAME=";
|
||||
|
||||
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('"'))
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
172
src/system.rs
172
src/system.rs
|
|
@ -1,29 +1,31 @@
|
|||
use std::{
|
||||
env,
|
||||
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)]
|
||||
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")
|
||||
.to_owned();
|
||||
format!(
|
||||
"{yellow}{username}{red}@{green}{hostname}{reset}",
|
||||
yellow = COLORS.yellow,
|
||||
red = COLORS.red,
|
||||
green = COLORS.green,
|
||||
reset = COLORS.reset,
|
||||
)
|
||||
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
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
|
|
@ -31,16 +33,34 @@ 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());
|
||||
let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
|
||||
shell_name.to_owned()
|
||||
|
||||
// Find last '/' and get the part after it, avoiding allocation
|
||||
shell_path
|
||||
.rsplit('/')
|
||||
.next()
|
||||
.unwrap_or("unknown_shell")
|
||||
.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<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);
|
||||
|
|
@ -49,53 +69,107 @@ pub fn get_root_disk_usage() -> Result<String, io::Error> {
|
|||
let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
|
||||
let usage = (used_size / total_size) * 100.0;
|
||||
|
||||
Ok(format!(
|
||||
let mut result = String::with_capacity(64);
|
||||
write!(
|
||||
result,
|
||||
"{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<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;
|
||||
}
|
||||
|
||||
offset += line_end + 1;
|
||||
}
|
||||
|
||||
let total_memory_gb = total_memory_kb / 1024.0 / 1024.0;
|
||||
let available_memory_gb = available_memory_kb / 1024.0 / 1024.0;
|
||||
#[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))
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
Ok(format!(
|
||||
let mut result = String::with_capacity(64);
|
||||
write!(
|
||||
result,
|
||||
"{used_memory:.2} GiB / {total_memory:.2} GiB \
|
||||
({cyan}{percentage_used}%{reset})",
|
||||
cyan = COLORS.cyan,
|
||||
reset = COLORS.reset,
|
||||
))
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
Ok(result)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,83 @@
|
|||
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<String, io::Error> {
|
||||
let uptime_seconds = {
|
||||
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());
|
||||
}
|
||||
unsafe { info.assume_init().uptime as u64 }
|
||||
#[allow(clippy::cast_sign_loss)]
|
||||
unsafe {
|
||||
info.assume_init().uptime as u64
|
||||
}
|
||||
};
|
||||
|
||||
let days = uptime_seconds / 86400;
|
||||
|
|
@ -15,22 +85,24 @@ pub fn get_current() -> Result<String, io::Error> {
|
|||
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(&days.to_string());
|
||||
result.push_str(itoa(days, &mut buf));
|
||||
result.push_str(if days == 1 { " day" } else { " days" });
|
||||
}
|
||||
if hours > 0 {
|
||||
if !result.is_empty() {
|
||||
result.push_str(", ");
|
||||
}
|
||||
result.push_str(&hours.to_string());
|
||||
result.push_str(itoa(hours, &mut buf));
|
||||
result.push_str(if hours == 1 { " hour" } else { " hours" });
|
||||
}
|
||||
if minutes > 0 {
|
||||
if !result.is_empty() {
|
||||
result.push_str(", ");
|
||||
}
|
||||
result.push_str(&minutes.to_string());
|
||||
result.push_str(itoa(minutes, &mut buf));
|
||||
result.push_str(if minutes == 1 { " minute" } else { " minutes" });
|
||||
}
|
||||
if result.is_empty() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue