Compare commits

..

No commits in common. "main" and "v0.4.9" have entirely different histories.

24 changed files with 384 additions and 1820 deletions

View file

@ -1,4 +0,0 @@
# https://github.com/rui314/mold?tab=readme-ov-file#how-to-use
[target.'cfg(target_os = "linux")']
linker = "clang"
rustflags = ["-C", "link-arg=-fuse-ld=mold"]

View file

@ -1,14 +0,0 @@
version: 2
updates:
# Update Cargo deps
- package-ecosystem: cargo
directory: "/"
schedule:
interval: "weekly"
# Update used workflows
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily

View file

@ -1,62 +0,0 @@
name: hotpath-comment
on:
workflow_run:
workflows: ["hotpath-profile"]
types:
- completed
permissions:
contents: read
pull-requests: write
jobs:
comment:
runs-on: ubuntu-latest
if: ${{ github.event.workflow_run.conclusion == 'success' }}
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Download profiling results
uses: actions/download-artifact@v7
with:
name: hotpath-results
path: /tmp/
github-token: ${{ secrets.GITHUB_TOKEN }}
run-id: ${{ github.event.workflow_run.id }}
- name: Setup Rust
uses: actions-rust-lang/setup-rust-toolchain@v1
- name: Install hotpath CLI
run: cargo install hotpath
- name: Post timing comparison comment
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
HEAD_METRICS=$(cat /tmp/head_timing.json)
BASE_METRICS=$(cat /tmp/base_timing.json)
PR_NUMBER=$(cat /tmp/pr_number.txt)
hotpath profile-pr \
--head-metrics "$HEAD_METRICS" \
--base-metrics "$BASE_METRICS" \
--github-token "$GH_TOKEN" \
--pr-number "$PR_NUMBER"
- name: Post allocation comparison comment
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
HEAD_METRICS=$(cat /tmp/head_alloc.json)
BASE_METRICS=$(cat /tmp/base_alloc.json)
PR_NUMBER=$(cat /tmp/pr_number.txt)
hotpath profile-pr \
--head-metrics "$HEAD_METRICS" \
--base-metrics "$BASE_METRICS" \
--github-token "$GH_TOKEN" \
--pr-number "$PR_NUMBER"

View file

