如何在GitHub Actions中使用Bencher


on:
  push:
    branches: main

jobs:
  benchmark_with_bencher:
    name: Continuous Benchmarking with Bencher
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
      BENCHER_TESTBED: ubuntu-latest
      BENCHER_ADAPTER: json
    steps:
      - uses: actions/checkout@v3
      - uses: bencherdev/bencher@main
      - name: Track Benchmarks with Bencher
        run: |
          bencher run \
          --branch main \
          --err \
          "bencher mock"
  1. 创建一个GitHub Actions的workflow文件(例如:.github/workflows/benchmark.yml)。
  2. pushmain分支的事件上运行。请查看GitHub Actions的on文档了解完整概览。请参阅下面的拉取请求
  3. 创建一个GitHub Actions的job(例如:benchmark_with_bencher)。
  4. 必须已经存在项目。设置--project标志或BENCHER_PROJECT环境变量为项目的缩写或UUID(例如:BENCHER_PROJECT: save-walter-white)。
  5. 必须已经存在API令牌。将BENCHER_API_TOKEN添加为存储库密钥。(例如:Repo -> Settings -> Secrets and variables -> Actions -> New repository secret)设置--token标志或BENCHER_API_TOKEN环境变量为API令牌。(例如:BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
  6. 可选:设定--testbed标志或BENCHER_TESTBED环境变量为测试平台的缩写或UUID。(例如:BENCHER_TESTBED: ubuntu-latest)测试平台必须已经存在。如果未设置此项,则将使用localhost测试平台。
  7. 可选:设定--adapter标志或BENCHER_ADAPTER环境变量为所需的适配器名称。(例如:BENCHER_ADAPTER: json)如果未设置此项,则将使用magic适配器。参见性能基准测试马甲适配器了解完整概览。
  8. Checkout你的源码。(例如:uses: actions/checkout@v3
  9. 使用GitHub Action安装Bencher CLI。(例如:uses: bencherdev/bencher@main
  10. bencher run CLI子命令跟踪你的性能基准测试
    1. 可选:设定--branch标志或BENCHER_BRANCH环境变量为分支的缩写或UUID(例如:--branch main)。分支必须已经存在。如果未设置此项,则将使用main分支。
    2. 如果生成了一个阈值,则设定命令失败。(例如:--err
    3. 运行你的性能基准测试并从结果中生成报告(例如:“bencher mock”)

拉取请求

为了在拉取请求中捕获性能回归,你需要在PRs上运行你的性能基准测试。如果你只期望同一存储库的分支有PRs,那么你可以简单地修改上面的例子,也在pull_request事件上运行。

⚠️ 这个方案只有在所有PRs都来自同一存储库时才能工作! 请参阅下面的来自Forks的拉取请求

on:
  push:
    branches: main
  pull_request:

jobs:
  benchmark_with_bencher:
    name: Continuous Benchmarking with Bencher
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
      BENCHER_TESTBED: ubuntu-latest
      BENCHER_ADAPTER: json
    steps:
      - uses: actions/checkout@v3
      - uses: bencherdev/bencher@main
      - name: Track Benchmarks with Bencher
        run: |
          bencher run \
          --if-branch "$GITHUB_REF_NAME" \
          --else-if-branch "$GITHUB_BASE_REF" \
          --else-if-branch main \
          --github-actions ${{ secrets.GITHUB_TOKEN }} \
          --err \
          "bencher mock"
  1. pushmain分支和pull_request事件上运行。限制只有在push 到指定的分支(例如:main)时运行是非常重要的,以防止PR分支的推送运行两次!
  2. 与其总是使用main分支,不如使用GitHub Action默认环境变量来:
    1. 如果已经存在,使用当前分支的数据。(例如:--if-branch "$GITHUB_REF_NAME"
    2. 如果存在,则克隆PR目标分支的数据和阀值。(例如:--else-if-branch "$GITHUB_BASE_REF"
    3. 否则,克隆main分支的数据和阀值。(例如:--else-if-branch main
    4. 设置项目分支有多种选择。请参阅分支选择了解完整概览。
  3. 设定GitHub API认证令牌。(例如:--github-actions ${{ secrets.GITHUB_TOKEN }})。当这个选项作为拉取请求的一部分被设定时,结果将以评论的形式添加到拉取请求中。这使用了GitHub Actions的GITHUB_TOKEN环境变量
  4. 参阅bencher run文档了解所有配置拉取请求评论的方式,使用--ci-*标志。

来自Forks的拉取请求

如果你计划接受来自forks的拉取请求,这常常发生在公共开源项目中,那么你需要稍微处理得不同。出于安全原因,诸如BENCHER_API_TOKENGITHUB_TOKEN等密钥在fork PRs的GitHub Actions中是无效的。也就是说,如果一个外部贡献者从一个fork打开一个PR,以上的例子是无法工作的。针对fork PRs有三个选择:

从目标分支对Fork PR进行基准测试

on:
  push:
    branches: main
  pull_request_target:

jobs:
  benchmark_main_with_bencher:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    name: Benchmark main with Bencher
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
      BENCHER_TESTBED: ubuntu-latest
      BENCHER_ADAPTER: json
    steps:
      - uses: actions/checkout@v3
      - uses: bencherdev/bencher@main
      - name: Track Benchmarks with Bencher
        run: |
          bencher run \
          --branch main \
          --err \
          "bencher mock"

  benchmark_pr_with_bencher:
    if: github.event_name == 'pull_request_target'
    name: Benchmark PR with Bencher
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_ADAPTER: json
      BENCHER_TESTBED: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          persist-credentials: false
      - uses: bencherdev/bencher@main
      - name: Track Benchmarks with Bencher
        run: |
          bencher run \
          --if-branch "${{ github.event.pull_request.head.ref }}" \
          --else-if-branch "${{ github.event.pull_request.base.ref }}" \
          --else-if-branch main \
          --github-actions "${{ secrets.GITHUB_TOKEN }}" \
          --token "${{ secrets.BENCHER_API_TOKEN }}" \
          --err \
          "bencher mock"
  1. pushmain分支和pull_request_target事件上运行。
  2. 创建一个仅在pushmain分支的事件上运行的job。除去if条件,这个job与以上的原始示例几乎相同。
  3. 创建一个仅在pull_request_target事件上运行的job
    1. 统计拉取请求的分支。
    2. 直接进入所有的密钥。使用--token ${{ secrets.BENCHER_API_TOKEN }}代替BENCHER_API_TOKEN环境变量。
    3. bencher run运行并追踪你的拉取请求的性能基准测试。

这种设置有效是因为pull_request_target在拉取请求的目标分支的上下文中运行,密钥如BENCHER_API_TOKENGITHUB_TOKEN是有效的。因此,只有在_target_分支上存在此工作流时,此工作流才会运行。避免将任何密钥设置为环境变量,例如BENCHER_API_TOKEN。相反,应明确地将API令牌传递给bencher run(例如:--token ${{ secrets.BENCHER_API_TOKEN }})。详情请参见此篇GitHub安全实验室拾写此博客文章对防止pwn请求的完整概览。

带有必要审查者从目标分支对Fork PR进行基准测试

on:
  push:
    branches: main
  pull_request_target:

jobs:
  benchmark_main_with_bencher:
    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
    name: Benchmark main with Bencher
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
      BENCHER_TESTBED: ubuntu-latest
      BENCHER_ADAPTER: json
    steps:
      - uses: actions/checkout@v3
      - uses: bencherdev/bencher@main
      - name: Track Benchmarks with Bencher
        run: |
          bencher run \
          --branch main \
          --err \
          "bencher mock"

  benchmark_pr_requires_review:
    if: github.event_name == 'pull_request_target'
    environment:
      ${{ (github.event.pull_request.head.repo.full_name == github.repository && 'internal') || 'external' }}
    runs-on: ubuntu-latest
    steps:
      - run: true

  benchmark_pr_with_bencher:
    needs: benchmark_pr_requires_review
    name: Benchmark PR with Bencher
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_ADAPTER: json
      BENCHER_TESTBED: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
        with:
          ref: ${{ github.event.pull_request.head.sha }}
          repository: ${{ github.event.pull_request.head.repo.full_name }}
          persist-credentials: false
      - uses: bencherdev/bencher@main
      - name: Track Benchmarks with Bencher
        run: |
          bencher run \
          --if-branch "${{ github.event.pull_request.head.ref }}" \
          --else-if-branch "${{ github.event.pull_request.base.ref }}" \
          --else-if-branch main \
          --github-actions "${{ secrets.GITHUB_TOKEN }}" \
          --token "${{ secrets.BENCHER_API_TOKEN }}" \
          --err \
          "bencher mock"

这个设置与从目标分支对Fork PR进行基准测试完全相同,只不过在每次fork拉取请求运行之前需要必要审查者的批准。相同存储库的拉取请求不需要批准。为了设定,你需要创建两个GitHub Action环境(例如:Repo -> Settings -> Environments -> New environment)。internal环境应没有Deployment protection rules。然而,external环境应设定Required reviewers为对fork PRs进行基准测试前信任的审查者。

对Fork PR进行基准测试并从默认分支上传

name: Run and Cache Benchmarks

on: pull_request

jobs:
  benchmark:
    name: Run Benchmarks
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3
      - name: Mock Benchmark
        run: echo '{"bencher::mock_0": { "latency": { "value": 1.0 }}}' &> benchmark_results.txt
      - uses: actions/upload-artifact@v3
        with:
          name: benchmark_results.txt
          path: ./benchmark_results.txt
      - uses: actions/upload-artifact@v3
        with:
          name: pr_event.json
          path: ${{ env.GITHUB_EVENT_PATH }}
name: Track Benchmarks

on:
  workflow_run:
    workflows: [Run and Cache Benchmarks]
    types:
      - completed

jobs:
  track_with_bencher:
    if: ${{ github.event.workflow_run.conclusion == 'success' }}
    runs-on: ubuntu-latest
    env:
      BENCHER_PROJECT: save-walter-white
      BENCHER_API_TOKEN: ${{ secrets.BENCHER_API_TOKEN }}
      BENCHER_ADAPTER: json
      BENCHER_TESTBED: ubuntu-latest
      BENCHMARK_RESULTS: benchmark_results.txt
      PR_EVENT: pr_event.json
    steps:
      - name: Download Benchmark Results
        uses: actions/github-script@v6
        with:
          script: |
            function downloadArtifact(artifactName) {
              let allArtifacts = await github.rest.actions.listWorkflowRunArtifacts({
                owner: context.repo.owner,
                repo: context.repo.repo,
                run_id: context.payload.workflow_run.id,
              });
              let matchArtifact = allArtifacts.data.artifacts.filter((artifact) => {
                return artifact.name == artifactName
              })[0];
              if (!matchArtifact) {
                core.setFailed(`Failed to find artifact: ${artifactName}`);
              }
              let download = await github.rest.actions.downloadArtifact({
                owner: context.repo.owner,
                repo: context.repo.repo,
                artifact_id: matchArtifact.id,
                archive_format: 'zip',
              });
              let fs = require('fs');
              fs.writeFileSync(`${process.env.GITHUB_WORKSPACE}/${artifactName}.zip`, Buffer.from(download.data));
            }
            downloadArtifact(process.env.BENCHMARK_RESULTS);
            downloadArtifact(process.env.PR_EVENT);
      - name: Unzip Benchmark Results
        run: |
          unzip $BENCHMARK_RESULTS.zip
          unzip $PR_EVENT.zip
      - name: Export PR Context
        uses: actions/github-script@v6
        with:
          script: |
            let fs = require('fs');
            let prEvent = JSON.parse(fs.readFileSync(process.env.PR_EVENT, {encoding: 'utf8'}));
            fs.appendFileSync(process.env.GITHUB_ENV, `PR_NUMBER=${prEvent.number}`);
            fs.appendFileSync(process.env.GITHUB_ENV, `PR_HEAD=${prEvent.pull_request.head.ref}`);
            fs.appendFileSync(process.env.GITHUB_ENV, `PR_BASE=${prEvent.pull_request.base.ref}`);
      - uses: bencherdev/bencher@main
          - name: Track Benchmarks with Bencher
            run: |
              bencher run \
              --if-branch "${{ env.PR_HEAD }}" \
              --else-if-branch "${{ env.PR_BASE }}" \
              --else-if-branch main \
              --github-actions "${{ secrets.GITHUB_TOKEN }}" \
              --ci-number "${{ env.PR_NUMBER }}" \
              --err \
              --file $BENCHMARK_RESULTS
  1. 创建一个Run and Cache Benchmarks工作流程文件。
  2. pull_request事件上运行你的性能基准测试。
  3. 保存性能基准测试到文件并作为artifact上传。
  4. 上传pull_request事件作为artifact。
  5. 创建第二个工作流文件,Track Benchmarks
  6. 使用workflow_run事件Track Benchmarks链到Run and Cache Benchmarks
  7. 从缓存的pull_request事件中提取必要数据。
  8. bencher run追踪缓存的性能基准测试结果。
  9. 创建第三个工作流文件并使用上述的初始示例在pushmain分支的事件上运行。

此设置方式有效是因为workflow_run在存储库的默认分支的上下文中运行,密钥如BENCHER_API_TOKENGITHUB_TOKEN是有效的。因此,这些工作流只有在默认分支上存在时才会运行。请参阅使用触发工作流程的数据了解完整概览。在初次工作流中使用的拉取请求数、头部分支和基地分支必须明确地传入,因为他们在workflow_run里是不可用的。



🐰 恭喜!你已经学会如何在GitHub Actions中使用Bencher!🎉


继续深入: 性能基准测试概览 ➡

🤖 该文档由 OpenAI GPT-4 自动生成。 它可能不准确并且可能包含错误。 如果您发现任何错误,请在 GitHub 上提出问题.