|
| 1 | +name: Refresh comparison benchmark |
| 2 | + |
| 3 | +on: |
| 4 | + schedule: |
| 5 | + # First of the month; the table tracks other projects' releases, not ours. |
| 6 | + - cron: '0 4 1 * *' |
| 7 | + workflow_dispatch: |
| 8 | + inputs: |
| 9 | + requests: |
| 10 | + description: Requests per run |
| 11 | + default: '2000' |
| 12 | + runs: |
| 13 | + description: Runs per client, the median is reported |
| 14 | + default: '11' |
| 15 | + # Exercise the harness whenever it changes, without proposing anything. |
| 16 | + pull_request: |
| 17 | + paths: |
| 18 | + - benchmarks/** |
| 19 | + - .github/workflows/comparison-benchmark.yaml |
| 20 | + |
| 21 | +permissions: |
| 22 | + contents: read |
| 23 | + |
| 24 | +concurrency: |
| 25 | + group: comparison-benchmark |
| 26 | + cancel-in-progress: false |
| 27 | + |
| 28 | +env: |
| 29 | + BRANCH: benchmarks/comparison-refresh |
| 30 | + REQUESTS: ${{ inputs.requests || '2000' }} |
| 31 | + RUNS: ${{ inputs.runs || '11' }} |
| 32 | + |
| 33 | +jobs: |
| 34 | + benchmark: |
| 35 | + name: Measure |
| 36 | + runs-on: ubuntu-latest |
| 37 | + timeout-minutes: 60 |
| 38 | + steps: |
| 39 | + # No credentials in this job: it installs and executes unpinned releases of |
| 40 | + # every client, so it must have nothing worth stealing. Reading a public |
| 41 | + # repository needs no token. |
| 42 | + - name: Checkout |
| 43 | + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 |
| 44 | + with: |
| 45 | + ref: ${{ github.event_name == 'pull_request' && github.sha || 'master' }} |
| 46 | + persist-credentials: false |
| 47 | + |
| 48 | + - name: Setup Node.js |
| 49 | + uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7 |
| 50 | + with: |
| 51 | + node-version: 24 |
| 52 | + |
| 53 | + - name: Setup uv |
| 54 | + uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1 |
| 55 | + |
| 56 | + # No lockfiles anywhere here on purpose: the point is to compare whatever |
| 57 | + # each project publishes today. |
| 58 | + - name: Install the Node.js clients |
| 59 | + run: npm install --prefix benchmarks/node --no-audit --no-fund --no-package-lock |
| 60 | + |
| 61 | + - name: Install the Python clients |
| 62 | + run: | |
| 63 | + uv venv --seed --python 3.12 benchmarks/python/.venv |
| 64 | + uv pip install --python benchmarks/python/.venv/bin/python -r benchmarks/python/requirements.txt |
| 65 | +
|
| 66 | + - name: Benchmark the Node.js clients |
| 67 | + run: node benchmarks/node/bench.mjs --requests "$REQUESTS" --runs "$RUNS" |
| 68 | + |
| 69 | + - name: Benchmark the Python clients |
| 70 | + run: benchmarks/python/.venv/bin/python benchmarks/python/bench.py --requests "$REQUESTS" --runs "$RUNS" |
| 71 | + |
| 72 | + - name: Rewrite the README table |
| 73 | + run: node benchmarks/update-readme.mjs |
| 74 | + |
| 75 | + - name: Upload the rewritten README and raw measurements |
| 76 | + uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7 |
| 77 | + if: always() |
| 78 | + with: |
| 79 | + name: comparison-results |
| 80 | + path: | |
| 81 | + README.md |
| 82 | + benchmarks/results-*.json |
| 83 | +
|
| 84 | + # Separate runner, so the token never shares an environment with third-party |
| 85 | + # code. This job installs nothing. |
| 86 | + propose: |
| 87 | + name: Open a pull request |
| 88 | + needs: benchmark |
| 89 | + if: github.event_name != 'pull_request' |
| 90 | + runs-on: ubuntu-latest |
| 91 | + steps: |
| 92 | + - name: Checkout |
| 93 | + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 |
| 94 | + with: |
| 95 | + ref: master |
| 96 | + token: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} |
| 97 | + |
| 98 | + - name: Download the rewritten README |
| 99 | + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8 |
| 100 | + with: |
| 101 | + name: comparison-results |
| 102 | + |
| 103 | + - name: Open a pull request |
| 104 | + env: |
| 105 | + GH_TOKEN: ${{ secrets.APIFY_SERVICE_ACCOUNT_GITHUB_TOKEN }} |
| 106 | + run: | |
| 107 | + # Without this, an artifact that unpacked somewhere unexpected would |
| 108 | + # look exactly like "the numbers did not move". |
| 109 | + test -f benchmarks/results-node.json || { echo 'the artifact did not unpack where expected'; exit 1; } |
| 110 | +
|
| 111 | + if git diff --quiet -- README.md; then |
| 112 | + echo "The measurements did not move the table; nothing to propose." |
| 113 | + exit 0 |
| 114 | + fi |
| 115 | +
|
| 116 | + git config user.name 'apify-service-account' |
| 117 | + git config user.email 'apify-service-account@users.noreply.github.com' |
| 118 | + git checkout -B "$BRANCH" |
| 119 | + git commit -m 'docs: refresh the client comparison benchmark' -- README.md |
| 120 | + git push --force origin "$BRANCH" |
| 121 | +
|
| 122 | + if [ -z "$(gh pr list --head "$BRANCH" --state open --json number --jq '.[].number')" ]; then |
| 123 | + gh pr create --base master --head "$BRANCH" \ |
| 124 | + --title 'docs: refresh the client comparison benchmark' \ |
| 125 | + --body "Measured by \`.github/workflows/comparison-benchmark.yaml\`, $RUNS runs of $REQUESTS requests per client. Raw numbers are attached to [the run](${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/actions/runs/${GITHUB_RUN_ID})." |
| 126 | + else |
| 127 | + echo "The existing pull request now points at the new measurements." |
| 128 | + fi |
0 commit comments