Documentation

Submit benchmark data with one CLI and the framework your team already uses.

Perfall accepts benchmark artifacts from seven supported adapters. Install perfall-cli from PyPI once, generate the artifact for your stack, and upload it with your project API key.

  • PyPI install
  • 7 supported adapters
  • PR regression gate

Quickstart

1. Install the upload CLI

Install perfall-cli once from PyPI on the machine that will publish benchmark results.

pip install perfall-cli

2. Create a project and copy its API key

Open the Projects page, create a project, and reveal the API key when you are ready to wire your benchmark job.

3. Generate one supported artifact

Pick the framework below that matches your stack and run its export command to create a JSON or JSONL artifact.

4. Upload the artifact

Run perfall ingest <adapter> with the artifact path and your project API key, then return after the next run to compare trends.

Fastest first signal

Want to confirm the CLI, API key, and dashboard path before wiring a real benchmark suite? Download the sample pytest-benchmark artifact and upload it with your project API key.

perfall ingest pytest-benchmark \
  --file pytest-benchmark-sample.json \
  --api-key "$PERFALL_API_KEY"

Supported frameworks

Choose the adapter that matches your benchmark output

The adapters below map directly to the current perfall-cli command surface. Use the section links to jump to the copy-paste commands for your stack.

Python

pytest-benchmark

Use pytest-benchmark's JSON export when your benchmark suite already lives in pytest.

Minimal benchmark
def test_sort(benchmark):
    values = [3, 1, 2]
    benchmark(sorted, values)
Generate artifact
pytest --benchmark-only --benchmark-json benchmark.json
Upload to Perfall
perfall ingest pytest-benchmark --file benchmark.json --api-key "$PERFALL_API_KEY"
  • Install the benchmark plugin in your project with `pip install pytest-benchmark`.
  • Perfall reads the JSON generated by pytest-benchmark's `--benchmark-json` option.

Go

go test benchmarks

Use Go's built-in benchmark support and upload the JSON event stream from `go test -bench`.

Minimal benchmark
func BenchmarkSort(b *testing.B) {
    for i := 0; i < b.N; i++ {
        values := []int{3, 1, 2}
        sort.Ints(values)
    }
}
Generate artifact
go test -bench . -benchmem -json ./... > go-benchmark.jsonl
Upload to Perfall
perfall ingest go-test --file go-benchmark.jsonl --api-key "$PERFALL_API_KEY"
  • Go benchmarks are part of the standard `testing` package, so you do not need a separate benchmark framework.
  • Perfall maps the common `ns/op`, `B/op`, and `allocs/op` metrics from the JSON output.

Java

JMH

Use JMH when you need stable JVM benchmark output and export the run as JSON.

Minimal benchmark
@State(Scope.Thread)
public class SortBenchmark {
    @Benchmark
    public int[] sort() {
        int[] values = {3, 1, 2};
        Arrays.sort(values);
        return values;
    }
}
Generate artifact
java -jar benchmarks.jar -rf json -rff jmh-result.json
Upload to Perfall
perfall ingest jmh --file jmh-result.json --api-key "$PERFALL_API_KEY"
  • Set up JMH in your Maven or Gradle build so you can run a benchmark jar before using the command above.
  • Perfall reads JMH primary metrics and secondary metrics from the JSON artifact.

Rust

Criterion.rs via cargo-criterion

Use Criterion.rs with cargo-criterion when you want a machine-readable JSONL stream.

Minimal benchmark
fn sort_benchmark(c: &mut Criterion) {
    c.bench_function("sort_u64", |b| {
        b.iter(|| {
            let mut values = vec![3_u64, 1, 2];
            values.sort();
        })
    });
}
Generate artifact
cargo criterion --message-format=json > cargo-criterion.jsonl
Upload to Perfall
perfall ingest criterion --file cargo-criterion.jsonl --api-key "$PERFALL_API_KEY"
  • Install cargo-criterion once with `cargo install cargo-criterion` if your environment does not already provide it.
  • Perfall reads one JSON object per line from the cargo-criterion output stream.

