name: Cross-library benchmark

# Audit finding R10: previously the cross-library benchmark ran on every push
# and every pull-request to `main`, adding 5–10 minutes of build + bench time
# per CI run with no consumer of the resulting artefact. The benchmark is now
# scheduled (nightly at 03:00 UTC) and on-demand via `workflow_dispatch`. The
# CI pipeline proper (.github/workflows/ci.yml) still verifies build / tests /
# lints on every push and pull-request.
on:
  schedule:
    # Nightly at 03:00 UTC. Pick a slot well away from common European /
    # American working-hours pushes to keep this off the critical path.
    - cron: "0 3 * * *"
  workflow_dispatch:
    inputs:
      size:
        description: "Number of bars per indicator (default 20000)"
        required: false
        default: "20000"
      iterations:
        description: "Batch iterations per indicator (default 10)"
        required: false
        default: "10"

# Least-privilege default for the auto-injected GITHUB_TOKEN. The single job
# only builds and uploads an artifact (upload-artifact uses the artifact
# storage API, not the contents scope), so it never needs repo write (OpenSSF
# Scorecard: Token-Permissions).
permissions:
  contents: read

env:
  CARGO_TERM_COLOR: always
  # Network-flake resilience: retry transient registry/DNS failures at the tool
  # level so a blip fetching crates.io / PyPI inside any build step (cargo,
  # maturin, pip) retries automatically instead of failing the job. Cargo treats
  # "couldn't resolve host" / connect / timeout as spurious and retries with
  # backoff; 10 attempts ride out a transient DNS blip on a runner.
  CARGO_NET_RETRY: "10"
  CARGO_NET_GIT_FETCH_WITH_CLI: "true"
  npm_config_fetch_retries: "5"
  npm_config_fetch_retry_maxtimeout: "120000"
  PIP_RETRIES: "5"
  PIP_DEFAULT_TIMEOUT: "120"

jobs:
  cross-library-bench:
    name: Cross-library benchmark report
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false

      - uses: ./.github/actions/setup-rust

      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
        timeout-minutes: 6

      - name: Set up Python
        id: setup_python
        continue-on-error: true
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.11"
          cache: pip
          cache-dependency-path: .github/requirements/bench.txt

      - name: Wait before Python retry
        if: steps.setup_python.outcome == 'failure'
        shell: bash
        run: |
          echo "::warning::setup-python failed (likely CDN flake), waiting 30s before retry..."
          sleep 30

      - name: Set up Python (retry)
        if: steps.setup_python.outcome == 'failure'
        uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
        with:
          python-version: "3.11"
          cache: pip
          cache-dependency-path: .github/requirements/bench.txt

      - name: Install Python deps + peer libs
        run: |
          python -m pip install --upgrade pip
          # Hash-locked deps (OpenSSF Scorecard PinnedDependencies). bench.yml
          # runs on a single Python version (3.11), so one lock file suffices.
          python -m pip install --require-hashes -r .github/requirements/bench.txt

      - name: Build Wickra wheel
        working-directory: bindings/python
        run: maturin build --release --out dist

      - name: Install Wickra wheel
        working-directory: bindings/python
        run: python -m pip install --find-links dist --force-reinstall wickra

      - name: Run cross-library benchmark
        working-directory: bindings/python
        # workflow_dispatch inputs are untrusted; pass them through the
        # environment and quote them rather than interpolating into the shell
        # command (OpenSSF Scorecard: Dangerous-Workflow).
        env:
          BENCH_SIZE: ${{ github.event.inputs.size || '20000' }}
          BENCH_ITERATIONS: ${{ github.event.inputs.iterations || '10' }}
        run: |
          python -m benchmarks.compare_libraries \
            --size "$BENCH_SIZE" \
            --iterations "$BENCH_ITERATIONS" \
            --streaming-window 5000 --streaming-iterations 2 \
            | tee benchmark.txt

      - name: Upload report
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: cross-library-bench
          path: bindings/python/benchmark.txt

  rust-cross-bench:
    name: Rust cross-library benchmark report
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
        with:
          persist-credentials: false

      - uses: ./.github/actions/setup-rust

      - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
        continue-on-error: true # cache is an optimisation; never block on a stuck/slow restore
        timeout-minutes: 6

      # Wickra vs the other Rust TA crates (kand, ta-rs, yata) on an identical
      # candle series — the like-for-like engine comparison with no binding
      # overhead. Streaming + batch, in crates/wickra-bench/benches/cross_lib.rs.
      - name: Run Rust cross-library benchmark
        run: cargo bench -p wickra-bench --bench cross_lib | tee rust_cross_bench.txt

      - name: Upload Rust report
        uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
        with:
          name: rust-cross-bench
          path: rust_cross_bench.txt
