> ## Documentation Index
> Fetch the complete documentation index at: https://codspeed.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# pytest-codspeed documentation

> A `pytest` plugin for benchmarking performance of Python code

export const BuildkiteIcon = props => <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" width={31} height={31} viewBox="0 0 31 31" fill="none" {...props}>
    <g clipPath="url(#a)">
      <path fill="#30F2A2" d="M1.652 6.448v9.005l9.011 4.509V10.95l-9.01-4.503Zm18.017 0v9.005l9.011-4.502-9.011-4.503Z" />
      <path fill="#14CC80" d="M19.668 15.459v9.006l9.011-4.503V10.95l-9.011 4.508Z" />
      <path fill="#14CC80" d="M10.664 10.95v9.012l9.011-4.503V6.448l-9.01 4.503Z" />
    </g>
    <defs>
      <clipPath id="a">
        <path fill="#fff" d="M1.652 6.448H28.68v18.055H1.652z" />
      </clipPath>
    </defs>
  </svg>;

export const GitLabIcon = props => <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" width={31} height={31} viewBox="0 0 31 31" fill="none" {...props}>
    <g clipPath="url(#a)">
      <path fill="#E24329" d="m29.301 12.446-.041-.105-3.968-10.356a1.034 1.034 0 0 0-1.026-.65c-.217.012-.425.09-.597.224-.17.137-.292.324-.352.534l-2.68 8.197H9.79l-2.68-8.197a1.041 1.041 0 0 0-.352-.536 1.063 1.063 0 0 0-1.214-.065c-.184.117-.327.289-.408.492l-3.976 10.35-.04.106a7.368 7.368 0 0 0 2.445 8.515l.013.011.037.026 6.044 4.526 2.99 2.264 1.822 1.375a1.224 1.224 0 0 0 1.482 0l1.821-1.375 2.99-2.264 6.082-4.553.015-.013a7.371 7.371 0 0 0 2.441-8.506Z" />
      <path fill="#FC6D26" d="m29.302 12.446-.04-.105a13.404 13.404 0 0 0-5.336 2.399l-8.715 6.59 5.551 4.195 6.081-4.554.015-.012a7.371 7.371 0 0 0 2.444-8.513Z" />
      <path fill="#FCA326" d="m9.656 25.525 2.99 2.263 1.822 1.375a1.225 1.225 0 0 0 1.482 0l1.821-1.375 2.99-2.263s-2.586-1.957-5.553-4.196c-2.968 2.24-5.552 4.196-5.552 4.196Z" />
      <path fill="#FC6D26" d="M6.493 14.74a13.386 13.386 0 0 0-5.334-2.405l-.04.105a7.368 7.368 0 0 0 2.445 8.516l.013.01.037.026 6.044 4.527 5.552-4.196-8.717-6.584Z" />
    </g>
    <defs>
      <clipPath id="a">
        <path fill="#fff" d="M.055.097h30.27v30.27H.056z" />
      </clipPath>
    </defs>
  </svg>;

export const GitHubIcon = props => <svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6" viewBox="0 0 32 31" width={32} height={31} fill="none" {...props}>
    <path fill="#F4F4F5" fillRule="evenodd" d="M16.093 1.187c-7.778 0-14.066 6.334-14.066 14.17A14.159 14.159 0 0 0 11.645 28.8c.7.141.955-.305.955-.68 0-.329-.023-1.455-.023-2.628-3.913.845-4.728-1.69-4.728-1.69-.628-1.641-1.56-2.064-1.56-2.064-1.28-.867.093-.867.093-.867 1.42.093 2.166 1.454 2.166 1.454 1.258 2.158 3.284 1.549 4.099 1.173.116-.915.489-1.548.885-1.9-3.121-.329-6.404-1.549-6.404-6.992 0-1.548.558-2.815 1.443-3.8-.14-.352-.629-1.807.14-3.754 0 0 1.188-.376 3.866 1.454a13.524 13.524 0 0 1 3.516-.469c1.188 0 2.399.164 3.517.47 2.678-1.83 3.866-1.455 3.866-1.455.768 1.947.279 3.402.14 3.754.908.985 1.443 2.252 1.443 3.8 0 5.443-3.283 6.64-6.427 6.992.512.445.954 1.29.954 2.627 0 1.9-.023 3.426-.023 3.895 0 .375.257.821.955.68a14.16 14.16 0 0 0 9.618-13.443c.023-7.836-6.288-14.17-14.043-14.17Z" clipRule="evenodd" />
  </svg>;