JavaScript / TypeScript

Vitest benchmark mode

Use Vitest benchmark mode when you want benchmark cases alongside your existing JS or TS tests.

Minimal benchmark
import { bench, describe } from 'vitest';

describe('sort', () => {
  bench('sort numbers', () => {
    [3, 1, 2].sort();
  });
});
Generate artifact
npx vitest bench --outputJson vitest-benchmark.json
Upload to Perfall
perfall ingest vitest --file vitest-benchmark.json --api-key "$PERFALL_API_KEY"
  • Install Vitest in your project with `npm install -D vitest`.
  • Perfall reads the JSON written by Vitest's `--outputJson` option.

C++

Google Benchmark

Use Google Benchmark JSON output when your native benchmark suite already builds a benchmark binary.

Minimal benchmark
static void BM_Sort(benchmark::State& state) {
  for (auto _ : state) {
    std::array<int, 3> values{3, 1, 2};
    std::sort(values.begin(), values.end());
  }
}
BENCHMARK(BM_Sort);
BENCHMARK_MAIN();
Generate artifact
./benchmarks --benchmark_out=google-benchmark.json --benchmark_out_format=json
Upload to Perfall
perfall ingest google-benchmark --file google-benchmark.json --api-key "$PERFALL_API_KEY"
  • Build your benchmark binary with Google Benchmark enabled before running the export command.
  • Perfall reads both timing fields and numeric counter fields from the generated JSON.

.NET

BenchmarkDotNet

Use BenchmarkDotNet with a JSON exporter when you want structured benchmark output from a .NET project.

Minimal benchmark
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Exporters.Json;

[JsonExporterAttribute.Brief]
public class MyBenchmarks
{
    [Benchmark]
    public void Sort()
    {
        var values = new[] { 3, 1, 2 };
        Array.Sort(values);
    }
}
Generate artifact
dotnet run -c Release --project benchmarks.csproj
Upload to Perfall
perfall ingest benchmarkdotnet --file BenchmarkDotNet.Artifacts/results/MyBenchmarks-report-brief.json --api-key "$PERFALL_API_KEY"
  • Add BenchmarkDotNet to your project with `dotnet add package BenchmarkDotNet` and include a JSON exporter attribute in your benchmark class.
  • Perfall reads the generated `*-report-brief.json` artifact.

CI regression gate

Fail CI when a benchmark regresses past your threshold

Perfall can upload the current run, compare it with the median of five recent compatible baseline-branch samples for each metric, print a concise summary, and exit non-zero when a regression exceeds your configured percentage. Latency and cost metrics are lower-is-better, while throughput metrics are higher-is-better.

CLI example
perfall ingest pytest-benchmark \
  --file benchmark.json \
  --api-key "$PERFALL_API_KEY" \
  --baseline-branch main \
  --baseline-window 5 \
  --fail-on-regression-percent 5
GitHub Actions example
name: Perfall benchmark gate

on:
  push:
    branches: [main]
  pull_request:

permissions:
  contents: read
  checks: write
  issues: write