@ -1,65 +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@v6
with:
fetch-depth: 0
- 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"' > /tmp/head_timing.json
- name: Run allocation profiling on HEAD
env:
HOTPATH_JSON: "true"
run: |
cargo run --features='hotpath,hotpath-alloc' 2>&1 | grep '^{"hotpath_profiling_mode"' > /tmp/head_alloc.json
- name: Checkout base branch
run: |
git checkout ${{ 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"' > /tmp/base_timing.json || echo '{}' > /tmp/base_timing.json
- name: Run allocation profiling on base
env:
HOTPATH_JSON: "true"
run: |
cargo run --features='hotpath,hotpath-alloc' 2>&1 | grep '^{"hotpath_profiling_mode"' > /tmp/base_alloc.json || echo '{}' > /tmp/base_alloc.json
- name: Save PR number
run: |
echo '${{ github.event.pull_request.number }}' > /tmp/pr_number.txt
- name: Upload profiling results
uses: actions/upload-artifact@v6
with:
name: hotpath-results
path: |
/tmp/head_timing.json
/tmp/head_alloc.json
/tmp/base_timing.json
/tmp/base_alloc.json
/tmp/pr_number.txt
retention-days: 1

View file

@ -10,45 +10,12 @@ env:
CARGO_TERM_COLOR: always CARGO_TERM_COLOR: always
jobs: jobs:
test: build:
name: Test on ${{ matrix.target }}
runs-on: ${{ matrix.os }} runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
target: aarch64-unknown-linux-gnu
steps: steps:
- name: "Checkout" - name: "Checkout"
uses: actions/checkout@v6 uses: actions/checkout@v4
- name: "Build with Cargo"
- name: "Setup Rust toolchain" run: cargo build --verbose
uses: actions-rust-lang/setup-rust-toolchain@v1
with:
target: ${{ matrix.target }}
- name: "Install cross-compilation tools"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
sudo apt-get update
sudo apt-get install -y gcc-aarch64-linux-gnu
- name: "Configure linker for aarch64"
if: matrix.target == 'aarch64-unknown-linux-gnu'
run: |
mkdir -p .cargo
cat >> .cargo/config.toml << EOF
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
EOF
- name: "Build"
run: cargo build --verbose --target ${{ matrix.target }}
- name: "Run tests"
if: matrix.target == 'x86_64-unknown-linux-gnu'
run: cargo test --verbose --target ${{ matrix.target }}

1
.gitignore vendored
View file

@ -1,3 +1,2 @@
/target /target
result* result*
/.direnv

View file

@ -1,26 +0,0 @@
condense_wildcard_suffixes = true
doc_comment_code_block_width = 80
edition = "2024" # Keep in sync with Cargo.toml.
enum_discrim_align_threshold = 60
force_explicit_abi = false
force_multiline_blocks = true
format_code_in_doc_comments = true
format_macro_matchers = true
format_strings = true
group_imports = "StdExternalCrate"
hex_literal_case = "Upper"
imports_granularity = "Crate"
imports_layout = "HorizontalVertical"
inline_attribute_width = 60
match_block_trailing_comma = true
max_width = 80
newline_style = "Unix"
normalize_comments = true
normalize_doc_attributes = true
overflow_delimited_expr = true
struct_field_align_threshold = 60
tab_spaces = 2
unstable_features = true
use_field_init_shorthand = true
use_try_shorthand = true
wrap_comments = true

View file

@ -1,13 +0,0 @@
[formatting]
align_entries = true
column_width = 100
compact_arrays = false
reorder_inline_tables = true
reorder_keys = true
[[rule]]
include = [ "**/Cargo.toml" ]
keys = [ "package" ]
[rule.formatting]
reorder_keys = false

578
Cargo.lock generated
View file

@ -11,95 +11,24 @@ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "alloca"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
dependencies = [
"cc",
]
[[package]] [[package]]
name = "anes" name = "anes"
version = "0.1.6" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299"
[[package]]
name = "anstream"
version = "0.6.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43d5b281e737544384e969a5ccad3f1cdd24b48086a0fc1b2a5262a26b8f4f4a"
dependencies = [
"anstyle",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"is_terminal_polyfill",
"utf8parse",
]
[[package]] [[package]]
name = "anstyle" name = "anstyle"
version = "1.0.10" version = "1.0.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
[[package]]
name = "anstyle-parse"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e7644824f0aa2c7b9384579234ef10eb7efb6a0deb83f9630a49594dd9c15c2"
dependencies = [
"utf8parse",
]
[[package]]
name = "anstyle-query"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc"
dependencies = [
"windows-sys 0.61.2",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d"
dependencies = [
"anstyle",
"once_cell_polyfill",
"windows-sys 0.61.2",
]
[[package]]
name = "arc-swap"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457"
[[package]]
name = "ascii"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d92bec98840b8f03a5ff5413de5293bfcd8bf96467cf5452609f939ec6f5de16"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.4.0" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]] [[package]]
name = "bitflags" name = "bitflags"
version = "2.9.0" version = "2.9.0"
@ -112,28 +41,12 @@ version = "3.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
[[package]]
name = "byteorder"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
[[package]] [[package]]
name = "cast" name = "cast"
version = "0.3.0" version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
dependencies = [
"find-msvc-tools",
"shlex",
]
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "1.0.0"
@ -141,10 +54,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chunked_transfer" name = "cfg_aliases"
version = "1.5.0" version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901" checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
[[package]] [[package]]
name = "ciborium" name = "ciborium"
@ -180,7 +93,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944" checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
dependencies = [ dependencies = [
"clap_builder", "clap_builder",
"clap_derive",
] ]
[[package]] [[package]]
@ -189,22 +101,8 @@ version = "4.5.35"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9" checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
dependencies = [ dependencies = [
"anstream",
"anstyle", "anstyle",
"clap_lex", "clap_lex",
"strsim",
]
[[package]]
name = "clap_derive"
version = "4.5.32"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "09176aae279615badda0765c0c0b3f6ed53f4709118af73cf4655d85d1530cd7"
dependencies = [
"heck",
"proc-macro2",
"quote",
"syn",
] ]
[[package]] [[package]]
@ -213,37 +111,20 @@ version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6"
[[package]]
name = "colorchoice"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
[[package]]
name = "colored"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fde0e0ec90c9dfb3b4b1a0891a7dcd0e2bffde2f7efed5fe7c9bb00e5bfb915e"
dependencies = [
"windows-sys 0.59.0",
]
[[package]] [[package]]
name = "criterion" name = "criterion"
version = "0.8.1" version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf" checksum = "3bf7af66b0989381bd0be551bd7cc91912a655a58c6918420c9527b1fd8b4679"
dependencies = [ dependencies = [
"alloca",
"anes", "anes",
"cast", "cast",
"ciborium", "ciborium",
"clap", "clap",
"criterion-plot", "criterion-plot",
"itertools", "itertools 0.13.0",
"num-traits", "num-traits",
"oorandom", "oorandom",
"page_size",
"plotters", "plotters",
"rayon", "rayon",
"regex", "regex",
@ -255,21 +136,12 @@ dependencies = [
[[package]] [[package]]
name = "criterion-plot" name = "criterion-plot"
version = "0.8.1" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1"
dependencies = [ dependencies = [
"cast", "cast",
"itertools", "itertools 0.10.5",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
dependencies = [
"crossbeam-utils",
] ]
[[package]] [[package]]
@ -303,110 +175,12 @@ version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929" checksum = "43da5946c66ffcc7745f48db692ffbb10a83bfe0afd96235c5c2a4fb23994929"
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]] [[package]]
name = "either" name = "either"
version = "1.15.0" version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "eyre"
version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec"
dependencies = [
"indenter",
"once_cell",
]
[[package]]
name = "find-msvc-tools"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a3076410a55c90011c298b04d0cfa770b00fa04e1e3c97d3f6c9de105a03844"
[[package]]
name = "futures-core"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
[[package]]
name = "futures-macro"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "futures-sink"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7"
[[package]]
name = "futures-task"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988"
[[package]]
name = "futures-util"
version = "0.3.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
dependencies = [
"futures-core",
"futures-macro",
"futures-sink",
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
name = "getrandom"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "335ff9f135e4384c8150d6f27c6daed433577f86b4750418338c01a1a2528592"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]] [[package]]
name = "half" name = "half"
version = "2.6.0" version = "2.6.0"
@ -418,95 +192,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hdrhistogram" name = "itertools"
version = "7.5.4" version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d" checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [ dependencies = [
"byteorder", "either",
"num-traits",
] ]
[[package]]
name = "heck"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]]
name = "hotpath"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bff4650da2df45a71a2f5c2c51fdcd5bea79270e0cc2a9984942d57fc77c0d7"
dependencies = [
"arc-swap",
"base64",
"cfg-if",
"clap",
"colored",
"crossbeam-channel",
"eyre",
"futures-util",
"hdrhistogram",
"hotpath-macros",
"libc",
"mach2",
"pin-project-lite",
"prettytable-rs",
"quanta",
"regex",
"serde",
"serde_json",
"tiny_http",
"tokio",
]
[[package]]
name = "hotpath-macros"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8d87ff506da0e93bac1a7ba104aa79fac048edab9d8ef922e75ba54106ee3d"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "httpdate"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9"
[[package]]
name = "indenter"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
[[package]]
name = "is-terminal"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [
"hermit-abi",
"libc",
"windows-sys 0.61.2",
]
[[package]]
name = "is_terminal_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6cb138bb79a146c1bd460005623e142ef0181e3d0219cb493e02f7d08a35695"
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.13.0" version = "0.13.0"
@ -532,27 +225,11 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "lazy_static"
version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.178" version = "0.2.171"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091" checksum = "c19937216e9d3aa9956d9bb8dfc0b0c8beb6058fc4f7a4dc4d850edf86a237d6"
[[package]]
name = "libredox"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
dependencies = [
"bitflags",
"libc",
]
[[package]] [[package]]
name = "log" name = "log"
@ -560,12 +237,6 @@ version = "0.4.27"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
[[package]]
name = "mach2"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.4"
@ -574,10 +245,22 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]] [[package]]
name = "microfetch" name = "microfetch"
version = "0.4.12" version = "0.4.9"
dependencies = [ dependencies = [
"criterion", "criterion",
"hotpath", "libc",
"nix",
]
[[package]]
name = "nix"
version = "0.30.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "537bc3c4a347b87fd52ac6c03a02ab1302962cfd93373c5d7a112cdc337854cc"
dependencies = [
"bitflags",
"cfg-if",
"cfg_aliases",
"libc", "libc",
] ]
@ -596,40 +279,12 @@ version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
name = "once_cell_polyfill"
version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]] [[package]]
name = "oorandom" name = "oorandom"
version = "11.1.5" version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e"
[[package]]
name = "page_size"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
dependencies = [
"libc",
"winapi",
]
[[package]]
name = "pin-project-lite"
version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b"
[[package]]
name = "pin-utils"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]] [[package]]
name = "plotters" name = "plotters"
version = "0.3.7" version = "0.3.7"
@ -658,19 +313,6 @@ dependencies = [
"plotters-backend", "plotters-backend",
] ]
[[package]]
name = "prettytable-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
dependencies = [
"encode_unicode",
"is-terminal",
"lazy_static",
"term",
"unicode-width",
]
[[package]] [[package]]
name = "proc-macro2" name = "proc-macro2"
version = "1.0.94" version = "1.0.94"
@ -680,21 +322,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "quanta"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
dependencies = [
"crossbeam-utils",
"libc",
"once_cell",
"raw-cpuid",
"wasi",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.40" version = "1.0.40"
@ -704,15 +331,6 @@ dependencies = [
"proc-macro2", "proc-macro2",
] ]
[[package]]
name = "raw-cpuid"
version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
"bitflags",
]
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.10.0" version = "1.10.0"
@ -733,17 +351,6 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "redox_users"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.11.1" version = "1.11.1"
@ -826,24 +433,6 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "shlex"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
[[package]]
name = "slab"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7a2ae44ef20feb57a68b23d846850f861394c2e02dc425a50098ae8c90267589"
[[package]]
name = "strsim"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
[[package]] [[package]]
name = "syn" name = "syn"
version = "2.0.100" version = "2.0.100"
@ -855,49 +444,6 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "thiserror"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52"
dependencies = [
"thiserror-impl",
]
[[package]]
name = "thiserror-impl"
version = "1.0.69"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tiny_http"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "389915df6413a2e74fb181895f933386023c71110878cd0825588928e64cdc82"
dependencies = [
"ascii",
"chunked_transfer",
"httpdate",
"log",
]
[[package]] [[package]]
name = "tinytemplate" name = "tinytemplate"
version = "1.2.1" version = "1.2.1"
@ -908,33 +454,12 @@ dependencies = [
"serde_json", "serde_json",
] ]
[[package]]
name = "tokio"
version = "1.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff360e02eab121e0bc37a2d3b4d4dc622e6eda3a8e5253d5435ecf5bd4c68408"
dependencies = [
"pin-project-lite",
]
[[package]] [[package]]
name = "unicode-ident" name = "unicode-ident"
version = "1.0.18" version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
[[package]]
name = "unicode-width"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
[[package]]
name = "utf8parse"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "walkdir" name = "walkdir"
version = "2.5.0" version = "2.5.0"
@ -945,12 +470,6 @@ dependencies = [
"winapi-util", "winapi-util",
] ]
[[package]]
name = "wasi"
version = "0.11.1+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.100" version = "0.2.100"
@ -1019,43 +538,15 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]] [[package]]
name = "winapi-util" name = "winapi-util"
version = "0.1.9" version = "0.1.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
dependencies = [ dependencies = [
"windows-sys 0.59.0", "windows-sys",
] ]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-link"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5"
[[package]] [[package]]
name = "windows-sys" name = "windows-sys"
version = "0.59.0" version = "0.59.0"
@ -1065,15 +556,6 @@ dependencies = [
"windows-targets", "windows-targets",
] ]
[[package]]
name = "windows-sys"
version = "0.61.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc"
dependencies = [
"windows-link",
]
[[package]] [[package]]
name = "windows-targets" name = "windows-targets"
version = "0.52.6" version = "0.52.6"