export const PypiInstall = ({packages}) => {
  return <CodeGroup>
      <code className="language-sh" filename="uv">
      uv add --dev {packages}
      </code>

      <code className="language-sh" filename="poetry">
      poetry add -G dev {packages}
      </code>

      <code className="language-sh" filename="pipenv">
      pipenv install -d {packages}
      </code>

      <code className="language-sh" filename="pip">
      pip install {packages}
      </code>
    </CodeGroup>;
};

<div className="flex flex-row gap-2 items-center">
  <a className="border-none" target="_blank" href="https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml">
    <img className="my-0" noZoom src="https://github.com/CodSpeedHQ/pytest-codspeed/actions/workflows/ci.yml/badge.svg" alt="CI Status" />
  </a>

  <a className="border-none" target="_blank" href="https://pypi.org/project/pytest-codspeed">
    <img className="my-0" noZoom src="https://img.shields.io/pypi/v/pytest-codspeed?color=%2334D058&label=pypi" alt="PyPI Version" />
  </a>

  <img className="my-0" noZoom src="https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-informational.svg" alt="Python Version" />
</div>

## Overview

`pytest-codspeed` is a pytest plugin for measuring and tracking performance of
Python code. It provides benchmarking capabilities with support for both
wall-time and CPU Simulation measurements.

### Installation

<PypiInstall packages="pytest-codspeed" />

### Example Usage

```shellsession title=terminal icon="square-terminal" theme={null}
$ pytest tests/ --codspeed
============================= test session starts ====================
platform darwin -- Python 3.13.0, pytest-7.4.4, pluggy-1.5.0
codspeed: 3.0.0 (enabled, mode: walltime, timer_resolution: 41.7ns)
rootdir: /home/user/codspeed-test, configfile: pytest.ini
plugins: codspeed-3.0.0
collected 1 items

tests/test_sum_squares.py .                                    [ 100%]

                         Benchmark Results
┏━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━━━━┳━━━━━━━━━━┳━━━━━━━━┓
┃     Benchmark  ┃ Time (best) ┃ Rel. StdDev ┃ Run time ┃ Iters  ┃
┣━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━━━━╋━━━━━━━━━━╋━━━━━━━━┫
┃test_sum_squares┃     1,873ns ┃        4.8% ┃    3.00s ┃ 66,930 ┃
┗━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━━━━┻━━━━━━━━━━┻━━━━━━━━┛
=============================== 1 benchmarked ========================
=============================== 1 passed in 4.12s ====================
```

### Command Line Options

<ParamField path="--codspeed">
  Enable the CodSpeed benchmarking plugin for the test session.

  *(This is automatically enabled when running under the CodSpeed runner or from
  the GitHub action)*
</ParamField>

<ParamField path="--codspeed-mode" type="auto | simulation | walltime | memory" default="auto">
  The measurement instrument to use for measuring performance.

  * `auto`: Automatically select the measurement instrument based on the
    environment.
  * `simulation`: Use [the CPU simulation instrument](/instruments/cpu).
  * `walltime`: Use [the wall-clock time instrument](/instruments/walltime).
    Automatically enabled on macro runners.
  * `memory`: Use [the memory instrument](/instruments/memory) to track heap
    allocations.
</ParamField>

<ParamField path="--codspeed-warmup-time" type="number" default={0.0}>
  The time to warm up the benchmark for (in seconds), **only for walltime
  mode**.
</ParamField>

<ParamField path="--codspeed-max-time" type="number" default={1.0}>
  The maximum time to run a benchmark for (in seconds), **only for walltime
  mode**.
</ParamField>

<ParamField path="--codspeed-max-rounds" type="number" default={100}>
  The maximum number of rounds to run a benchmark for, **only for walltime
  mode**.
</ParamField>

<Note>
  All of these walltime-specific command line options can be overridden by more
  specific settings set by the benchmark marker.

  For example, if you set `warmup_time` in the benchmark marker, it will take
  precedence over the `--codspeed-warmup-time` command line option.
