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. +
+ https://deps.rs/repo/github/notashelf/microfetch + stars +
-![Demo](.github/assets/demo.png) +
+

Microfetch

+

Microscopic fetch tool in Rust, for NixOS systems, with special emphasis on speed

+
+ Synopsis
+ Features | Motivation
| Benchmarks
+ Installation +
+
+ +## 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)_... + +

+ latest demo +

## 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) }