View file

@ -1,6 +1,6 @@
[package] [package]
name = "microfetch" name = "microfetch"
version = "0.4.12" version = "0.4.9"
edition = "2024" edition = "2024"
[lib] [lib]
@ -12,57 +12,28 @@ name = "microfetch"
path = "src/main.rs" path = "src/main.rs"
[dependencies] [dependencies]
hotpath = { optional = true, version = "0.9.0" } nix = { version = "0.30", features = ["fs", "hostname", "feature"] }
libc = "0.2.178" libc = "0.2"
[dev-dependencies] [dev-dependencies]
criterion = "0.8.1" criterion = "0.6"
[features]
hotpath = [ "dep:hotpath", "hotpath/hotpath" ]
hotpath-alloc = [ "hotpath/hotpath-alloc" ]
hotpath-off = [ "hotpath/hotpath-off" ]
[[bench]] [[bench]]
name = "benchmark"
harness = false harness = false
name = "benchmark"
[profile.dev] [profile.dev]
opt-level = 1 opt-level = 3
[profile.release] [profile.release]
strip = true
opt-level = "s"
lto = true
codegen-units = 1 codegen-units = 1
lto = true panic = "abort"
opt-level = "s"
panic = "abort"
strip = true
[profile.profiler] [profile.profiler]
debug = true inherits = "release"
inherits = "release" debug = true
split-debuginfo = "unpacked" split-debuginfo = "unpacked"
strip = "none" strip = "none"
[lints.clippy]
complexity = { level = "warn", priority = -1 }
nursery = { level = "warn", priority = -1 }
pedantic = { level = "warn", priority = -1 }
perf = { level = "warn", priority = -1 }
style = { level = "warn", priority = -1 }
# The lint groups above enable some less-than-desirable rules, we should manually
# enable those to keep our sanity.
absolute_paths = "allow"
arbitrary_source_item_ordering = "allow"
implicit_return = "allow"
missing_docs_in_private_items = "allow"
non_ascii_literal = "allow"
pattern_type_mismatch = "allow"
print_stdout = "allow"
question_mark_used = "allow"
similar_names = "allow"
single_call_fn = "allow"
std_instead_of_core = "allow"
too_long_first_doc_paragraph = "allow"
too_many_lines = "allow"
unused_trait_names = "allow"

200
README.md
View file