</Note>

## Creating Benchmarks

There are multiple ways to mark tests as benchmarks at different levels:

### The `pytest.mark.benchmark` marker

Marking a test with the `pytest.mark.benchmark` marker will automatically mark
it as a benchmark. This means that the entire test function will be measured.
For more fine-grained control, see the
[benchmark fixture](#with-the-benchmark-fixture) section.

```python highlight={3} title="test_sum_powers.py" theme={null}
import pytest

@pytest.mark.benchmark
def test_sum_squares():
    input = [1, 2, 3, 4, 5]
    output = sum(i**2 for i in input)
    assert output == 55
```

You can also mark all the tests contained in a test file as benchmarks by using
the `pytestmark` variable at the module level.

```python highlight={3} title="test_sum_powers.py" theme={null}
import pytest

pytestmark = pytest.mark.benchmark

def test_sum_squares():
    input = [1, 2, 3, 4, 5]
    output = sum(i**2 for i in input)
    assert output == 55

def test_sum_cubes():
    input = [1, 2, 3, 4, 5]
    output = sum(i**3 for i in input)
    assert output == 225
```

### The `benchmark` fixture

When more fine-grained control is needed, the `benchmark` fixture can be used.
This fixture is exposed by the `pytest-codspeed` plugin, allowing to select
exactly the code to be measured.

<Info>
  A fixture is a function that can be used to set up and tear down the state of
  a test. More information about fixtures can be found in the [pytest
  documentation](https://docs.pytest.org/en/6.2.x/fixture.html).
</Info>

#### Direct invocation

The fixture can be used directly in the test function:

```python theme={null}
def test_sum_squares(benchmark):
    data = [1, 2, 3, 4, 5]
    benchmark(sum, data) # Only the `sum` function is measured
```

The fixture behaves as an identity function: calling
`benchmark(target, *args, **kwargs)` will have the same effect as calling
`target(*args, **kwargs)`. The return value will also be passed along to make it
possible to write assertions on the result.

For example:

```python theme={null}
def test_sum_squares(benchmark):
    input = [1, 2, 3, 4, 5]
    output = benchmark(sum, [i**2 for i in input])
    assert output == 55
```

It's also possible to use it with lambda functions:

```python theme={null}
def test_sum_squares(benchmark):
    input = [1, 2, 3, 4, 5]
    output = benchmark(lambda: sum(i**2 for i in input))
    assert output == 55
```

#### As a decorator

If you want to measure a block of code containing multiple function calls, you
can use the fixture as a decorator:

```python theme={null}
def test_sum_squares_cubes(benchmark):
    input = [1, 2, 3, 4, 5]
    @benchmark
    def measured_function():
        squares = sum(i**2 for i in input)
        cubes = sum(i**3 for i in input)
        return squares + cubes
```

<Tip>
  When using the fixture, the marker is not necessary anymore, except if you
  want to customize the execution.
</Tip>

<Warning>
  **The benchmark fixture can only be used once per test function**.

  For example, the following code will raise an error:

  ```python theme={null}
  def test_invalid(benchmark):
      benchmark(func1)  # OK
      benchmark(func2)  # ERROR: RuntimeError
  ```
</Warning>

### Benchmark options

The `@pytest.mark.benchmark` marker accepts several options to customize the
benchmark execution:

<ParamField path="group" type="string">
  The group name to use for the benchmark. This can be useful to organize related benchmarks together.

  {/* TODO(COD-141): remove this */} *(Will be supported soon in the UI)*
</ParamField>

<ParamField path="min_time" type="number" default={0.0}>
  The minimum time of a round (in seconds). Only available in walltime mode.
</ParamField>

<ParamField path="max_time" type="number" default={1.0}>
  The maximum time to run the benchmark for (in seconds). Only available in
  walltime mode.
</ParamField>

<ParamField path="max_rounds" type="number" default={100}>
  The maximum number of rounds to run the benchmark for. Takes precedence over
  max\_time. Only available in walltime mode.
</ParamField>

Example usage:

```python theme={null}
@pytest.mark.benchmark(
    group="sorting",
    min_time=0.1,
    max_time=1.0,
    max_rounds=100
)
def test_sorting_algorithm(benchmark):
    data = [1, 2, 3, 4, 5]
    benchmark(quicksort, data)
```

<Note>
  The `min_time`, `max_time` and `max_rounds` options are only available in
  walltime mode. When using CPU simulation mode (Valgrind), these options are
  ignored.
</Note>

### Pedantic mode (advanced)

For fine-grained control over benchmark execution protocol, you can use the
`benchmark.pedantic` method.

For example:

```python theme={null}
def test_pedantic_mode(benchmark):
    def setup():
        # Setup code that shouldn't be measured
        data = list(range(1000))
        return (data,), {}  # Returns (args, kwargs) for target

    def target(data):
        # Code to benchmark
        return sorted(data)

    def teardown(data):
        # Cleanup code that shouldn't be measured
        data.clear()

    result = benchmark.pedantic(
        target,
        setup=setup,
        teardown=teardown,
        rounds=5,          # Number of rounds to run
        warmup_rounds=1    # Number of warmup rounds
    )
```

The `benchmark.pedantic` method accepts the following parameters:

<ParamField path="target" required>
  The function to benchmark. This is the main code that will be measured.
</ParamField>

<ParamField path="args" type="tuple[Any, ...]" default="()">
  Positional arguments to pass to the target function.
</ParamField>

<ParamField path="kwargs" type="dict[str, Any]" default="{}">
  Keyword arguments to pass to the target function.
</ParamField>

<ParamField path="setup" type="Callable | None" default="None">
  Optional setup function that runs before each round. If it returns a tuple of
  (args, kwargs), these will be passed to the target function.
</ParamField>

<ParamField path="teardown" type="Callable | None" default="None">
  Optional teardown function that runs after each round. Receives the same
  arguments as the target function.
</ParamField>

<ParamField path="warmup_rounds" type="int" default="0">
  Number of warmup rounds to run before the actual benchmark. These rounds are
  not included in the measurements.
</ParamField>

<ParamField path="rounds" type="int" default="1">
  Number of rounds to run the benchmark for.

  <Info>This parameter is ignored when using the CPU simulation mode.</Info>
</ParamField>

<ParamField path="iterations" type="int" default="1">
  Number of iterations to run within each round. The total number of executions will be $rounds \times iterations$.

  <Info>This parameter is ignored when using the CPU simulation mode.</Info>
</ParamField>

## Recipes

### Parametrized benchmarks

`pytest-codspeed` fully supports pytest's parametrization out of the box:

```python theme={null}
import pytest

@pytest.mark.parametrize("size", [10, 100, 1000])
def test_parametrized_benchmark(benchmark, size):
    data = list(range(size))
    benchmark(sum, data)
```

For complex parameters or values that may change over time, attach explicit ids
so each case keeps a stable, readable name across runs:

```python theme={null}
@pytest.mark.parametrize(
    "size",
    [10, 100, 1000],
    ids=["small", "medium", "large"],
)
def test_parametrized_benchmark(benchmark, size):
    data = list(range(size))
    benchmark(sum, data)
```

See
[Naming Parametrized Cases](/guides/how-to-benchmark-python-with-pytest#naming-parametrized-cases)
in the pytest guide for the full rationale.

## Compatibility

`pytest-codspeed` is designed to be fully backward compatible with
[pytest-benchmark](https://pypi.org/project/pytest-benchmark/).

<Info>
  You can use both plugins in the same project, though only one will be active
  at a time.
</Info>

## Running the benchmarks continuously

To run the benchmarks continuously in your CI, you can use `pytest-codspeed`
along with the CodSpeed runner.

We have first-class support for the following CI providers:

<Columns cols={2}>
  <Card horizontal title="GitHub Actions" href="/integrations/ci/github-actions" icon={<GitHubIcon />} />

  <Card horizontal title="GitLab CI" href="/integrations/ci/gitlab-ci" icon={<GitLabIcon />} />

  <Card horizontal title="Buildkite" href="/integrations/ci/buildkite" icon={<BuildkiteIcon />} />
</Columns>

<Info>
  If your provider is not listed here, please [open an
  issue](https://github.com/CodSpeedHQ/codspeed) or contact us on
  [Discord](https://discord.com/invite/MxpaCfKSqF).
</Info>
