diff --git a/.cargo/config.toml b/.cargo/config.toml
new file mode 100644
index 0000000..b7efcee
--- /dev/null
+++ b/.cargo/config.toml
@@ -0,0 +1,4 @@
+# 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"]
diff --git a/.envrc b/.envrc
new file mode 100644
index 0000000..9e15f92
--- /dev/null
+++ b/.envrc
@@ -0,0 +1,5 @@
+watch_file flake.nix
+watch_file flake.lock
+watch_dir nix
+
+use flake
diff --git a/.github/assets/demo.png b/.github/assets/demo.png
index 945e489..137b845 100644
Binary files a/.github/assets/demo.png and b/.github/assets/demo.png differ
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..a1067c4
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,14 @@
+version: 2
+updates:
+ # Update Cargo deps
+ - package-ecosystem: cargo
+ directory: "/"
+ schedule:
+ interval: "weekly"
+
+ # Update used workflows
+ - package-ecosystem: github-actions
+ directory: "/"
+ schedule:
+ interval: daily
+
diff --git a/.github/workflows/hotpath-comment.yml b/.github/workflows/hotpath-comment.yml
new file mode 100644
index 0000000..a5ff678
--- /dev/null
+++ b/.github/workflows/hotpath-comment.yml
@@ -0,0 +1,62 @@
+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"
diff --git a/.github/workflows/hotpath-profile.yml b/.github/workflows/hotpath-profile.yml
new file mode 100644
index 0000000..f79b668
--- /dev/null
+++ b/.github/workflows/hotpath-profile.yml
@@ -0,0 +1,65 @@
+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
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index e87e1da..5f435fb 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -10,12 +10,45 @@ env:
CARGO_TERM_COLOR: always
jobs:
- build:
-
- runs-on: ubuntu-latest
+ test:
+ name: Test on ${{ matrix.target }}
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ include:
+ - os: ubuntu-latest
+ target: x86_64-unknown-linux-gnu
+ - os: ubuntu-latest
+ target: aarch64-unknown-linux-gnu
steps:
- name: "Checkout"
- uses: actions/checkout@v4
- - name: "Build with Cargo"
- run: cargo build --verbose
+ uses: actions/checkout@v6
+
+ - name: "Setup Rust toolchain"
+ 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 }}
diff --git a/.gitignore b/.gitignore
index 8ea0ee8..8799178 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
/target
result*
+/.direnv
diff --git a/.rustfmt.toml b/.rustfmt.toml
new file mode 100644
index 0000000..9d5c77e
--- /dev/null
+++ b/.rustfmt.toml
@@ -0,0 +1,26 @@
+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
diff --git a/.taplo.toml b/.taplo.toml
new file mode 100644
index 0000000..5a6456b
--- /dev/null
+++ b/.taplo.toml
@@ -0,0 +1,13 @@
+[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
diff --git a/Cargo.lock b/Cargo.lock
index 6251ee2..065eded 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1,48 +1,138 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
-version = 3
+version = 4
[[package]]
-name = "addr2line"
-version = "0.21.0"
+name = "aho-corasick"
+version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
+checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916"
dependencies = [
- "gimli",
+ "memchr",
]
[[package]]
-name = "adler"
-version = "1.0.2"
+name = "alloca"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
-
-[[package]]
-name = "backtrace"
-version = "0.3.71"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
+checksum = "e5a7d05ea6aea7e9e64d25b9156ba2fee3fdd659e34e41063cd2fc7cd020d7f4"
dependencies = [
- "addr2line",
"cc",
- "cfg-if",
- "libc",
- "miniz_oxide",
- "object",
- "rustc-demangle",
]
+[[package]]
+name = "anes"
+version = "0.1.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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]]
+name = "anstyle"
+version = "1.0.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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]]
+name = "autocfg"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26"
+
+[[package]]
+name = "base64"
+version = "0.22.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
+
[[package]]
name = "bitflags"
-version = "2.6.0"
+version = "2.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de"
+checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd"
+
+[[package]]
+name = "bumpalo"
+version = "3.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf"
+
+[[package]]
+name = "byteorder"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
+
+[[package]]
+name = "cast"
+version = "0.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5"
[[package]]
name = "cc"
-version = "1.1.7"
+version = "1.2.46"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc"
+checksum = "b97463e1064cb1b1c1384ad0a0b9c8abd0988e2a91f52606c80ef14aadb63e36"
+dependencies = [
+ "find-msvc-tools",
+ "shlex",
+]
[[package]]
name = "cfg-if"
@@ -51,24 +141,201 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
-name = "cfg_aliases"
-version = "0.2.1"
+name = "chunked_transfer"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724"
+checksum = "6e4de3bc4ea267985becf712dc6d9eed8b04c953b3fcfb339ebc87acd9804901"
[[package]]
-name = "color-eyre"
-version = "0.6.3"
+name = "ciborium"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "55146f5e46f237f7423d74111267d4597b59b0dad0ffaf7303bce9945d843ad5"
+checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e"
dependencies = [
- "backtrace",
- "eyre",
- "indenter",
- "once_cell",
- "owo-colors",
+ "ciborium-io",
+ "ciborium-ll",
+ "serde",
]
+[[package]]
+name = "ciborium-io"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757"
+
+[[package]]
+name = "ciborium-ll"
+version = "0.2.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9"
+dependencies = [
+ "ciborium-io",
+ "half",
+]
+
+[[package]]
+name = "clap"
+version = "4.5.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8aa86934b44c19c50f87cc2790e19f54f7a67aedb64101c2e1a2e5ecfb73944"
+dependencies = [
+ "clap_builder",
+ "clap_derive",
+]
+
+[[package]]
+name = "clap_builder"
+version = "4.5.35"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2414dbb2dd0695280da6ea9261e327479e9d37b0630f6b53ba2a11c60c679fd9"
+dependencies = [
+ "anstream",
+ "anstyle",
+ "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]]
+name = "clap_lex"
+version = "0.7.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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]]
+name = "criterion"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d883447757bb0ee46f233e9dc22eb84d93a9508c9b868687b274fc431d886bf"
+dependencies = [
+ "alloca",
+ "anes",
+ "cast",
+ "ciborium",
+ "clap",
+ "criterion-plot",
+ "itertools",
+ "num-traits",
+ "oorandom",
+ "page_size",
+ "plotters",
+ "rayon",
+ "regex",
+ "serde",
+ "serde_json",
+ "tinytemplate",
+ "walkdir",
+]
+
+[[package]]
+name = "criterion-plot"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ed943f81ea2faa8dcecbbfa50164acf95d555afec96a27871663b300e387b2e4"
+dependencies = [
+ "cast",
+ "itertools",
+]
+
+[[package]]
+name = "crossbeam-channel"
+version = "0.5.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "82b8f8f868b36967f9606790d1903570de9ceaf870a7bf9fbbd3016d636a2cb2"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-deque"
+version = "0.8.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
+dependencies = [
+ "crossbeam-epoch",
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-epoch"
+version = "0.9.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
+dependencies = [
+ "crossbeam-utils",
+]
+
+[[package]]
+name = "crossbeam-utils"
+version = "0.8.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
+
+[[package]]
+name = "crunchy"
+version = "0.2.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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]]
+name = "either"
+version = "1.15.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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"
@@ -80,22 +347,224 @@ dependencies = [
]
[[package]]
-name = "gimli"
-version = "0.28.1"
+name = "find-msvc-tools"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
+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]]
+name = "half"
+version = "2.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "459196ed295495a68f7d7fe1d84f6c4b7ff0e21fe3017b2f283c6fac3ad803c9"
+dependencies = [
+ "cfg-if",
+ "crunchy",
+]
+
+[[package]]
+name = "hdrhistogram"
+version = "7.5.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "765c9198f173dd59ce26ff9f95ef0aafd0a0fe01fb9d72841bc5066a4c06511d"
+dependencies = [
+ "byteorder",
+ "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.3"
+version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
+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]]
+name = "itertools"
+version = "0.13.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186"
+dependencies = [
+ "either",
+]
+
+[[package]]
+name = "itoa"
+version = "1.0.15"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
+
+[[package]]
+name = "js-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1cfaf33c695fc6e08064efbc1f72ec937429614f25eef83af942d0e227c3a28f"
+dependencies = [
+ "once_cell",
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "lazy_static"
+version = "1.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
[[package]]
name = "libc"
-version = "0.2.155"
+version = "0.2.178"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c"
+checksum = "37c93d8daa9d8a012fd8ab92f088405fb202ea0b6ab73ee2482ae66af4f42091"
+
+[[package]]
+name = "libredox"
+version = "0.1.10"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "416f7e718bdb06000964960ffa43b4335ad4012ae8b99060261aa4a8088d5ccb"
+dependencies = [
+ "bitflags",
+ "libc",
+]
+
+[[package]]
+name = "log"
+version = "0.4.27"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94"
+
+[[package]]
+name = "mach2"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dae608c151f68243f2b000364e1f7b186d9c29845f7d2d85bd31b9ad77ad552b"
[[package]]
name = "memchr"
@@ -105,56 +574,566 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3"
[[package]]
name = "microfetch"
-version = "0.3.3"
+version = "0.4.12"
dependencies = [
- "color-eyre",
- "nix",
-]
-
-[[package]]
-name = "miniz_oxide"
-version = "0.7.4"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08"
-dependencies = [
- "adler",
-]
-
-[[package]]
-name = "nix"
-version = "0.29.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
-dependencies = [
- "bitflags",
- "cfg-if",
- "cfg_aliases",
+ "criterion",
+ "hotpath",
"libc",
]
[[package]]
-name = "object"
-version = "0.32.2"
+name = "num-traits"
+version = "0.2.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
dependencies = [
- "memchr",
+ "autocfg",
]
[[package]]
name = "once_cell"
-version = "1.19.0"
+version = "1.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d"
[[package]]
-name = "owo-colors"
-version = "3.5.0"
+name = "once_cell_polyfill"
+version = "1.70.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
+checksum = "384b8ab6d37215f3c5301a95a4accb5d64aa607f1fcb26a11b5303878451b4fe"
[[package]]
-name = "rustc-demangle"
-version = "0.1.24"
+name = "oorandom"
+version = "11.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f"
+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]]
+name = "plotters"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747"
+dependencies = [
+ "num-traits",
+ "plotters-backend",
+ "plotters-svg",
+ "wasm-bindgen",
+ "web-sys",
+]
+
+[[package]]
+name = "plotters-backend"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a"
+
+[[package]]
+name = "plotters-svg"
+version = "0.3.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670"
+dependencies = [
+ "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]]
+name = "proc-macro2"
+version = "1.0.94"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a31971752e70b8b2686d7e46ec17fb38dad4051d94024c88df49b667caea9c84"
+dependencies = [
+ "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]]
+name = "quote"
+version = "1.0.40"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d"
+dependencies = [
+ "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]]
+name = "rayon"
+version = "1.10.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
+dependencies = [
+ "either",
+ "rayon-core",
+]
+
+[[package]]
+name = "rayon-core"
+version = "1.12.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
+dependencies = [
+ "crossbeam-deque",
+ "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]]
+name = "regex"
+version = "1.11.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-automata",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-automata"
+version = "0.4.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
+dependencies = [
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+]
+
+[[package]]
+name = "regex-syntax"
+version = "0.8.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c"
+
+[[package]]
+name = "rustversion"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2"
+
+[[package]]
+name = "ryu"
+version = "1.0.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
+
+[[package]]
+name = "same-file"
+version = "1.0.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
+dependencies = [
+ "winapi-util",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.219"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "serde_json"
+version = "1.0.140"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+dependencies = [
+ "itoa",
+ "memchr",
+ "ryu",
+ "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]]
+name = "syn"
+version = "2.0.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b09a44accad81e1ba1cd74a32461ba89dee89095ba17b32f5d03683b1b1fc2a0"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "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]]
+name = "tinytemplate"
+version = "1.2.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc"
+dependencies = [
+ "serde",
+ "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]]
+name = "unicode-ident"
+version = "1.0.18"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+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]]
+name = "walkdir"
+version = "2.5.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b"
+dependencies = [
+ "same-file",
+ "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]]
+name = "wasm-bindgen"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1edc8929d7499fc4e8f0be2262a241556cfc54a0bea223790e71446f2aab1ef5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "rustversion",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f0a0651a5c2bc21487bde11ee802ccaf4c51935d0d3d42a6101f98161700bc6"
+dependencies = [
+ "bumpalo",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7fe63fc6d09ed3792bd0897b314f53de8e16568c2b3f7982f468c0bf9bd0b407"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ae87ea40c9f689fc23f209965b6fb8a99ad69aeeb0231408be24920604395de"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.100"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1a05d73b933a847d6cccdda8f838a22ff101ad9bf93e33684f39c1f5f0eece3d"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "web-sys"
+version = "0.3.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33b6dd2ef9186f1f2072e409e99cd22a975331a6b3591b12c764e0e55c60d5d2"
+dependencies = [
+ "js-sys",
+ "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]]
+name = "winapi-util"
+version = "0.1.9"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb"
+dependencies = [
+ "windows-sys 0.59.0",
+]
+
+[[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]]
+name = "windows-sys"
+version = "0.59.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
+dependencies = [
+ "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]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/Cargo.toml b/Cargo.toml
index 9682a7d..f6ddf6f 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -1,24 +1,68 @@
[package]
+name = "microfetch"
+version = "0.4.12"
+edition = "2024"
+
+[lib]
+name = "microfetch_lib"
+path = "src/lib.rs"
+
+[[bin]]
name = "microfetch"
-version = "0.3.3"
-edition = "2021"
+path = "src/main.rs"
[dependencies]
-nix = {version = "0.29", features = ["fs", "hostname"]}
-color-eyre = { version = "0.6", default-features = false }
+hotpath = { optional = true, version = "0.9.0" }
+libc = "0.2.178"
+
+[dev-dependencies]
+criterion = "0.8.1"
+
+[features]
+hotpath = [ "dep:hotpath", "hotpath/hotpath" ]
+hotpath-alloc = [ "hotpath/hotpath-alloc" ]
+hotpath-off = [ "hotpath/hotpath-off" ]
+
+[[bench]]
+harness = false
+name = "benchmark"
[profile.dev]
-opt-level = 3
+opt-level = 1
[profile.release]
-strip = true
-opt-level = "z"
-lto = true
codegen-units = 1
-panic = "abort"
+lto = true
+opt-level = "s"
+panic = "abort"
+strip = true
[profile.profiler]
-inherits = "release"
-debug = true
+debug = true
+inherits = "release"
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"
diff --git a/README.md b/README.md
index 55f928c..8f59640 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,45 @@
-# Microfetch
+
-A stupidly simple fetch tool, written in Rust. Runs in a fraction of a second,
-displays most nonsense people on r/unixporn care about. Aims to replace
-fastfetch on my system, but probably not on yours. Though you are more than
-welcome to use it on your system: it's fast.
+
+

+

+
-
+
+
+## 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)_...
+
+
+
+
## Features
- Fast
- Really fast
- Minimal dependencies
+- Tiny binary (~370kb [^1])
- Actually really fast
- Cool NixOS logo (other, inferior, distros are not supported)
- Reliable detection of following info:
@@ -20,25 +48,155 @@ welcome to use it on your system: it's fast.
- Name
- Version
- Architecture
- - Current shell (from $SHELL)
- - WM/Compositor and display backend
+ - Current shell (from `$SHELL`, trimmed if store path)
+ - Current Desktop (DE/WM/Compositor and display backend)
- Memory Usage/Total Memory
- Storage Usage/Total Storage (for `/` only)
- Shell Colors
- Did I mention fast?
+- 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
+
+[Rube-Goldmark Machine]: https://en.wikipedia.org/wiki/Rube_Goldberg_machine
+
+Fastfetch, as its name _probably_ already hinted, is a very fast fetch tool
+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
-Microfetch's performance is mostly hardware-dependant, however, the overall
-trend seems to be < 2ms on any modern (2015 and after) CPU. Below are the
-benchmarks with Hyperfine on my desktop system.
+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 |
-| :-------------------------- | --------: | -------: | -------: | -------: |
-| `target/release/microfetch` | 1.3 ± 0.1 | 1.2 | 3.7 | 1.00 |
+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.
-On an average configuration, this is roughly 25 times faster than fastfetch and
-around 80 times faster than neofetch. Results, as stated above, may vary.
+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
+
+[Criterion.rs]: https://github.com/bheisler/criterion.rs
+[Getting Started Guide]: https://bheisler.github.io/criterion.rs/book/getting_started.html
+
+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
+```
+
+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,
+> 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`.
+Additionally, you can try out Microfetch in a Nix shell.
+
+```bash
+nix shell nixpkgs#microfetch
+```
+
+Or run it directly with `nix run`
+
+```bash
+nix run nixpkgs#microfetch
+```
+
+Non-Nix users will have to build Microfetch with `cargo`. It is not published
+anywhere but I imagine you can use `cargo install --git` to install it from
+source.
+
+```bash
+cargo install --git https://github.com/notashelf/microfetch.git
+```
+
+Microfetch is _currently_ not available anywhere else. Though, does it _really_
+have to be?
## Customizing
@@ -46,14 +204,31 @@ You can't.
### Why?
-Customization, of any kind, is expensive: I could try reading environment
-variables, parse command-line arguments or read a configuration file but all of
-those increment execution time and resource consumption by a lot.
+Customization, of most kinds, are expensive: I could try reading environment
+variables, parse command-line arguments or read a configuration file to allow
+configuring various fields but those inflate execution time and the resource
+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?
-To be fair, you _can_ customize Microfetch by... Well, patching it. It's not the
-best way per se, but it will be the only way that does not compromise on speed.
+[main module]: ./src/main.rs
+[discussions tab]: https://github.com/NotAShelf/microfetch/discussions
+
+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
+`.overrideAttrs` to the derivation. You can apply your patches in `patches` and
+share your derivations with people. Feel free to use the [discussions tab] to
+share your own variants of Microfetch!
## Contributing
@@ -62,7 +237,45 @@ them altogether, as you might have a really good idea worth discussing but as a
general rule of thumb consider talking to me before creating a feature PR.
Contributions that help improve performance in specific areas of Microfetch are
-welcome. Though, prepare to be bombarded with questions.
+welcome. Though, prepare to be bombarded with questions if your changes are
+large.
+
+### Hacking
+
+A Nix flake is provided. You may use `nix develop` to get started. Direnv users
+may instead run `direnv allow` to get a complete environment with shell
+integration.
+
+Non-Nix user will need `cargo`, `clang` and `mold` installed on their system to
+build Microfetch. As Mold seems to yield _slightly_ better results than the
+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
+
+Huge thanks to everyone who took the time to make pull requests or nag me in
+person about current issues. To list a few, special thanks to:
+
+- [@Nydragon](https://github.com/Nydragon) - For packaging Microfetch in Nixpkgs
+- [@ErrorNoInternet](https://github.com/ErrorNoInternet) - Performance
+ improvements and code assistance
+- [@SoraTenshi](https://github.com/SoraTenshi) - General tips and code
+ improvements
+- [@bloxx12](https://github.com/bloxx12) - Performance improvements and
+ benchmarking plots
+- [@sioodmy](https://github.com/sioodmy) - Being cute
+- [@mewoocat](https://github.com/mewoocat) - The awesome NixOS logo ASCII used
+ in Microfetch
+- [@uzaaft](https://github.com/uzaaft) - Helping me going faster
+
+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.
## License
diff --git a/benches/benchmark.rs b/benches/benchmark.rs
new file mode 100644
index 0000000..8d62082
--- /dev/null
+++ b/benches/benchmark.rs
@@ -0,0 +1,33 @@
+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},
+ system::{
+ get_memory_usage,
+ get_root_disk_usage,
+ get_shell,
+ get_username_and_hostname,
+ },
+ uptime::get_current,
+};
+
+fn main_benchmark(c: &mut Criterion) {
+ let utsname = UtsName::uname().expect("Failed to get uname");
+ c.bench_function("user_info", |b| {
+ b.iter(|| get_username_and_hostname(&utsname));
+ });
+ 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("shell", |b| b.iter(get_shell));
+
+ c.bench_function("desktop", |b| b.iter(get_desktop_info));
+ c.bench_function("uptime", |b| b.iter(get_current));
+ 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("colors", |b| b.iter(print_dots));
+}
+
+criterion_group!(benches, main_benchmark);
+criterion_main!(benches);
diff --git a/flake.lock b/flake.lock
index 77bd438..452a532 100644
--- a/flake.lock
+++ b/flake.lock
@@ -2,15 +2,19 @@
"nodes": {
"nixpkgs": {
"locked": {
- "lastModified": 1722719969,
- "narHash": "sha256-E47qbT/mRtBCSZra+9S9208sp/QnNeOAq7EhHX+eMNE=",
+ "lastModified": 1743359643,
+ "narHash": "sha256-RkyJ9a67s0zEIz4O66TyZOIGh4TFZ4dKHKMgnxZCh2I=",
+ "lastModified": 1763381801,
+ "narHash": "sha256-325fR0JmHW7B74/gHPv/S9w1Rfj/M2HniwQFUwdrZ9k=",
"owner": "NixOS",
"repo": "nixpkgs",
- "rev": "83a364ced9d5b8a6bdac897bbef6b91e70777b97",
+ "rev": "ca77b4bc80e558ce59f2712fdb276f90c0ee309a",
+ "rev": "46931757ea8bdbba25c076697f8e73b8dc39fef5",
"type": "github"
},
"original": {
"owner": "NixOS",
+ "ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
diff --git a/flake.nix b/flake.nix
index 4ee4f5f..90978a2 100644
--- a/flake.nix
+++ b/flake.nix
@@ -1,6 +1,6 @@
{
description = "A microscopic fetch script in Rust, for NixOS systems";
- inputs.nixpkgs.url = "github:NixOS/nixpkgs";
+ inputs.nixpkgs.url = "github:NixOS/nixpkgs?ref=nixos-unstable";
outputs = {
self,
@@ -10,8 +10,16 @@
forEachSystem = nixpkgs.lib.genAttrs systems;
pkgsForEach = nixpkgs.legacyPackages;
in {
- packages = forEachSystem (system: {
- default = pkgsForEach.${system}.callPackage ./nix/package.nix {};
+ packages = forEachSystem (system: let
+ pkgs = pkgsForEach.${system};
+ in {
+ default = self.packages.${system}.microfetch;
+ microfetch = pkgs.callPackage ./nix/package.nix {};
+ microfetch-mold = pkgs.callPackage ./nix/package.nix {useMold = true;};
+ });
+
+ devShells = forEachSystem (system: {
+ default = pkgsForEach.${system}.callPackage ./nix/shell.nix {};
});
};
}
diff --git a/nix/package.nix b/nix/package.nix
index 1a7dde3..052f576 100644
--- a/nix/package.nix
+++ b/nix/package.nix
@@ -1,30 +1,53 @@
{
lib,
- rustPlatform,
+ stdenv,
stdenvAdapters,
+ rustPlatform,
llvm,
+ useMold ? stdenv.isLinux && !stdenv.hostPlatform.isAarch,
}: let
toml = (lib.importTOML ../Cargo.toml).package;
pname = toml.name;
inherit (toml) version;
+
+ # Select stdenv based on useMold flag
+ stdenv =
+ if useMold
+ then stdenvAdapters.useMoldLinker llvm.stdenv
+ else llvm.stdenv;
in
- rustPlatform.buildRustPackage.override {stdenv = stdenvAdapters.useMoldLinker llvm.stdenv;} {
- RUSTFLAGS = "-C link-arg=-fuse-ld=mold";
-
+ rustPlatform.buildRustPackage.override {inherit stdenv;} {
inherit pname version;
-
- src = builtins.path {
- name = "${pname}-${version}";
- path = lib.sources.cleanSource ../.;
- };
+ src = let
+ fs = lib.fileset;
+ s = ../.;
+ in
+ 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;
+ 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 = {
- description = "A microscopic fetch script in Rust, for NixOS systems";
+ description = "Microscopic fetch script in Rust, for NixOS systems";
homepage = "https://github.com/NotAShelf/microfetch";
license = lib.licenses.gpl3Only;
- maintainers = with lib.maintainers; [NotAShelf];
+ maintainers = [lib.maintainers.NotAShelf];
mainProgram = "microfetch";
};
}
diff --git a/nix/shell.nix b/nix/shell.nix
new file mode 100644
index 0000000..ae65e10
--- /dev/null
+++ b/nix/shell.nix
@@ -0,0 +1,32 @@
+{
+ mkShell,
+ cargo,
+ rustc,
+ mold,
+ clang,
+ rust-analyzer-unwrapped,
+ rustfmt,
+ clippy,
+ taplo,
+ rustPlatform,
+ gnuplot,
+}:
+mkShell {
+ name = "microfetch";
+ strictDeps = true;
+ nativeBuildInputs = [
+ cargo
+ rustc
+ mold
+ clang
+
+ rust-analyzer-unwrapped
+ (rustfmt.override {asNightly = true;})
+ clippy
+ taplo
+
+ gnuplot # for Criterion.rs plots
+ ];
+
+ env.RUST_SRC_PATH = "${rustPlatform.rustLibSrc}";
+}
diff --git a/src/colors.rs b/src/colors.rs
index 1aa8096..7c65944 100644
--- a/src/colors.rs
+++ b/src/colors.rs
@@ -1,13 +1,81 @@
-pub const RESET: &str = "\x1b[0m";
-pub const BLUE: &str = "\x1b[34m";
-pub const CYAN: &str = "\x1b[36m";
-pub const GREEN: &str = "\x1b[32m";
-pub const YELLOW: &str = "\x1b[33m";
-pub const RED: &str = "\x1b[31m";
-pub const MAGENTA: &str = "\x1b[35m";
+use std::sync::LazyLock;
-pub fn print_dots() -> Result {
- let colors = format!("{BLUE} {CYAN} {GREEN} {YELLOW} {RED} {MAGENTA} {RESET}");
-
- Ok(colors)
+pub struct Colors {
+ pub reset: &'static str,
+ pub blue: &'static str,
+ pub cyan: &'static str,
+ pub green: &'static str,
+ pub yellow: &'static str,
+ pub red: &'static str,
+ pub magenta: &'static str,
+}
+
+impl Colors {
+ const fn new(is_no_color: bool) -> Self {
+ if is_no_color {
+ Self {
+ reset: "",
+ blue: "",
+ cyan: "",
+ green: "",
+ yellow: "",
+ red: "",
+ magenta: "",
+ }
+ } else {
+ Self {
+ reset: "\x1b[0m",
+ blue: "\x1b[34m",
+ cyan: "\x1b[36m",
+ green: "\x1b[32m",
+ yellow: "\x1b[33m",
+ red: "\x1b[31m",
+ magenta: "\x1b[35m",
+ }
+ }
+ }
+}
+
+pub static COLORS: LazyLock = LazyLock::new(|| {
+ const NO_COLOR: *const libc::c_char = c"NO_COLOR".as_ptr();
+ let is_no_color = unsafe { !libc::getenv(NO_COLOR).is_null() };
+ Colors::new(is_no_color)
+});
+
+#[must_use]
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
+pub fn print_dots() -> String {
+ // Pre-calculate capacity: 6 color codes + " " (glyph + 2 spaces) per color
+ const GLYPH: &str = "";
+ let capacity = COLORS.blue.len()
+ + COLORS.cyan.len()
+ + COLORS.green.len()
+ + COLORS.yellow.len()
+ + COLORS.red.len()
+ + COLORS.magenta.len()
+ + COLORS.reset.len()
+ + (GLYPH.len() + 2) * 6;
+
+ let mut result = String::with_capacity(capacity);
+ result.push_str(COLORS.blue);
+ result.push_str(GLYPH);
+ result.push_str(" ");
+ result.push_str(COLORS.cyan);
+ result.push_str(GLYPH);
+ result.push_str(" ");
+ result.push_str(COLORS.green);
+ result.push_str(GLYPH);
+ result.push_str(" ");
+ result.push_str(COLORS.yellow);
+ result.push_str(GLYPH);
+ result.push_str(" ");
+ result.push_str(COLORS.red);
+ result.push_str(GLYPH);
+ result.push_str(" ");
+ result.push_str(COLORS.magenta);
+ result.push_str(GLYPH);
+ result.push_str(" ");
+ result.push_str(COLORS.reset);
+
+ result
}
diff --git a/src/desktop.rs b/src/desktop.rs
index 9d72325..501e967 100644
--- a/src/desktop.rs
+++ b/src/desktop.rs
@@ -1,20 +1,42 @@
-use color_eyre::Result;
-use std::{env, io};
+use std::{ffi::CStr, fmt::Write};
-pub fn get_desktop_info() -> Result {
- let desktop_env = env::var("XDG_CURRENT_DESKTOP");
- let display_backend = env::var("XDG_SESSION_TYPE");
+#[must_use]
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
+pub fn get_desktop_info() -> String {
+ // Retrieve the environment variables and handle Result types
+ let desktop_str = unsafe {
+ let ptr = libc::getenv(c"XDG_CURRENT_DESKTOP".as_ptr());
+ if ptr.is_null() {
+ "Unknown"
+ } else {
+ let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown");
+ s.strip_prefix("none+").unwrap_or(s)
+ }
+ };
- // Trim "none+" from the start of desktop_env if present
- // XXX: This is a workaround for NixOS modules that set XDG_CURRENT_DESKTOP to "none+foo"
- // instead of just "foo"
- // Use "Unknown" if desktop_env or display_backend is empty
- let desktop_env = match desktop_env {
- Err(_) => String::from("Unknown"),
- Ok(s) => s.trim_start_matches("none+").to_owned(),
- };
+ let backend_str = unsafe {
+ let ptr = libc::getenv(c"XDG_SESSION_TYPE".as_ptr());
+ if ptr.is_null() {
+ "Unknown"
+ } else {
+ let s = CStr::from_ptr(ptr).to_str().unwrap_or("Unknown");
+ if s.is_empty() { "Unknown" } else { s }
+ }
+ };
- let display_backend = display_backend.unwrap_or_else(|_| String::from("Unknown"));
+ // 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(" (");
- Ok(format!("{desktop_env} ({display_backend})"))
+ // 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
}
diff --git a/src/lib.rs b/src/lib.rs
new file mode 100644
index 0000000..1e0f9f3
--- /dev/null
+++ b/src/lib.rs
@@ -0,0 +1,46 @@
+pub mod colors;
+pub mod desktop;
+pub mod release;
+pub mod syscall;
+pub mod system;
+pub mod uptime;
+
+use std::mem::MaybeUninit;
+
+/// Wrapper for `libc::utsname` with safe accessor methods
+pub struct UtsName(libc::utsname);
+
+impl UtsName {
+ /// Calls `uname` syscall and returns a `UtsName` wrapper
+ ///
+ /// # Errors
+ ///
+ /// Returns an error if the `uname` syscall fails
+ pub fn uname() -> Result {
+ let mut uts = MaybeUninit::uninit();
+ if unsafe { libc::uname(uts.as_mut_ptr()) } != 0 {
+ return Err(std::io::Error::last_os_error());
+ }
+ Ok(Self(unsafe { uts.assume_init() }))
+ }
+
+ #[must_use]
+ pub const fn nodename(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(self.0.nodename.as_ptr()) }
+ }
+
+ #[must_use]
+ pub const fn sysname(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(self.0.sysname.as_ptr()) }
+ }
+
+ #[must_use]
+ pub const fn release(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(self.0.release.as_ptr()) }
+ }
+
+ #[must_use]
+ pub const fn machine(&self) -> &std::ffi::CStr {
+ unsafe { std::ffi::CStr::from_ptr(self.0.machine.as_ptr()) }
+ }
+}
diff --git a/src/main.rs b/src/main.rs
index 30fa78a..e29d6d1 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,69 +1,112 @@
mod colors;
mod desktop;
mod release;
+mod syscall;
mod system;
mod uptime;
-use crate::colors::{print_dots, BLUE, CYAN, RESET};
-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::{self, Cursor, Write};
-use color_eyre::Report;
-use nix::sys::sysinfo::sysinfo;
+pub use microfetch_lib::UtsName;
-fn main() -> Result<(), Report> {
- color_eyre::install()?;
+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,
+};
- let user_info = get_username_and_hostname()?;
- let os_name = get_os_pretty_name()?;
- let kernel_version = get_system_info()?;
- let shell = get_shell()?;
- let uptime = get_current()?;
- let window_manager = get_desktop_info()?;
- let sys_info = sysinfo()?;
- let memory_usage = get_memory_usage(sys_info);
- let storage = get_root_disk_usage()?;
- let colors = print_dots()?;
+#[cfg_attr(feature = "hotpath", hotpath::main)]
+fn main() -> Result<(), Box> {
+ if Some("--version") == std::env::args().nth(1).as_deref() {
+ println!("Microfetch {}", env!("CARGO_PKG_VERSION"));
+ } else {
+ let utsname = UtsName::uname()?;
+ let fields = Fields {
+ user_info: get_username_and_hostname(&utsname),
+ os_name: get_os_pretty_name()?,
+ kernel_version: get_system_info(&utsname),
+ shell: get_shell(),
+ desktop: get_desktop_info(),
+ uptime: get_current()?,
+ memory_usage: get_memory_usage()?,
+ storage: get_root_disk_usage()?,
+ colors: print_dots(),
+ };
+ print_system_info(&fields)?;
+ }
- print_system_info(
- &user_info,
- &os_name,
- &kernel_version,
- &shell,
- &uptime,
- &window_manager,
- &memory_usage,
- &storage,
- &colors,
- );
-
- Ok(())
+ Ok(())
}
+// Struct to hold all the fields we need in order to print the fetch. This
+// helps avoid Clippy warnings about argument count, and makes it slightly
+// easier to pass data around. Though, it is not like we really need to.
+struct Fields {
+ user_info: String,
+ os_name: String,
+ kernel_version: String,
+ shell: String,
+ uptime: String,
+ desktop: String,
+ memory_usage: String,
+ storage: String,
+ colors: String,
+}
+
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
fn print_system_info(
- user_info: &str,
- os_name: &str,
- kernel_version: &str,
- shell: &str,
- uptime: &str,
- window_manager: &str,
- memory_usage: &str,
- storage: &str,
- colors: &str,
-) {
- println!(
- "
- {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}WM{RESET} {window_manager}
- {BLUE} ▝█▛ {CYAN}██▖{BLUE}▗▄▄▄▄▄▄▄▄▄▄▄ {CYAN} {BLUE}Memory{RESET} {memory_usage}
- {BLUE} ▝ {CYAN}▟█▜█▖{BLUE}▀▀▀▀▀██▛▀▀▘ {CYAN} {BLUE}Storage (/){RESET} {storage}
- {CYAN} ▟█▘ ▜█▖ {BLUE}▝█▛ {CYAN} {BLUE}Colors{RESET} {colors}
+ fields: &Fields,
+) -> Result<(), Box> {
+ use crate::colors::COLORS;
+
+ let Fields {
+ user_info,
+ os_name,
+ kernel_version,
+ shell,
+ uptime,
+ desktop,
+ memory_usage,
+ storage,
+ colors,
+ } = fields;
+
+ let cyan = COLORS.cyan;
+ let blue = COLORS.blue;
+ let reset = COLORS.reset;
+
+ let mut buf = [0u8; 2048];
+ 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(())
}
diff --git a/src/release.rs b/src/release.rs
index 25f5640..8d3232e 100644
--- a/src/release.rs
+++ b/src/release.rs
@@ -1,42 +1,69 @@
-use color_eyre::Result;
-use std::fs::{self, read_to_string};
-use std::io;
+use std::{fmt::Write as _, io};
-// Try to detect OS type as accurately as possible and without depending on uname.
-// /etc/os-release should generally imply Linux, and /etc/bsd-release would imply BSD system.
-fn detect_os() -> Result<&'static str, io::Error> {
- if fs::metadata("/etc/os-release").is_ok() || fs::metadata("/usr/lib/os-release").is_ok() {
- Ok("Linux")
- } else if fs::metadata("/etc/rc.conf").is_ok() || fs::metadata("/etc/bsd-release").is_ok() {
- Ok("BSD")
- } else {
- Ok("Unknown")
- }
-}
-
-pub fn get_system_info() -> Result {
- let system = detect_os()?;
-
- let kernel_release = read_to_string("/proc/sys/kernel/osrelease")?;
- let kernel_release = kernel_release.trim();
-
- let architecture = read_to_string("/proc/sys/kernel/arch")?;
- let architecture = architecture.trim();
-
- let result = format!("{system} {kernel_release} ({architecture})");
- Ok(result)
+use crate::{UtsName, syscall::read_file_fast};
+
+#[must_use]
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
+pub fn get_system_info(utsname: &UtsName) -> String {
+ let sysname = utsname.sysname().to_str().unwrap_or("Unknown");
+ let release = utsname.release().to_str().unwrap_or("Unknown");
+ let machine = utsname.machine().to_str().unwrap_or("Unknown");
+
+ // Pre-allocate capacity: sysname + " " + release + " (" + machine + ")"
+ let capacity = sysname.len() + 1 + release.len() + 2 + machine.len() + 1;
+ let mut result = String::with_capacity(capacity);
+
+ write!(result, "{sysname} {release} ({machine})").unwrap();
+ result
}
+/// 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 {
- let os_release_content = read_to_string("/etc/os-release")?;
- let pretty_name = os_release_content
- .lines()
- .find(|line| line.starts_with("PRETTY_NAME="))
- .map(|line| {
- line.trim_start_matches("PRETTY_NAME=")
- .trim_matches('"')
- .to_string()
- });
+ // Fast byte-level scanning for PRETTY_NAME=
+ const PREFIX: &[u8] = b"PRETTY_NAME=";
- Ok(pretty_name.unwrap_or_else(|| "Unknown".to_string()))
+ 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'"'
+ {
+ &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())
}
diff --git a/src/syscall.rs b/src/syscall.rs
new file mode 100644
index 0000000..0c8634b
--- /dev/null
+++ b/src/syscall.rs
@@ -0,0 +1,210 @@
+//! 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 {
+ const O_RDONLY: i32 = 0;
+
+ // Use stack-allocated buffer for null-terminated path (max 256 bytes)
+ let path_bytes = path.as_bytes();
+ if path_bytes.len() >= 256 {
+ return Err(io::Error::new(io::ErrorKind::InvalidInput, "Path too long"));
+ }
+
+ let mut path_buf = [0u8; 256];
+ path_buf[..path_bytes.len()].copy_from_slice(path_bytes);
+ // XXX: Already zero-terminated since array is initialized to zeros
+
+ unsafe {
+ let fd = sys_open(path_buf.as_ptr(), O_RDONLY);
+ if fd < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ let bytes_read = sys_read(fd, buffer.as_mut_ptr(), buffer.len());
+ let _ = sys_close(fd);
+
+ if bytes_read < 0 {
+ return Err(io::Error::last_os_error());
+ }
+
+ #[allow(clippy::cast_sign_loss)]
+ {
+ Ok(bytes_read as usize)
+ }
+ }
+}
diff --git a/src/system.rs b/src/system.rs
index 551a833..ba8fe79 100644
--- a/src/system.rs
+++ b/src/system.rs
@@ -1,60 +1,184 @@
-use color_eyre::Result;
-use nix::sys::statvfs::statvfs;
-use nix::sys::sysinfo::SysInfo;
+use std::{ffi::CStr, fmt::Write as _, io, mem::MaybeUninit};
-use std::env;
-use std::io::{self};
+use crate::{UtsName, colors::COLORS, syscall::read_file_fast};
-use crate::colors::{CYAN, GREEN, RED, RESET, YELLOW};
+#[must_use]
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
+pub fn get_username_and_hostname(utsname: &UtsName) -> String {
+ let username = unsafe {
+ let ptr = libc::getenv(c"USER".as_ptr());
+ if ptr.is_null() {
+ "unknown_user"
+ } else {
+ CStr::from_ptr(ptr).to_str().unwrap_or("unknown_user")
+ }
+ };
+ let hostname = utsname.nodename().to_str().unwrap_or("unknown_host");
-pub fn get_username_and_hostname() -> Result {
- let username = env::var("USER").unwrap_or_else(|_| "unknown_user".to_string());
- let hostname = nix::unistd::gethostname()?;
- let hostname = hostname.to_string_lossy();
+ 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);
- Ok(format!("{YELLOW}{username}{RED}@{GREEN}{hostname}"))
+ 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
}
-pub fn get_shell() -> Result {
- let shell_path = env::var("SHELL").unwrap_or_else(|_| "unknown_shell".to_string());
- let shell_name = shell_path.rsplit('/').next().unwrap_or("unknown_shell");
-
- Ok(shell_name.to_string())
-}
-
-pub fn get_root_disk_usage() -> Result {
- let vfs = statvfs("/")?;
- let block_size = vfs.block_size() as u64;
- let total_blocks = vfs.blocks();
- let available_blocks = vfs.blocks_available();
-
- let total_size = block_size * total_blocks;
- let used_size = total_size - (block_size * available_blocks);
-
- let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0);
- let used_size = used_size as f64 / (1024.0 * 1024.0 * 1024.0);
- let usage = (used_size as f64 / total_size as f64) * 100.0;
-
- Ok(format!(
- "{used_size:.2} GiB / {total_size:.2} GiB ({CYAN}{usage:.0}%{RESET})"
- ))
-}
-
-pub fn get_memory_usage(info: SysInfo) -> String {
- #[inline(always)]
- fn parse_memory_info(info: SysInfo) -> (f64, f64) {
- let total_memory_kb = (info.ram_total() / 1024) as f64;
- let available_memory_kb = (info.ram_unused() / 1024) as f64;
-
- 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;
-
- (used_memory_gb, total_memory_gb)
+#[must_use]
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
+pub fn get_shell() -> String {
+ unsafe {
+ let ptr = libc::getenv(c"SHELL".as_ptr());
+ if ptr.is_null() {
+ return "unknown_shell".into();
}
- let (used_memory, total_memory) = parse_memory_info(info);
- let percentage_used = (used_memory / total_memory * 100.0).round() as u64;
-
- format!("{used_memory:.2} GiB / {total_memory:.2} GiB ({CYAN}{percentage_used}%{RESET})")
+ 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 {
+ 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);
+
+ let total_size = total_size as f64 / (1024.0 * 1024.0 * 1024.0);
+ 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 {
+ #[cfg_attr(feature = "hotpath", hotpath::measure)]
+ fn parse_memory_info() -> Result<(f64, f64), io::Error> {
+ let mut total_memory_kb = 0u64;
+ let mut available_memory_kb = 0u64;
+ let mut buffer = [0u8; 1024];
+
+ // Use fast syscall-based file reading
+ let bytes_read = read_file_fast("/proc/meminfo", &mut buffer)?;
+ let meminfo = &buffer[..bytes_read];
+
+ // 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;
+ }
+
+ #[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;
+
+ 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)
}
diff --git a/src/uptime.rs b/src/uptime.rs
index 6c44640..095af7d 100644
--- a/src/uptime.rs
+++ b/src/uptime.rs
@@ -1,26 +1,114 @@
-use color_eyre::Result;
-use nix::sys::sysinfo::sysinfo;
-use std::io;
+use std::{io, mem::MaybeUninit};
-pub fn get_current() -> Result {
- let info = sysinfo().map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
- let uptime_seconds = info.uptime().as_secs_f64();
+/// 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 total_minutes = (uptime_seconds / 60.0).round() as u64;
- let days = total_minutes / (60 * 24);
- let hours = (total_minutes % (60 * 24)) / 60;
- let minutes = total_minutes % 60;
+ let mut i = buf.len();
+ while n > 0 {
+ i -= 1;
+ buf[i] = b'0' + (n % 10) as u8;
+ n /= 10;
+ }
- let mut parts = Vec::with_capacity(3);
- if days > 0 {
- parts.push(format!("{days} days"));
- }
- if hours > 0 || days > 0 {
- parts.push(format!("{hours} hours"));
- }
- if minutes > 0 || hours > 0 || days > 0 {
- parts.push(format!("{minutes} minutes"));
- }
-
- Ok(parts.join(", "))
+ unsafe { std::str::from_utf8_unchecked(&buf[i..]) }
+}
+
+/// Direct `sysinfo` syscall using inline assembly
+///
+/// # Safety
+///
+/// This function uses inline assembly to make a direct syscall.
+/// The caller must ensure the sysinfo pointer is valid.
+#[inline]
+unsafe fn sys_sysinfo(info: *mut libc::sysinfo) -> i64 {
+ #[cfg(target_arch = "x86_64")]
+ {
+ let ret: i64;
+ unsafe {
+ std::arch::asm!(
+ "syscall",
+ in("rax") 99_i64, // __NR_sysinfo
+ in("rdi") info,
+ out("rcx") _,
+ out("r11") _,
+ lateout("rax") ret,
+ options(nostack)
+ );
+ }
+ ret
+ }
+
+ #[cfg(target_arch = "aarch64")]
+ {
+ let ret: i64;
+ unsafe {
+ std::arch::asm!(
+ "svc #0",
+ in("x8") 179_i64, // __NR_sysinfo
+ in("x0") info,
+ lateout("x0") ret,
+ options(nostack)
+ );
+ }
+ ret
+ }
+
+ #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))]
+ {
+ unsafe { libc::sysinfo(info) as i64 }
+ }
+}
+
+/// Gets the current system uptime.
+///
+/// # Errors
+///
+/// Returns an error if the system uptime cannot be retrieved.
+#[cfg_attr(feature = "hotpath", hotpath::measure)]
+pub fn get_current() -> Result {
+ let uptime_seconds = {
+ let mut info = MaybeUninit::uninit();
+ if unsafe { sys_sysinfo(info.as_mut_ptr()) } != 0 {
+ return Err(io::Error::last_os_error());
+ }
+ #[allow(clippy::cast_sign_loss)]
+ unsafe {
+ info.assume_init().uptime as u64
+ }
+ };
+
+ let days = uptime_seconds / 86400;
+ let hours = (uptime_seconds / 3600) % 24;
+ let minutes = (uptime_seconds / 60) % 60;
+
+ let mut result = String::with_capacity(32);
+ let mut buf = [0u8; 20]; // Enough for u64::MAX
+
+ if days > 0 {
+ result.push_str(itoa(days, &mut buf));
+ result.push_str(if days == 1 { " day" } else { " days" });
+ }
+ if hours > 0 {
+ if !result.is_empty() {
+ result.push_str(", ");
+ }
+ result.push_str(itoa(hours, &mut buf));
+ result.push_str(if hours == 1 { " hour" } else { " hours" });
+ }
+ if minutes > 0 {
+ if !result.is_empty() {
+ result.push_str(", ");
+ }
+ result.push_str(itoa(minutes, &mut buf));
+ result.push_str(if minutes == 1 { " minute" } else { " minutes" });
+ }
+ if result.is_empty() {
+ result.push_str("less than a minute");
+ }
+
+ Ok(result)
}