jobs:
  benchmarks:
    runs-on: ubuntu-latest
    env:
      PERFALL_API_KEY: ${{ secrets.PERFALL_API_KEY }}
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: "3.12"
      - id: perfall_ingest
        run: |
          python -m pip install --upgrade pip
          python -m pip install perfall-cli pytest pytest-benchmark
          pytest --benchmark-only --benchmark-json benchmark.json
      - run: |
          if [ "$GITHUB_EVENT_NAME" = "pull_request" ]; then
            perfall ingest pytest-benchmark \
              --file benchmark.json \
              --api-key "$PERFALL_API_KEY" \
              --baseline-branch "${{ github.event.pull_request.base.ref }}" \
              --baseline-window 5 \
              --fail-on-regression-percent 5
          else
            perfall ingest pytest-benchmark \
              --file benchmark.json \
              --api-key "$PERFALL_API_KEY"
          fi
      - name: Publish Perfall benchmark check
        if: ${{ always() }}
        uses: actions/github-script@v7
        env:
          PERFALL_DASHBOARD_URL: ${{ steps.perfall_ingest.outputs.perfall_dashboard_url }}
          PERFALL_PR_COMMENT: ${{ steps.perfall_ingest.outputs.perfall_pr_comment_markdown }}
        with:
          script: |
            const summary = process.env.PERFALL_PR_COMMENT || 'Perfall did not publish a benchmark verdict.';
            const failed = summary.includes('Regression gate: **FAILED**');
            const warming = summary.includes('insufficient history');
            await github.rest.checks.create({
              owner: context.repo.owner, repo: context.repo.repo,
              name: 'Perfall benchmark', head_sha: context.sha, status: 'completed',
              conclusion: failed ? 'failure' : warming ? 'neutral' : 'success',
              details_url: process.env.PERFALL_DASHBOARD_URL || undefined,
              output: { title: failed ? 'Regression threshold exceeded' : warming ? 'Warming up: no comparable baseline' : 'Regression gate passed', summary }
            });
          
  • For GitLab CI, copy gitlab-ci.perfall-benchmark-example.yml to .gitlab-ci.yml (or include its job) and store PERFALL_API_KEY as a masked variable in Settings > CI/CD > Variables. GitLab merge-request pipelines use CI_MERGE_REQUEST_TARGET_BRANCH_NAME; default-branch pipelines upload history without a gate.
  • Required secret: PERFALL_API_KEY.
  • The public starter installs perfall-cli from PyPI directly and does not depend on any repo-local GitHub Action files.
  • The gate uses the median of the five most recent compatible samples on the requested baseline branch for the same identifier. It also reports the range and relative spread: (max - min) / |median| × 100. Spread at or below 5% is stable; higher spread is variable. A variable baseline never suppresses a threshold breach. A metric with fewer than five samples has insufficient history and warms up without failing; use --baseline-window 1 only when you intentionally want the latest-sample comparison.
  • Inside GitHub Actions, Perfall automatically isolates baselines by the runner OS, architecture, and CPU model. The resolved testbed appears in the job log and step summary; a runner change intentionally starts a fresh warm-up.
  • For pull-request workflows, set baseline-branch to the PR base branch, such as GitHub Actions github.event.pull_request.base.ref.
  • The public starter upserts one marked Perfall PR comment per pull request and publishes a stable Perfall benchmark Check Run linked to the branch-filtered dashboard. It needs only contents: read, issues: write, and checks: write; the check runs with always() so a failed threshold still leaves review evidence. A breach fails the check, while insufficient baseline history is shown as a neutral warming-up check.
  • When the threshold is exceeded, the CLI exits non-zero and names the failing metric, current value, baseline value, and percent delta.
  • When the threshold is not exceeded, the CLI still prints the comparison summary so the benchmark decision stays visible in CI logs.
  • Normalized throughput metrics such as ops/s, B/s, and items/s are higher-is-better; a drop beyond the threshold fails the gate.

Manual API fallback

The CLI is the recommended path, but direct integrations can POST normalized JSON to the ingest API when they need more control.

Endpoint
POST https://perfall.it/ingest/v1/samples
Minimal payload
{
  "api_key": "project-api-key",
  "run": {
    "tool": "pytest-benchmark",
    "branch": "main"
  },
  "samples": [
    {
      "identifier": "bench.sort.mean",
      "value": 10.4,
      "timestamp": "2026-03-19T05:30:00+00:00"
    }
  ]
}
  • Required fields: api_key and at least one samples[] entry.
  • Each sample needs an identifier and a numeric value.
  • Success responses include status, project_id, accepted_samples, and identifiers.
  • Quota rejection uses the deterministic error code account_daily_quota_exceeded.
  • Advanced fallback endpoint: POST https://perfall.it/ingest/submit.

Need an API key before you can upload?

Create an account, create a project, and copy its API key from the Projects page before running the upload command for your framework.