diff --git a/.github/actions/runner-fallback/action.yml b/.github/actions/runner-fallback/action.yml new file mode 100644 index 0000000..9b320f2 --- /dev/null +++ b/.github/actions/runner-fallback/action.yml @@ -0,0 +1,61 @@ +# actions/runner-fallback/action.yml +name: "Runner Fallback" +description: | + Chooses a self-hosted runner when one with the required labels is online, + otherwise returns a fallback GitHub-hosted label. +inputs: + primary-runner: + description: 'Comma-separated label list for the preferred self-hosted runner (e.g. "self-hosted,linux")' + required: true + fallback-runner: + description: 'Comma-separated label list or single label for the fallback (e.g. "ubuntu-latest")' + required: true + github-token: + description: "PAT or GITHUB_TOKEN with `repo` scope" + required: true + +outputs: + use-runner: + description: "JSON array of labels you can feed straight into runs-on" + value: ${{ steps.pick.outputs.use-runner }} + +runs: + using: "composite" + steps: + - name: Check self-hosted fleet + id: pick + shell: bash + env: + TOKEN: ${{ inputs.github-token }} + PRIMARY: ${{ inputs.primary-runner }} + FALLBACK: ${{ inputs.fallback-runner }} + run: | + # -------- helper ----------- + to_json_array () { + local list="$1"; IFS=',' read -ra L <<<"$list" + printf '['; printf '"%s",' "${L[@]}"; printf ']' + } + # -------- query API --------- + repo="${{ github.repository }}" + runners=$(curl -s -H "Authorization: Bearer $TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos/$repo/actions/runners?per_page=100") + # Split wanted labels + IFS=',' read -ra WANT <<<"$PRIMARY" + online_found=0 + while read -r row; do + labels=$(jq -r '.labels[].name' <<<"$row") + ok=1 + for w in "${WANT[@]}"; do + grep -Fxq "$w" <<<"$labels" || { ok=0; break; } + done + [ "$ok" -eq 1 ] && { online_found=1; break; } + done < <(jq -c '.runners[] | select(.status=="online")' <<<"$runners") + + if [ "$online_found" -eq 1 ]; then + echo "✅ Self-hosted runner online." + echo "use-runner=$(to_json_array "$PRIMARY")" >>"$GITHUB_OUTPUT" + else + echo "❌ No matching self-hosted runner online - using fallback." + echo "use-runner=$(to_json_array "$FALLBACK")" >>"$GITHUB_OUTPUT" + fi diff --git a/.github/runners/runner-arm/Dockerfile b/.github/runners/runner-arm/Dockerfile new file mode 100644 index 0000000..741114e --- /dev/null +++ b/.github/runners/runner-arm/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:latest + +ARG RUNNER_VERSION="2.323.0" + +# Prevents installdependencies.sh from prompting the user and blocking the image creation +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update -y && apt upgrade -y && useradd -m docker +RUN apt install -y --no-install-recommends \ + curl jq build-essential libssl-dev libffi-dev python3 python3-venv python3-dev python3-pip \ + # dot net core dependencies + libicu74 libssl3 libkrb5-3 zlib1g libcurl4 + + +RUN cd /home/docker && mkdir actions-runner && cd actions-runner \ + && curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz \ + && tar xzf ./actions-runner-linux-arm64-${RUNNER_VERSION}.tar.gz + +RUN chown -R docker ~docker && /home/docker/actions-runner/bin/installdependencies.sh + +COPY entrypoint.sh entrypoint.sh + +# make the script executable +RUN chmod +x entrypoint.sh + +# since the config and run script for actions are not allowed to be run by root, +# set the user to "docker" so all subsequent commands are run as the docker user +USER docker + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/.github/runners/runner-arm/docker-compose.yml b/.github/runners/runner-arm/docker-compose.yml new file mode 100644 index 0000000..3ecee8e --- /dev/null +++ b/.github/runners/runner-arm/docker-compose.yml @@ -0,0 +1,18 @@ +# docker-compose.yml + +services: + github-runner: + build: + context: . + args: + RUNNER_VERSION: 2.323.0 + # container_name commented to allow for multiple runners + # container_name: github-runner + env_file: + - .env + volumes: + - runner-work:/home/runner/actions-runner/_work + restart: unless-stopped + +volumes: + runner-work: diff --git a/.github/runners/runner-arm/entrypoint.sh b/.github/runners/runner-arm/entrypoint.sh new file mode 100644 index 0000000..97e8609 --- /dev/null +++ b/.github/runners/runner-arm/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +REPOSITORY=$REPO +ACCESS_TOKEN=$GH_TOKEN +LABELS=$RUNNER_LABELS + +# echo "REPO ${REPOSITORY}" +# echo "ACCESS_TOKEN ${ACCESS_TOKEN}" + +REG_TOKEN=$(curl -X POST -H "Authorization: token ${ACCESS_TOKEN}" -H "Accept: application/vnd.github+json" https://api.github.com/repos/${REPOSITORY}/actions/runners/registration-token | jq .token --raw-output) + +cd /home/docker/actions-runner + +./config.sh --url https://github.com/${REPOSITORY} --token ${REG_TOKEN} --labels ${LABELS} + +cleanup() { + echo "Removing runner..." + ./config.sh remove --unattended --token ${REG_TOKEN} +} + +trap 'cleanup; exit 130' INT +trap 'cleanup; exit 143' TERM + +./run.sh & wait $! \ No newline at end of file diff --git a/.github/runners/runner-arm/example.env b/.github/runners/runner-arm/example.env new file mode 100644 index 0000000..6ab2dc8 --- /dev/null +++ b/.github/runners/runner-arm/example.env @@ -0,0 +1,9 @@ + +# Repository name +REPO="Magnus167/rustframe" + +# GitHub runner token +GH_TOKEN="some_token_here" + +# Labels for the runner +RUNNER_LABELS=self-hosted-linux,linux \ No newline at end of file diff --git a/.github/runners/runner-arm/start.sh b/.github/runners/runner-arm/start.sh new file mode 100644 index 0000000..891f4d4 --- /dev/null +++ b/.github/runners/runner-arm/start.sh @@ -0,0 +1,4 @@ + + +docker compose up -d --build +# docker compose up -d --build --scale github-runner=2 diff --git a/.github/runners/runner-x64/Dockerfile b/.github/runners/runner-x64/Dockerfile new file mode 100644 index 0000000..efcba95 --- /dev/null +++ b/.github/runners/runner-x64/Dockerfile @@ -0,0 +1,30 @@ +FROM ubuntu:latest + +ARG RUNNER_VERSION="2.323.0" + +# Prevents installdependencies.sh from prompting the user and blocking the image creation +ARG DEBIAN_FRONTEND=noninteractive + +RUN apt update -y && apt upgrade -y && useradd -m docker +RUN apt install -y --no-install-recommends \ + curl jq build-essential libssl-dev libffi-dev python3 python3-venv python3-dev python3-pip \ + # dot net core dependencies + libicu74 libssl3 libkrb5-3 zlib1g libcurl4 + + +RUN cd /home/docker && mkdir actions-runner && cd actions-runner \ + && curl -O -L https://github.com/actions/runner/releases/download/v${RUNNER_VERSION}/actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz \ + && tar xzf ./actions-runner-linux-x64-${RUNNER_VERSION}.tar.gz + +RUN chown -R docker ~docker && /home/docker/actions-runner/bin/installdependencies.sh + +COPY entrypoint.sh entrypoint.sh + +# make the script executable +RUN chmod +x entrypoint.sh + +# since the config and run script for actions are not allowed to be run by root, +# set the user to "docker" so all subsequent commands are run as the docker user +USER docker + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/.github/runners/runner-x64/docker-compose.yml b/.github/runners/runner-x64/docker-compose.yml new file mode 100644 index 0000000..3ecee8e --- /dev/null +++ b/.github/runners/runner-x64/docker-compose.yml @@ -0,0 +1,18 @@ +# docker-compose.yml + +services: + github-runner: + build: + context: . + args: + RUNNER_VERSION: 2.323.0 + # container_name commented to allow for multiple runners + # container_name: github-runner + env_file: + - .env + volumes: + - runner-work:/home/runner/actions-runner/_work + restart: unless-stopped + +volumes: + runner-work: diff --git a/.github/runners/runner-x64/entrypoint.sh b/.github/runners/runner-x64/entrypoint.sh new file mode 100644 index 0000000..97e8609 --- /dev/null +++ b/.github/runners/runner-x64/entrypoint.sh @@ -0,0 +1,24 @@ +#!/bin/bash + +REPOSITORY=$REPO +ACCESS_TOKEN=$GH_TOKEN +LABELS=$RUNNER_LABELS + +# echo "REPO ${REPOSITORY}" +# echo "ACCESS_TOKEN ${ACCESS_TOKEN}" + +REG_TOKEN=$(curl -X POST -H "Authorization: token ${ACCESS_TOKEN}" -H "Accept: application/vnd.github+json" https://api.github.com/repos/${REPOSITORY}/actions/runners/registration-token | jq .token --raw-output) + +cd /home/docker/actions-runner + +./config.sh --url https://github.com/${REPOSITORY} --token ${REG_TOKEN} --labels ${LABELS} + +cleanup() { + echo "Removing runner..." + ./config.sh remove --unattended --token ${REG_TOKEN} +} + +trap 'cleanup; exit 130' INT +trap 'cleanup; exit 143' TERM + +./run.sh & wait $! \ No newline at end of file diff --git a/.github/runners/runner-x64/example.env b/.github/runners/runner-x64/example.env new file mode 100644 index 0000000..6ab2dc8 --- /dev/null +++ b/.github/runners/runner-x64/example.env @@ -0,0 +1,9 @@ + +# Repository name +REPO="Magnus167/rustframe" + +# GitHub runner token +GH_TOKEN="some_token_here" + +# Labels for the runner +RUNNER_LABELS=self-hosted-linux,linux \ No newline at end of file diff --git a/.github/runners/runner-x64/start.sh b/.github/runners/runner-x64/start.sh new file mode 100644 index 0000000..891f4d4 --- /dev/null +++ b/.github/runners/runner-x64/start.sh @@ -0,0 +1,4 @@ + + +docker compose up -d --build +# docker compose up -d --build --scale github-runner=2 diff --git a/.github/workflows/docs-and-testcov.yml b/.github/workflows/docs-and-testcov.yml index 2307ec2..94eab46 100644 --- a/.github/workflows/docs-and-testcov.yml +++ b/.github/workflows/docs-and-testcov.yml @@ -17,8 +17,23 @@ permissions: pages: write jobs: - docs-and-testcov: + pick-runner: runs-on: ubuntu-latest + outputs: + runner: ${{ steps.choose.outputs.use-runner }} + steps: + - uses: actions/checkout@v4 + + - id: choose + uses: ./.github/actions/runner-fallback + with: + primary-runner: "self-hosted,ubuntu-latest" + fallback-runner: "ubuntu-latest" + github-token: ${{ secrets.GITHUB_TOKEN }} + + docs-and-testcov: + needs: pick-runner + runs-on: ${{ fromJson(needs.pick-runner.outputs.runner) }} steps: - uses: actions/checkout@v4 @@ -28,10 +43,10 @@ jobs: with: toolchain: stable override: true - + - name: Build documentation run: cargo doc --no-deps --release - + - name: Prepare documentation for Pages run: | @@ -45,28 +60,26 @@ jobs: run: | mkdir -p testcov cargo tarpaulin --engine llvm --out Html --out Json - + - name: Check for tarpaulin-report.html run: | if [ ! -f tarpaulin-report.html ]; then echo "tarpaulin-report.html not found!" exit 1 fi - + - name: Export tarpaulin coverage badge JSON + # extract raw coverage and round to 2 decimal places run: | - # extract raw coverage - coverage=$(jq '.coverage' tarpaulin-report.json) - # round to 2 decimal places - formatted=$(printf "%.2f" "$coverage") - # build the badge JSON using the pre-formatted string - jq --arg message "$formatted" \ - '{schemaVersion:1, - label:"tarpaulin-report", - message:$message, - color:"blue"}' \ - tarpaulin-report.json \ - > tarpaulin-badge.json + coverage=$(jq '.coverage' tarpaulin-report.json) + formatted=$(printf "%.2f" "$coverage") + jq --arg message "$formatted" \ + '{schemaVersion:1, + label:"tarpaulin-report", + message:$message, + color:"blue"}' \ + tarpaulin-report.json \ + > tarpaulin-badge.json - name: Save last commit date JSON run: | @@ -78,7 +91,7 @@ jobs: color:"blue"}' \ <(echo '{}') \ > last-commit-date.json - + - name: Copy files to output directory run: | mkdir output diff --git a/.github/workflows/run-benchmarks-self-hosted.yml b/.github/workflows/run-benchmarks-self-hosted.yml new file mode 100644 index 0000000..037eca6 --- /dev/null +++ b/.github/workflows/run-benchmarks-self-hosted.yml @@ -0,0 +1,29 @@ +name: Run benchmarks + +on: + workflow_dispatch: + push: + branches: + - main + +jobs: + run-benchmarks: + runs-on: self-hosted-linux + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Run benchmarks + run: cargo bench + + - name: Upload benchmark reports + uses: actions/upload-artifact@v4 + with: + name: benchmark-reports-${{ github.sha }} + path: ./target/criterion/ diff --git a/.github/workflows/run-benchmarks.yml b/.github/workflows/run-benchmarks.yml new file mode 100644 index 0000000..b05029e --- /dev/null +++ b/.github/workflows/run-benchmarks.yml @@ -0,0 +1,29 @@ +name: Run benchmarks + +on: + workflow_dispatch: + # push: + # branches: + # - main + +jobs: + run-benchmarks: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable + + - name: Run benchmarks + run: cargo bench + + - name: Upload benchmark reports + uses: actions/upload-artifact@v4 + with: + name: benchmark-reports-${{ github.sha }} + path: ./target/criterion/ diff --git a/.github/workflows/run-unit-tests.yml b/.github/workflows/run-unit-tests.yml index 2811fca..c6e2f91 100644 --- a/.github/workflows/run-unit-tests.yml +++ b/.github/workflows/run-unit-tests.yml @@ -11,10 +11,24 @@ concurrency: cancel-in-progress: true jobs: + pick-runner: + runs-on: ubuntu-latest + outputs: + runner: ${{ steps.choose.outputs.use-runner }} + steps: + - uses: actions/checkout@v4 + - id: choose + uses: ./.github/actions/runner-fallback + with: + primary-runner: "self-hosted,ubuntu-latest" + fallback-runner: "ubuntu-latest" + github-token: ${{ secrets.GITHUB_TOKEN }} + run-unit-tests: + needs: pick-runner if: github.event.pull_request.draft == false name: run-unit-tests - runs-on: ubuntu-latest + runs-on: ${{ fromJson(needs.pick-runner.outputs.runner) }} env: CARGO_TERM_COLOR: always @@ -24,12 +38,16 @@ jobs: run: rustup update stable - name: Install cargo-llvm-cov uses: taiki-e/install-action@cargo-llvm-cov - - name: Generate code coverage - run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info - - name: Run doc-tests + + - name: Run doctests run: cargo test --doc --all-features --workspace --release + + - name: Run unit tests with code coverage + run: cargo llvm-cov --all-features --release --workspace --lcov --output-path lcov.info + - name: Test docs generation run: cargo doc --no-deps --release + - name: Upload coverage to Codecov uses: codecov/codecov-action@v3 with: diff --git a/Cargo.lock b/Cargo.lock index 4403b50..36431bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "android-tzdata" version = "0.1.1" @@ -17,18 +26,47 @@ dependencies = [ "libc", ] +[[package]] +name = "anes" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi", + "libc", + "winapi", +] + [[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + [[package]] name = "bumpalo" version = "3.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628fb46dfa0b37568d12e5edd512553eccf6a22a78e8bde00bb4aed84d5bdbf" +[[package]] +name = "cast" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" + [[package]] name = "cc" version = "1.2.19" @@ -58,12 +96,158 @@ dependencies = [ "windows-link", ] +[[package]] +name = "ciborium" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" +dependencies = [ + "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 = "3.2.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" +dependencies = [ + "bitflags", + "clap_lex", + "indexmap", + "textwrap", +] + +[[package]] +name = "clap_lex" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" +dependencies = [ + "os_str_bytes", +] + [[package]] name = "core-foundation-sys" version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "criterion" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" +dependencies = [ + "anes", + "atty", + "cast", + "ciborium", + "clap", + "criterion-plot", + "itertools", + "lazy_static", + "num-traits", + "oorandom", + "plotters", + "rayon", + "regex", + "serde", + "serde_derive", + "serde_json", + "tinytemplate", + "walkdir", +] + +[[package]] +name = "criterion-plot" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" +dependencies = [ + "cast", + "itertools", +] + +[[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 = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + +[[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 = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + [[package]] name = "iana-time-zone" version = "0.1.63" @@ -88,6 +272,31 @@ dependencies = [ "cc", ] +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown", +] + +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +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" @@ -98,6 +307,12 @@ dependencies = [ "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.172" @@ -110,6 +325,12 @@ version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "num-traits" version = "0.2.19" @@ -125,6 +346,46 @@ version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" +[[package]] +name = "oorandom" +version = "11.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6790f58c7ff633d8771f42965289203411a5e5c68388703c06e14f24770b41e" + +[[package]] +name = "os_str_bytes" +version = "6.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" + +[[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 = "proc-macro2" version = "1.0.95" @@ -143,11 +404,61 @@ dependencies = [ "proc-macro2", ] +[[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 = "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 = "rustframe" version = "0.0.1-a.0" dependencies = [ "chrono", + "criterion", ] [[package]] @@ -156,6 +467,53 @@ 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" @@ -173,12 +531,38 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "textwrap" +version = "0.16.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13547615a44dc9c452a8a534638acdf07120d4b6847c8178705da06306a3057" + +[[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 = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" +[[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 = "wasm-bindgen" version = "0.2.100" @@ -237,6 +621,47 @@ 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", +] + +[[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-core" version = "0.61.0" @@ -295,3 +720,76 @@ checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] + +[[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-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 2f181da..c003a95 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,3 +13,10 @@ crate-type = ["cdylib", "lib"] [dependencies] chrono = "^0.4.10" + +[dev-dependencies] +criterion = { version = "0.4", features = ["html_reports"] } + +[[bench]] +name = "benchmarks" +harness = false \ No newline at end of file diff --git a/benches/benchmarks.rs b/benches/benchmarks.rs new file mode 100644 index 0000000..cce0a6b --- /dev/null +++ b/benches/benchmarks.rs @@ -0,0 +1,206 @@ +// Combined benchmarks for rustframe +use chrono::NaiveDate; +use criterion::{criterion_group, criterion_main, Criterion}; +// Import Duration for measurement_time and warm_up_time +use rustframe::{ + frame::{Frame, RowIndex}, + matrix::{BoolMatrix, Matrix}, + utils::{BDateFreq, BDatesList}, +}; +use std::time::Duration; + +// You can define a custom Criterion configuration function +// This will be passed to the criterion_group! macro +pub fn for_short_runs() -> Criterion { + Criterion::default() + // (samples != total iterations) + // limits the number of statistical data points. + .sample_size(50) + // measurement time per sample + .measurement_time(Duration::from_millis(2000)) + // reduce warm-up time as well for faster overall run + .warm_up_time(Duration::from_millis(50)) + // You could also make it much shorter if needed, e.g., 50ms measurement, 100ms warm-up + // .measurement_time(Duration::from_millis(50)) + // .warm_up_time(Duration::from_millis(100)) +} + +fn bool_matrix_operations_benchmark(c: &mut Criterion) { + let sizes = [1, 100, 1000]; + // let sizes = [1000]; + + for &size in &sizes { + let data1: Vec = (0..size * size).map(|x| x % 2 == 0).collect(); + let data2: Vec = (0..size * size).map(|x| x % 3 == 0).collect(); + let bm1 = BoolMatrix::from_vec(data1.clone(), size, size); + let bm2 = BoolMatrix::from_vec(data2.clone(), size, size); + + c.bench_function(&format!("bool_matrix_and ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &bm1 & &bm2; + }); + }); + + c.bench_function(&format!("bool_matrix_or ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &bm1 | &bm2; + }); + }); + + c.bench_function(&format!("bool_matrix_xor ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &bm1 ^ &bm2; + }); + }); + + c.bench_function(&format!("bool_matrix_not ({}x{})", size, size), |b| { + b.iter(|| { + let _result = !&bm1; + }); + }); + } +} + +fn matrix_boolean_operations_benchmark(c: &mut Criterion) { + let sizes = [1, 100, 1000]; + // let sizes = [1000]; + + for &size in &sizes { + let data1: Vec = (0..size * size).map(|x| x % 2 == 0).collect(); + let data2: Vec = (0..size * size).map(|x| x % 3 == 0).collect(); + let bm1 = BoolMatrix::from_vec(data1.clone(), size, size); + let bm2 = BoolMatrix::from_vec(data2.clone(), size, size); + + c.bench_function(&format!("boolean AND ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &bm1 & &bm2; + }); + }); + + c.bench_function(&format!("boolean OR ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &bm1 | &bm2; + }); + }); + + c.bench_function(&format!("boolean XOR ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &bm1 ^ &bm2; + }); + }); + + c.bench_function(&format!("boolean NOT ({}x{})", size, size), |b| { + b.iter(|| { + let _result = !&bm1; + }); + }); + } +} + +fn matrix_operations_benchmark(c: &mut Criterion) { + let sizes = [1, 100, 1000]; + // let sizes = [1000]; + + for &size in &sizes { + let data: Vec = (0..size * size).map(|x| x as f64).collect(); + let ma = Matrix::from_vec(data.clone(), size, size); + + c.bench_function(&format!("scalar add ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma + 1.0; + }); + }); + + c.bench_function(&format!("scalar subtract ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma - 1.0; + }); + }); + + c.bench_function(&format!("scalar multiply ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma * 2.0; + }); + }); + + c.bench_function(&format!("scalar divide ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma / 2.0; + }); + }); + } + + // Benchmarking matrix addition + for &size in &sizes { + let data1: Vec = (0..size * size).map(|x| x as f64).collect(); + let data2: Vec = (0..size * size).map(|x| (x + 1) as f64).collect(); + let ma = Matrix::from_vec(data1.clone(), size, size); + let mb = Matrix::from_vec(data2.clone(), size, size); + + c.bench_function(&format!("matrix add ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma + &mb; + }); + }); + + c.bench_function(&format!("matrix subtract ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma - &mb; + }); + }); + + c.bench_function(&format!("matrix multiply ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma * &mb; + }); + }); + + c.bench_function(&format!("matrix divide ({}x{})", size, size), |b| { + b.iter(|| { + let _result = &ma / &mb; + }); + }); + } +} + +fn benchmark_frame_operations(c: &mut Criterion) { + let n_periods = 1000; + let n_cols = 1000; + let dates: Vec = + BDatesList::from_n_periods("2024-01-02".to_string(), BDateFreq::Daily, n_periods) + .unwrap() + .list() + .unwrap(); + + // let col_names= str(i) for i in range(1, 1000) + let col_names: Vec = (1..=n_cols).map(|i| format!("col_{}", i)).collect(); + + let data1: Vec = (0..n_periods * n_cols).map(|x| x as f64).collect(); + let data2: Vec = (0..n_periods * n_cols).map(|x| (x + 1) as f64).collect(); + let ma = Matrix::from_vec(data1.clone(), n_periods, n_cols); + let mb = Matrix::from_vec(data2.clone(), n_periods, n_cols); + + let fa = Frame::new( + ma.clone(), + col_names.clone(), + Some(RowIndex::Date(dates.clone())), + ); + let fb = Frame::new(mb, col_names, Some(RowIndex::Date(dates))); + + c.bench_function("frame element-wise multiply (1000x1000)", |b| { + b.iter(|| { + let _result = &fa * &fb; + }); + }); +} + +// Define the criterion group and pass the custom configuration function +criterion_group!( + name = combined_benches; + config = for_short_runs(); // Use the custom configuration here + targets = bool_matrix_operations_benchmark, + matrix_boolean_operations_benchmark, + matrix_operations_benchmark, + benchmark_frame_operations +); +criterion_main!(combined_benches);