Files
wickra/.github/workflows/bench.yml
T
kingchenc 5b23b36261 ci: pin CI dependency installs by hash (Scorecard PinnedDependencies) (#114)
* ci: use npm ci instead of npm install for reproducible installs

Pins the node binding dependency install to the committed package-lock.json
integrity hashes (OpenSSF Scorecard PinnedDependencies). npm ci installs
strictly from the lockfile; npm install could resolve newer patch versions.
Covers ci.yml and both release.yml node steps.

* ci: hash-pin Python dev tooling in ci.yml (Scorecard #19)

Replaces the unpinned 'pip install maturin pytest numpy hypothesis' with a
hash-locked '--require-hashes -r' install (OpenSSF Scorecard PinnedDependencies).

Two lock files are needed because numpy publishes no single release with wheels
for both cp39 and cp313 (<=2.0.2 has cp39 only, >=2.1 drops cp39):
  ci-dev-py39.txt  numpy 2.0.2  (Python 3.9, + tomli/exceptiongroup)
  ci-dev-py3.txt   numpy 2.4.6  (Python 3.10+)

The step selects the file by matrix.python-version under shell: bash. Both are
generated from ci-dev.in via uv (scripts/update-lockfiles.sh, added next).

* ci: hash-pin Python deps in bench.yml (Scorecard #16)

Replaces the unpinned 'pip install maturin numpy pandas talipp finta' with a
hash-locked '--require-hashes -r .github/requirements/bench.txt' install.
bench.yml runs on a single Python version (3.11), so one lock file (generated
from bench.in via uv) is sufficient.

* build: add scripts/update-lockfiles.sh to regenerate all lockfiles

One command refreshes every committed lockfile across languages: Cargo.lock and
fuzz/Cargo.lock (cargo update), the Node binding package-lock.json, and the
hash-pinned Python requirements under .github/requirements/ (uv pip compile
--generate-hashes). Uses uv for the Python locks so a target Python version's
hashed transitive closure can be resolved without that interpreter installed
(needed for the numpy cp39/cp313 split); bootstraps uv if absent.

.gitattributes pins *.sh to LF so the script stays runnable on Linux/macOS.

* ci: split ci-dev requirements per Python version + Dependabot rehash

Splits the single ci-dev.in into ci-dev-py39.in (numpy <2.1, the last series
with cp39 wheels) and ci-dev-py3.in (3.10+), giving a 1:1 .in->.txt layout.
The cap keeps Python 3.9 permanently installable and stops Dependabot from
proposing 3.9-breaking numpy bumps.

Adds a Dependabot pip entry on /.github/requirements so the hash-locked tooling
is kept current automatically; the canonical manual refresh stays
scripts/update-lockfiles.sh. Only the '# via -r' provenance lines in the .txt
change; no package versions or hashes move.

* ci: cache pip and npm downloads in the PR-loop jobs

Adds setup-python cache: pip (ci.yml python matrix + bench.yml, keyed on the
hash-locked requirements) and setup-node cache: npm (ci.yml node job, keyed on
bindings/node/package-lock.json), on both the primary and retry setup steps.

Scoped to jobs that actually install dependencies; the examples-smoke and
clippy-bindings jobs install nothing and are left uncached. release.yml is
intentionally left out: it runs only on tag push (not the PR loop) and is the
publish-critical path, so no caching is added there.

* docs: document hash-pinned requirements and update-lockfiles.sh

Updates the lockfile-policy table: the bindings/python row no longer claims CI
installs tooling unpinned, and a new .github/requirements row documents the
hash-locked CI/bench tooling and the per-Python-version ci-dev split. Adds a
paragraph pointing contributors at scripts/update-lockfiles.sh (uv-based,
self-bootstrapping) as the canonical lockfile refresh.

* docs: changelog entry for hash-pinned CI dependency installs
2026-06-01 17:58:31 +02:00

118 lines
4.6 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Cross-library benchmark
# Audit finding R10: previously the cross-library benchmark ran on every push
# and every pull-request to `main`, adding 510 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- uses: dtolnay/rust-toolchain@29eef336d9b2848a0b548edc03f92a220660cdb8 # stable branch, 2026-03-27
- 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