@ -1,30 +1,17 @@
<!-- markdownlint-disable MD013 MD033 MD041 -->
<div align="center"> <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://deps.rs/repo/github/notashelf/microfetch/status.svg" alt="https://deps.rs/repo/github/notashelf/microfetch">
<img src="https://img.shields.io/github/stars/notashelf/microfetch?label=stars&color=DEA584" alt="stars"> <!-- <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">
</div> </div>
<div id="doc-begin" align="center"> <h1 align="center">Microfetch</h1>
<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="#benchmarks">Benchmarks</a><br/>
<a href="#installation">Installation</a>
<br/>
</div>
## Synopsis 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
[fastfetch]: https://github.com/fastfetch-cli/fastfetch 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
Stupidly small and simple, laughably fast and pretty fetch tool. Written in Rust personal system, but [probably not yours](#customizing). Though, you are more
for speed and ease of maintainability. Runs in a _fraction of a millisecond_ and than welcome to use it on your system: it's pretty [fast](#benchmarks)...
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"> <p align="center">
<img <img
@ -39,7 +26,6 @@ on your system: it is pretty _[fast](#benchmarks)_...
- Fast - Fast
- Really fast - Really fast
- Minimal dependencies - Minimal dependencies
- Tiny binary (~370kb [^1])
- 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:
@ -56,80 +42,41 @@ on your system: it is pretty _[fast](#benchmarks)_...
- Did I mention fast? - Did I mention fast?
- Respects [`NO_COLOR` spec](https://no-color.org/) - Respects [`NO_COLOR` spec](https://no-color.org/)
[^1]: With the Mold linker, which is enabled by default in the Flake package,
the binary size is roughly 350kb. That's nearly 20kb reduction in size :)
## Motivation ## Motivation
[Rube-Goldmark Machine]: https://en.wikipedia.org/wiki/Rube_Goldberg_machine 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_ already hinted, is a very fast fetch tool I cannot re-iterate it enough, Microfetch is _annoyingly fast_.
written in C. I used to use Fastfetch on my systems, but I eventually came to
the realization that I am _not interested in any of its additional features_. I
don't use Sixel, I don't change my configuration more than maybe once a year and
I don't even display most of the fields that it does. Sure the configurability
is nice and I can configure the defaults that I do not like but how often do I
really do that?
Since I already enjoy programming challenges, and don't use a fetch program that
often, I eventually came to try and answer the question _how fast can I make my
fetch script?_ 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. Since
Fetch scripts are kind of a coming-of-age ritual for most Linux users, I've
decided to use it on my system. You also might be interested if you like the
defaults and like speed.
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_. It does not, however,
solve a technical problem. The "problem" Microfetch solves is entirely
self-imposed. On the matter of _size_, 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?~~ Microfetch now features
handwritten assembly to unsafely optimize some areas. In hindsight you all
should have seen this coming. Is it faster? Yes.
Also see: [Rube-Goldmark Machine]
## Benchmarks ## Benchmarks
Below are the benchmarks that I've used to back up my claims of Microfetch's The performance may be sometimes influenced by hardware-specific race
speed. It is fast, it is _very_ fast and that is the point of its existence. It conditions, or even your kernel configuration meaning it may (at times) depend
_could_ be faster, and it will be. Eventually. 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.
At this point in time, the performance may be sometimes influenced by | Command | Mean [ms] | Min [ms] | Max [ms] | Relative | Written by raf? |
hardware-specific race conditions or even your kernel configuration. Which means | :----------- | -----------: | -------: | -------: | -------------: | --------------: |
that Microfetch's speed may (at times) depend on your hardware setup. However, | `microfetch` | 1.0 ± 0.1 | 0.9 | 1.7 | 1.00 | yes |
even with the worst possible hardware I could find in my house, I've achieved a | `fastfetch` | 48.6 ± 1.6 | 45.8 | 61.3 | 46.65 ± 4.75 | no |
nice less-than-1ms invocation time. Which is pretty good. While Microfetch | `pfetch` | 206.0 ± 4.5 | 198.0 | 241.4 | 197.50 ± 19.53 | no |
_could_ be made faster, we're in the territory of environmental bottlenecks | `neofetch` | 689.1 ± 29.1 | 637.7 | 811.2 | 660.79 ± 69.56 | no |
given how little Microfetch actually allocates.
Below are the actual benchmarks with Hyperfine measured on my Desktop system. As far as I'm concerned, Microfetch is significantly faster than every other
The benchmarks were performed under medium system load, and may not be the same fetch tool that I have tried. The only downsides of using Rust for the project
on your system. Please _also_ note that those benchmarks will not be always kept (in exchange for speed and maintainability) is the slightly "bloated" dependency
up to date, but I will try to update the numbers as I make Microfetch faster. 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
| Command | Mean [µs] | Min [µs] | Max [µs] | Relative | Written by raf? | to use it to utilize the binary cache properly
| :----------- | ----------------: | -------: | -------: | -------------: | --------------: |
| `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 ### Benchmarking Individual Functions
@ -140,38 +87,11 @@ To benchmark individual functions, [Criterion.rs] is used. See Criterion's
[Getting Started Guide] for details or just run `cargo bench` to benchmark all [Getting Started Guide] for details or just run `cargo bench` to benchmark all
features of Microfetch. 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
```
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 ## Installation
> [!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`.
@ -204,31 +124,17 @@ You can't.
### Why? ### Why?
Customization, of most kinds, are expensive: I could try reading environment Customization, of any kind, is expensive: I could try reading environment
variables, parse command-line arguments or read a configuration file to allow variables, parse command-line arguments or read a configuration file but all of
configuring various fields but those inflate execution time and the resource those increment execution time and resource consumption by a lot.
consumption by a lot. Since Microfetch is closer to a code golf challenge than a
program that attempts to fill a gap, I have elected not to make this trade.
### Really? ### Really?
[main module]: ./src/main.rs To be fair, you _can_ customize Microfetch by, well, patching it. It's not the
[discussions tab]: https://github.com/NotAShelf/microfetch/discussions best way per se, but it will be the only way that does not compromise on speed.
To be fair, you _can_ customize Microfetch by, well, patching it. It is
certainly not the easiest way of doing so but if you are planning to change
something in Microfetch, patching is the best way to go. It will also the only
way that does not compromise on speed, unless you patch in bad code. Various
users have adapted Microfetch to their distribution by patching the
[main module] and inserting the logo of their choice. This is also the best way
to go if you plan to make small changes. If your changes are not small, you
might want to look for a program that is designed to be customizable; Microfetch
is built for maximum performance.
The Nix package allows passing patches in a streamlined manner by passing The Nix package allows passing patches in a streamlined manner by passing
`.overrideAttrs` to the derivation. You can apply your patches in `patches` and `.overrideAttrs` to the derivation.
share your derivations with people. Feel free to use the [discussions tab] to
share your own variants of Microfetch!
## Contributing ## Contributing
@ -240,22 +146,13 @@ Contributions that help improve performance in specific areas of Microfetch are
welcome. Though, prepare to be bombarded with questions if your changes are welcome. Though, prepare to be bombarded with questions if your changes are
large. large.
### Hacking ## Hacking
A Nix flake is provided. You may use `nix develop` to get started. Direnv users A Nix flake is provided. `nix develop` to get started. Direnv users may simply
may instead run `direnv allow` to get a complete environment with shell run `direnv allow` to get started.
integration.
Non-Nix user will need `cargo`, `clang` and `mold` installed on their system to Non-nix users will need `cargo` and `gcc` installed on their system, see
build Microfetch. As Mold seems to yield _slightly_ better results than the `Cargo.toml` for available release profiles.
default linker, it has been set as the default in `.cargo/config.toml` for
x86-64 Linux. You may override those defaults using the `RUSTFLAGS` environment
variable. For example:
```sh
# Use ld instead of Mold
$ RUSTFLAGS="-C linker=/path/to/ld.lld" cargo build
```
## Thanks ## Thanks
@ -272,7 +169,6 @@ person about current issues. To list a few, special thanks to:
- [@sioodmy](https://github.com/sioodmy) - Being cute - [@sioodmy](https://github.com/sioodmy) - Being cute
- [@mewoocat](https://github.com/mewoocat) - The awesome NixOS logo ASCII used - [@mewoocat](https://github.com/mewoocat) - The awesome NixOS logo ASCII used
in Microfetch in Microfetch
- [@uzaaft](https://github.com/uzaaft) - Helping me going faster
Additionally a big thank you to everyone who used, talked about or criticized Additionally a big thank you to everyone who used, talked about or criticized
Microfetch. I might have missed your name here, but you have my thanks. Microfetch. I might have missed your name here, but you have my thanks.

View file

@ -1,32 +1,26 @@
use criterion::{Criterion, criterion_group, criterion_main}; use criterion::{Criterion, criterion_group, criterion_main};
use microfetch_lib::{ use microfetch_lib::colors::print_dots;
UtsName, use microfetch_lib::desktop::get_desktop_info;
colors::print_dots, use microfetch_lib::release::{get_os_pretty_name, get_system_info};
desktop::get_desktop_info, use microfetch_lib::system::{
release::{get_os_pretty_name, get_system_info}, get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname,
system::{
get_memory_usage,
get_root_disk_usage,
get_shell,
get_username_and_hostname,
},
uptime::get_current,
}; };
use microfetch_lib::uptime::get_current;
fn main_benchmark(c: &mut Criterion) { 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| { c.bench_function("user_info", |b| {
b.iter(|| get_username_and_hostname(&utsname)); b.iter(|| get_username_and_hostname(&utsname));
}); });
c.bench_function("os_name", |b| b.iter(get_os_pretty_name)); c.bench_function("os_name", |b| b.iter(get_os_pretty_name));
c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname))); c.bench_function("kernel_version", |b| b.iter(|| get_system_info(&utsname)));
c.bench_function("shell", |b| b.iter(get_shell)); c.bench_function("shell", |b| b.iter(get_shell));
c.bench_function("desktop", |b| b.iter(get_desktop_info)); c.bench_function("desktop", |b| b.iter(get_desktop_info));
c.bench_function("uptime", |b| b.iter(get_current)); c.bench_function("uptime", |b| b.iter(get_current));
c.bench_function("memory_usage", |b| b.iter(get_memory_usage)); c.bench_function("memory_usage", |b| b.iter(get_memory_usage));
c.bench_function("storage", |b| b.iter(get_root_disk_usage)); c.bench_function("storage", |b| b.iter(get_root_disk_usage));
c.bench_function("colors", |b| b.iter(print_dots)); c.bench_function("colors", |b| b.iter(print_dots));
} }
criterion_group!(benches, main_benchmark); criterion_group!(benches, main_benchmark);

4
flake.lock generated
View file

@ -4,17 +4,13 @@
"locked": { "locked": {
"lastModified": 1743359643, "lastModified": 1743359643,
"narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=", "narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=",
"lastModified": 1763381801,
"narHash": "sha256-325fR0JmHW7B74/gHPv/S9w1Rfj/M2HniwQFUwdrZ9k=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a", "rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a",
"rev": "46931757ea8bdbba25c076697f8e73b8dc39fef5",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }

View file

@ -1,6 +1,6 @@
{ {
description = "A microscopic fetch script in Rust, for NixOS systems"; description = "A microscopic fetch script in Rust, for NixOS systems";
inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable"; inputs.nixpkgs.url = "github:NixOS/nixpkgs";
outputs = { outputs = {
self, self,
@ -10,12 +10,9 @@
forEachSystem = nixpkgs.lib.genAttrs systems; forEachSystem = nixpkgs.lib.genAttrs systems;
pkgsForEach = nixpkgs.legacyPackages; pkgsForEach = nixpkgs.legacyPackages;
in { in {
packages = forEachSystem (system: let packages = forEachSystem (system: {
pkgs = pkgsForEach.${system};
in {
default = self.packages.${system}.microfetch; default = self.packages.${system}.microfetch;
microfetch = pkgs.callPackage ./nix/package.nix {}; microfetch = pkgsForEach.${system}.callPackage ./nix/package.nix {};
microfetch-mold = pkgs.callPackage ./nix/package.nix {useMold = true;};
}); });
devShells = forEachSystem (system: { devShells = forEachSystem (system: {

View file

@ -1,53 +1,31 @@
{ {
lib, lib,
stdenv,
stdenvAdapters,
rustPlatform, rustPlatform,
stdenvAdapters,
llvm, llvm,
useMold ? stdenv.isLinux && !stdenv.hostPlatform.isAarch,
}: let }: let
toml = (lib.importTOML ../Cargo.toml).package; toml = (lib.importTOML ../Cargo.toml).package;
pname = toml.name; pname = toml.name;
inherit (toml) version; inherit (toml) version;
# Select stdenv based on useMold flag
stdenv =
if useMold
then stdenvAdapters.useMoldLinker llvm.stdenv
else llvm.stdenv;
in in
rustPlatform.buildRustPackage.override {inherit stdenv;} { rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} {
RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
inherit pname version; inherit pname version;
src = let
fs = lib.fileset; src = builtins.path {
s = ../.; name = "${pname}-${version}";
in path = lib.sources.cleanSource ../.;
fs.toSource { };
root = s;
fileset = fs.unions [
(fs.fileFilter (file: builtins.any file.hasExt ["rs"]) (s + /src))
(s + /Cargo.lock)
(s + /Cargo.toml)
(s + /benches)
];
};
cargoLock.lockFile = ../Cargo.lock; cargoLock.lockFile = ../Cargo.lock;
enableParallelBuilding = true; enableParallelBuilding = true;
buildNoDefaultFeatures = true;
doCheck = false;
# Only set RUSTFLAGS for mold if useMold is enabled
env = lib.optionalAttrs useMold {
CARGO_LINKER = "clang";
RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
};
meta = { meta = {
description = "Microscopic fetch script in Rust, for NixOS systems"; description = "A microscopic fetch script in Rust, for NixOS systems";
homepage = "https://github.com/NotAShelf/microfetch"; homepage = "https://github.com/NotAShelf/microfetch";
license = lib.licenses.gpl3Only; license = lib.licenses.gpl3Only;
maintainers = [lib.maintainers.NotAShelf]; maintainers = with lib.maintainers; [NotAShelf];
mainProgram = "microfetch"; mainProgram = "microfetch";
}; };
} }

View file

@ -1,31 +1,25 @@
{ {
mkShell, mkShell,
cargo,
rustc,
mold,
clang,
rust-analyzer-unwrapped, rust-analyzer-unwrapped,
rustfmt, rustfmt,
clippy, clippy,
taplo, cargo,
rustc,
rustPlatform, rustPlatform,
gnuplot, gnuplot,
}: }:
mkShell { mkShell {
name = "microfetch";
strictDeps = true; strictDeps = true;
nativeBuildInputs = [ nativeBuildInputs = [
cargo cargo
rustc rustc
mold
clang
rust-analyzer-unwrapped rust-analyzer-unwrapped
(rustfmt.override {asNightly = true;}) rustfmt
clippy clippy
taplo
gnuplot # for Criterion.rs plots gnuplot # For Criterion.rs plots
]; ];
env.RUST_SRC_PATH = "${rustPlatform.rustLibSrc}"; env.RUST_SRC_PATH = "${rustPlatform.rustLibSrc}";

View file

@ -1,81 +1,57 @@
use std::env;
use std::sync::LazyLock; use std::sync::LazyLock;
pub struct Colors { pub struct Colors {
pub reset: &'static str, pub reset: &'static str,
pub blue: &'static str, pub blue: &'static str,
pub cyan: &'static str, pub cyan: &'static str,
pub green: &'static str, pub green: &'static str,
pub yellow: &'static str, pub yellow: &'static str,
pub red: &'static str, pub red: &'static str,
pub magenta: &'static str, pub magenta: &'static str,
} }
impl Colors { impl Colors {
const fn new(is_no_color: bool) -> Self { const fn new(is_no_color: bool) -> Self {
if is_no_color { if is_no_color {
Self { Self {
reset: "", reset: "",
blue: "", blue: "",
cyan: "", cyan: "",
green: "", green: "",
yellow: "", yellow: "",
red: "", red: "",
magenta: "", magenta: "",
} }
} else { } else {
Self { Self {
reset: "\x1b[0m", reset: "\x1b[0m",
blue: "\x1b[34m", blue: "\x1b[34m",
cyan: "\x1b[36m", cyan: "\x1b[36m",
green: "\x1b[32m", green: "\x1b[32m",
yellow: "\x1b[33m", yellow: "\x1b[33m",
red: "\x1b[31m", red: "\x1b[31m",
magenta: "\x1b[35m", magenta: "\x1b[35m",
} }
}
} }
}
} }
pub static COLORS: LazyLock<Colors> = LazyLock::new(|| { pub static COLORS: LazyLock<Colors> = LazyLock::new(|| {
const NO_COLOR: *const libc::c_char = c"NO_COLOR".as_ptr(); // check for NO_COLOR once at startup
let is_no_color = unsafe { !libc::getenv(NO_COLOR).is_null() }; let is_no_color = env::var("NO_COLOR").is_ok();
Colors::new(is_no_color) Colors::new(is_no_color)
}); });
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn print_dots() -> String { pub fn print_dots() -> String {
// Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color format!(
const GLYPH: &str = ""; "{} {} {} {} {} {} {}",
let capacity = COLORS.blue.len() COLORS.blue,
+ COLORS.cyan.len() COLORS.cyan,
+ COLORS.green.len() COLORS.green,
+ COLORS.yellow.len() COLORS.yellow,
+ COLORS.red.len() COLORS.red,
+ COLORS.magenta.len() COLORS.magenta,
+ COLORS.reset.len() COLORS.reset,
+ (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
} }

View file

@ -1,42 +1,28 @@
use std::{ffi::CStr, fmt::Write};
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_desktop_info() -> String { pub fn get_desktop_info() -> String {
// Retrieve the environment variables and handle Result types // Retrieve the environment variables and handle Result types
let desktop_str = unsafe { let desktop_env = std::env::var("XDG_CURRENT_DESKTOP");
let ptr = libc::getenv(c"XDG_CURRENT_DESKTOP".as_ptr()); let display_backend_result = std::env::var("XDG_SESSION_TYPE");
if ptr.is_null() {
"Unknown" // Capitalize the first letter of the display backend value
} else { let mut display_backend = display_backend_result.unwrap_or_default();
let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); if let Some(c) = display_backend.as_mut_str().get_mut(0..1) {
s.strip_prefix("none+").unwrap_or(s) c.make_ascii_uppercase();
} }
};
let backend_str = unsafe { // Trim "none+" from the start of desktop_env if present
let ptr = libc::getenv(c"XDG_SESSION_TYPE".as_ptr()); // Use "Unknown" if desktop_env is empty or has an error
if ptr.is_null() { let desktop_env = match desktop_env {
"Unknown" Err(_) => "Unknown".to_string(),
Ok(s) => s.trim_start_matches("none+").to_string(),
};
// Handle the case where display_backend might be empty after capitalization
let display_backend = if display_backend.is_empty() {
"Unknown"
} else { } else {
let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown"); &display_backend
if s.is_empty() { "Unknown" } else { s }
} }
}; .to_string();
// Pre-calculate capacity: desktop_len + " (" + backend_len + ")" format!("{desktop_env} ({display_backend})")
// 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
} }

View file

@ -1,46 +1,5 @@
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,112 +1,82 @@
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::{self, Cursor, Write}; use crate::colors::print_dots;
use crate::desktop::get_desktop_info;
use crate::release::{get_os_pretty_name, get_system_info};
use crate::system::{get_memory_usage, get_root_disk_usage, get_shell, get_username_and_hostname};
use crate::uptime::get_current;
use std::io::{Write, stdout};
pub use microfetch_lib::UtsName;
use crate::{
colors::print_dots,
desktop::get_desktop_info,
release::{get_os_pretty_name, get_system_info},
system::{
get_memory_usage,
get_root_disk_usage,
get_shell,
get_username_and_hostname,
},
uptime::get_current,
};
#[cfg_attr(feature = "hotpath", hotpath::main)]
fn main() -> Result<(), Box<dyn std::error::Error>> { 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 = UtsName::uname()?; let utsname = nix::sys::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()?,
kernel_version: get_system_info(&utsname), kernel_version: get_system_info(&utsname),
shell: get_shell(), shell: get_shell(),
desktop: get_desktop_info(), desktop: get_desktop_info(),
uptime: get_current()?, uptime: get_current()?,
memory_usage: get_memory_usage()?, memory_usage: get_memory_usage()?,
storage: get_root_disk_usage()?, storage: get_root_disk_usage()?,
colors: print_dots(), colors: print_dots(),
}; };
print_system_info(&fields)?; print_system_info(&fields)?;
} }
Ok(()) Ok(())
} }
// Struct to hold all the fields we need in order to print the fetch. This // Struct to hold all the fields we need to print
// helps avoid Clippy warnings about argument count, and makes it slightly // helps avoid clippy warnings about argument count
// easier to pass data around. Though, it is not like we really need to. // and makes it easier to pass around, though its
// not like we need to
struct Fields { struct Fields {
user_info: String, user_info: String,
os_name: String, os_name: String,
kernel_version: String, kernel_version: String,
shell: String, shell: String,
uptime: String, uptime: String,
desktop: String, desktop: String,
memory_usage: String, memory_usage: String,
storage: String, storage: String,
colors: String, colors: String,
} }
#[cfg_attr(feature = "hotpath", hotpath::measure)] fn print_system_info(fields: &Fields) -> Result<(), Box<dyn std::error::Error>> {
fn print_system_info( use crate::colors::COLORS;
fields: &Fields,
) -> Result<(), Box<dyn std::error::Error>> {
use crate::colors::COLORS;
let Fields { let Fields {
user_info, user_info,
os_name, os_name,
kernel_version, kernel_version,
shell, shell,
uptime, uptime,
desktop, desktop,
memory_usage, memory_usage,
storage, storage,
colors, colors,
} = fields; } = fields;
let cyan = COLORS.cyan; let cyan = COLORS.cyan;
let blue = COLORS.blue; let blue = COLORS.blue;
let reset = COLORS.reset; let reset = COLORS.reset;
let system_info = format!("
{cyan} {blue} {user_info} ~{reset}
{cyan} {blue} {cyan} {cyan} {blue}System{reset} {os_name}
{cyan} {blue} {cyan} {cyan} {blue}Kernel{reset} {kernel_version}
{blue} {blue}{cyan} {cyan} {blue}Shell{reset} {shell}
{blue} {cyan} {cyan} {blue}Uptime{reset} {uptime}
{blue} {cyan} {cyan} {cyan} {blue}Desktop{reset} {desktop}
{blue} {cyan}{blue} {cyan} {blue}Memory{reset} {memory_usage}
{blue} {cyan}{blue} {cyan}󱥎 {blue}Storage (/){reset} {storage}
{cyan} {blue} {cyan} {blue}Colors{reset} {colors}\n");
let mut buf = [0u8; 2048]; Ok(stdout().write_all(system_info.as_bytes())?)
let mut cursor = Cursor::new(&mut buf[..]);
write!(
cursor,
"
{cyan} {blue} {user_info} ~{reset}
{cyan} {blue} {cyan} {cyan} {blue}System{reset} {os_name}
{cyan} {blue} {cyan} {cyan} {blue}Kernel{reset} {kernel_version}
{blue} {blue}{cyan} {cyan} {blue}Shell{reset} {shell}
{blue} {cyan} {cyan} {blue}Uptime{reset} {uptime}
{blue} {cyan} {cyan} {cyan} {blue}Desktop{reset} {desktop}
{blue} {cyan}{blue} {cyan}󰍛 {blue}Memory{reset} {memory_usage}
{blue} {cyan}{blue} {cyan}󱥎 {blue}Storage (/){reset} {storage}
{cyan} {blue} {cyan} {blue}Colors{reset} {colors}\n\n"
)?;
let len = cursor.position() as usize;
// Direct syscall to avoid stdout buffering allocation
let written = unsafe { libc::write(libc::STDOUT_FILENO, buf.as_ptr().cast(), len) };
if written < 0 {
return Err(io::Error::last_os_error().into());
}
if written as usize != len {
return Err(io::Error::new(io::ErrorKind::WriteZero, "partial write to stdout").into());
}
Ok(())
} }

View file

@ -1,69 +1,33 @@
use std::{fmt::Write as _, io}; use nix::sys::utsname::UtsName;
use std::{
fs::File,
io::{self, BufRead, BufReader},
};
use crate::{UtsName, syscall::read_file_fast};
#[must_use]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_system_info(utsname: &UtsName) -> String { pub fn get_system_info(utsname: &UtsName) -> String {
let sysname = utsname.sysname().to_str().unwrap_or("Unknown"); format!(
let release = utsname.release().to_str().unwrap_or("Unknown"); "{} {} ({})",
let machine = utsname.machine().to_str().unwrap_or("Unknown"); utsname.sysname().to_str().unwrap_or("Unknown"),
utsname.release().to_str().unwrap_or("Unknown"),
// Pre-allocate capacity: sysname + " " + release + " (" + machine + ")" utsname.machine().to_str().unwrap_or("Unknown")
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> { pub fn get_os_pretty_name() -> Result<String, io::Error> {
// Fast byte-level scanning for PRETTY_NAME= let file = File::open("/etc/os-release")?;
const PREFIX: &[u8] = b"PRETTY_NAME="; let reader = BufReader::new(file);
let mut buffer = [0u8; 1024]; for line in reader.lines() {
let line = line?;
// Use fast syscall-based file reading if let Some(pretty_name) = line.strip_prefix("PRETTY_NAME=") {
let bytes_read = read_file_fast("/etc/os-release", &mut buffer)?; if let Some(trimmed) = pretty_name
let content = &buffer[..bytes_read]; .strip_prefix('"')
.and_then(|s| s.strip_suffix('"'))
let mut offset = 0; {
return Ok(trimmed.to_string());
while offset < content.len() { }
let remaining = &content[offset..]; return Ok(pretty_name.to_string());
}
// 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'"'
{
&value[1..value.len() - 1]
} else {
value
};
// Convert to String - should be valid UTF-8
return Ok(String::from_utf8_lossy(trimmed).into_owned());
} }
Ok("Unknown".to_string())
offset += line_end + 1;
}
Ok("Unknown".to_owned())
} }

View file

@ -1,210 +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 n
///
/// 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,184 +1,87 @@
use std::{ffi::CStr, fmt::Write as _, io, mem::MaybeUninit}; use crate::colors::COLORS;
use nix::sys::{statvfs::statvfs, utsname::UtsName};
use std::{
env,
fs::File,
io::{self, Read},
};
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 { pub fn get_username_and_hostname(utsname: &UtsName) -> String {
let username = unsafe { let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_string());
let ptr = libc::getenv(c"USER".as_ptr()); let hostname = utsname
if ptr.is_null() { .nodename()
"unknown_user" .to_str()
} else { .unwrap_or("unknown_host")
CStr::from_ptr(ptr).to_str().unwrap_or("unknown_user") .to_string();
} format!(
}; "{yellow}{username}{red}@{green}{hostname}{reset}",
let hostname = utsname.nodename().to_str().unwrap_or("unknown_host"); yellow = COLORS.yellow,
red = COLORS.red,
let capacity = COLORS.yellow.len() green = COLORS.green,
+ username.len() reset = COLORS.reset,
+ 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]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn get_shell() -> String { pub fn get_shell() -> String {
unsafe { let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_string());
let ptr = libc::getenv(c"SHELL".as_ptr()); let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
if ptr.is_null() { shell_name.to_string()
return "unknown_shell".into();
}
let bytes = CStr::from_ptr(ptr).to_bytes();
let start = bytes.iter().rposition(|&b| b == b'/').map_or(0, |i| i + 1);
let name = std::str::from_utf8_unchecked(&bytes[start..]);
name.into()
}
} }
/// 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> { pub fn get_root_disk_usage() -> Result<String, io::Error> {
let mut vfs = MaybeUninit::uninit(); let vfs = statvfs("/")?;
let path = b"/\0"; let block_size = vfs.block_size() as u64;
let total_blocks = vfs.blocks();
let available_blocks = vfs.blocks_available();
if unsafe { libc::statvfs(path.as_ptr().cast(), vfs.as_mut_ptr()) } != 0 { let total_size = block_size * total_blocks;
return Err(io::Error::last_os_error()); let used_size = total_size - (block_size * available_blocks);
}
let vfs = unsafe { vfs.assume_init() }; let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0);
let block_size = vfs.f_bsize; let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
let total_blocks = vfs.f_blocks; let usage = (used_size / total_size) * 100.0;
let available_blocks = vfs.f_bavail;
let total_size = block_size * total_blocks; Ok(format!(
let used_size = total_size - (block_size * available_blocks); "{used_size:.2} GiB / {total_size:.2} GiB ({cyan}{usage:.0}%{reset})",
cyan = COLORS.cyan,
let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0); reset = COLORS.reset,
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,
"{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> { pub fn get_memory_usage() -> Result<String, io::Error> {
#[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; 1024];
// Use fast syscall-based file reading File::open("/proc/meminfo")?.read_to_string(&mut meminfo)?;
let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)?;
let meminfo = &buffer[..bytes_read];
// Fast scanning for MemTotal and MemAvailable for line in meminfo.lines() {
let mut offset = 0; let mut split = line.split_whitespace();
let mut found_total = false; match split.next().unwrap_or_default() {
let mut found_available = false; "MemTotal:" => total_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0),
"MemAvailable:" => {
while offset < meminfo.len() && (!found_total || !found_available) { available_memory_kb = split.next().unwrap_or("0").parse().unwrap_or(0.0);
let remaining = &meminfo[offset..]; // MemTotal comes before MemAvailable, stop parsing
break;
// 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;
let used_memory_gb = total_memory_gb - available_memory_gb;
Ok((used_memory_gb, total_memory_gb))
} }
#[allow(clippy::cast_precision_loss)] let (used_memory, total_memory) = parse_memory_info()?;
let total_memory_gb = total_memory_kb as f64 / 1024.0 / 1024.0; let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
#[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)) Ok(format!(
} "{used_memory:.2} GiB / {total_memory:.2} GiB ({cyan}{percentage_used}%{reset})",
cyan = COLORS.cyan,
let (used_memory, total_memory) = parse_memory_info()?; reset = COLORS.reset,
#[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,
"{used_memory:.2} GiB / {total_memory:.2} GiB \
({cyan}{percentage_used}%{reset})",
cyan = COLORS.cyan,
reset = COLORS.reset,
)
.unwrap();
Ok(result)
} }

View file

@ -1,114 +1,40 @@
use std::{io, mem::MaybeUninit}; use std::{io, mem::MaybeUninit};
/// Faster integer to string conversion without the 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> { 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 { sys_sysinfo(info.as_mut_ptr()) } != 0 { if unsafe { libc::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)] unsafe { info.assume_init().uptime as u64 }
unsafe { };
info.assume_init().uptime as u64
}
};
let days = uptime_seconds / 86400; let days = uptime_seconds / 86400;
let hours = (uptime_seconds / 3600) % 24; let hours = (uptime_seconds / 3600) % 24;
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 {
result.push_str(&days.to_string());
if days > 0 { result.push_str(if days == 1 { " day" } else { " days" });
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(itoa(hours, &mut buf)); if hours > 0 {
result.push_str(if hours == 1 { " hour" } else { " hours" }); if !result.is_empty() {
} result.push_str(", ");
if minutes > 0 { }
if !result.is_empty() { result.push_str(&hours.to_string());
result.push_str(", "); 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(if minutes == 1 { " minute" } else { " minutes" });
}
if result.is_empty() {
result.push_str("less than a minute");
} }
result.push_str(itoa(minutes, &mut buf));
result.push_str(if minutes == 1 { " minute" } else { " minutes" });
}
if result.is_empty() {
result.push_str("less than a minute");
}
Ok(result) Ok(result)
} }