Skip to content

Support user-defined benchmark suites. #109

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 127 commits into from
Dec 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
509d93c
benchmarks -> _benchmarks
ericsnowcurrently Jun 18, 2021
e16141d
Use a new API for pyperformance.benchmarks.
ericsnowcurrently Jun 18, 2021
d2101b2
Deal with benchmark objects instead of names.
ericsnowcurrently Jun 21, 2021
20b2c23
Refactor select_benchmarks().
ericsnowcurrently Jun 21, 2021
20e8492
Add and use the default manifest file.
ericsnowcurrently Jun 21, 2021
4f74cb8
Move run_perf_script() back.
ericsnowcurrently Jun 22, 2021
3917d22
Clean up benchmark/__init__.py.
ericsnowcurrently Jun 22, 2021
32e15ec
Make the utils a package.
ericsnowcurrently Jun 22, 2021
b041e2e
Move each of the default benchmarks into its own directory.
ericsnowcurrently Jun 23, 2021
d79eba5
Make BenchmarkSpec.metafile as "secondary" attribute.
ericsnowcurrently Jun 23, 2021
f30421d
Fix benchmark selection.
ericsnowcurrently Jun 23, 2021
043836b
Fix the default benchmarks selection.
ericsnowcurrently Jun 24, 2021
6ba9603
Fix the run script filename.
ericsnowcurrently Jun 24, 2021
0f15e8e
Run benchmarks from the metadata instead of hard-coded.
ericsnowcurrently Jun 24, 2021
91c27f9
Fix the requirements.
ericsnowcurrently Jun 24, 2021
a4f97ad
Pass "name" through to parse_pyproject_toml().
ericsnowcurrently Jun 25, 2021
6d385bf
Leave a note about classifiers.
ericsnowcurrently Jun 25, 2021
5308b29
Drop an unused file.
ericsnowcurrently Jun 26, 2021
4c8a18f
Load manifest and select benchmarks before running the command.
ericsnowcurrently Jun 28, 2021
0f63a54
Fix Benchmark.__repr__().
ericsnowcurrently Jun 28, 2021
c7a92f4
Fix a default arg in load_metadata().
ericsnowcurrently Jun 28, 2021
c45ece7
Fix the packaging data.
ericsnowcurrently Jun 29, 2021
e940e24
Support per-benchmark venvs in VirtualEnvironment.
ericsnowcurrently Jun 29, 2021
0420e96
Ignore pyproject.toml name only if provided.
ericsnowcurrently Jun 30, 2021
6bce386
Add requirements lock files to the benchmarks.
ericsnowcurrently Jun 30, 2021
29cf228
Make a venv for each benchmark instead of sharing one.
ericsnowcurrently Jun 30, 2021
6cadadc
Support "libsdir" in metadata.
ericsnowcurrently Jul 20, 2021
3fb5187
Fix an error message.
ericsnowcurrently Jul 20, 2021
abf56e9
Use the default resolve() if the default manifest is explicit.
ericsnowcurrently Jul 20, 2021
4bf223a
Merge in the version properly.
ericsnowcurrently Jul 20, 2021
9acbbe4
Preserve PYTHONPATH (with libsdir) when invoking pyperf via benchmark…
ericsnowcurrently Jul 20, 2021
d06aa7b
Add a note about using an upstream lib for parsing pyproject.toml.
ericsnowcurrently Jul 20, 2021
e48d29b
Use the full benchmark version rather than the canonicalized form.
ericsnowcurrently Jul 20, 2021
31f6021
Add iter_clean_lines() to _utils.
ericsnowcurrently Jul 22, 2021
4b13645
Use a run ID when running benchmarks.
ericsnowcurrently Jul 22, 2021
b045e9a
Finish implementing "pre" and "post" script support.
ericsnowcurrently Jul 22, 2021
54b8ba0
Drop "pre" and "post" script support. (It isn't necessary.)
ericsnowcurrently Jul 22, 2021
362bb6e
Use the correct venv name for each benchmark.
ericsnowcurrently Jul 22, 2021
e4fc5d4
Stop supporting "libsdir" in the benchmark metadata.
ericsnowcurrently Jul 22, 2021
9816103
Clean up run_perf_script() and related code.
ericsnowcurrently Jul 22, 2021
b7f90f3
Add a missing import.
ericsnowcurrently Sep 30, 2021
7d42a40
Drop accidental files.
ericsnowcurrently Oct 4, 2021
bb599e9
Install all dependencies when running tests.
ericsnowcurrently Oct 4, 2021
df30711
Temporarily get tests passing on Windows.
ericsnowcurrently Oct 4, 2021
8d563f9
Ignore stdlib_dir mismatch for now.
ericsnowcurrently Oct 4, 2021
d05b07c
Move requirements.txt into the data dir.
ericsnowcurrently Nov 2, 2021
701c910
Move the benchmarks to the data dir.
ericsnowcurrently Nov 2, 2021
afafda6
Move _pythoninfo out of the _utils dir.
ericsnowcurrently Nov 2, 2021
cf4679a
Move _pyproject_toml out of the _utils dir.
ericsnowcurrently Nov 2, 2021
d3bdaad
Make _utils a single module.
ericsnowcurrently Nov 2, 2021
8f0b3e3
Move benchmarks.* up to the top level.
ericsnowcurrently Nov 2, 2021
3ce65a6
Move benchmark.* up to the top level.
ericsnowcurrently Nov 2, 2021
3479e27
Clean up the metadata files.
ericsnowcurrently Nov 2, 2021
35e0c7a
Do not import _manifest in itself.
ericsnowcurrently Nov 2, 2021
0fe268a
Drop version and origin from the manifest.
ericsnowcurrently Nov 2, 2021
698dec9
Add a project-level symlink to the default benchmarks.
ericsnowcurrently Nov 2, 2021
f3c6c4b
Names starting with a digit.
ericsnowcurrently Nov 2, 2021
0987842
Drop the base metadata file.
ericsnowcurrently Nov 2, 2021
2c99d0f
All extra project fields even if there is a metabase.
ericsnowcurrently Nov 2, 2021
121a281
Fix a typo.
ericsnowcurrently Nov 2, 2021
0f9d202
Allow specifying the supported groups in the manifest.
ericsnowcurrently Nov 2, 2021
3d19edf
Add tags to all the benchmarks.
ericsnowcurrently Nov 2, 2021
7270c76
Use the tags to get the groups in the default manifest.
ericsnowcurrently Nov 3, 2021
efeb77e
Show the default group before the others.
ericsnowcurrently Nov 3, 2021
3b3de1d
Drop an outdated comment.
ericsnowcurrently Nov 3, 2021
8ceb990
Allow excluding benchmarks in a group.
ericsnowcurrently Nov 3, 2021
0f10859
Finish _init_metadata().
ericsnowcurrently Nov 3, 2021
9e3124d
metabase -> inherits
ericsnowcurrently Nov 3, 2021
62f7c23
Document the manifest and benchmark formats.
ericsnowcurrently Nov 3, 2021
05bb86f
Fix some typos.
ericsnowcurrently Nov 6, 2021
318720f
Print some diagnostic info on error.
ericsnowcurrently Nov 6, 2021
b51042b
Fall back to metadata for version.
ericsnowcurrently Nov 6, 2021
5eeae8e
Add benchmarks to the default group instead of names.
ericsnowcurrently Nov 6, 2021
0dc395a
Install the requirements, even if the venv already exists.
ericsnowcurrently Nov 6, 2021
6fb7403
Only re-install reqs for benchmark venvs.
ericsnowcurrently Nov 8, 2021
4979d5b
Support an "includes" section in the manifest.
ericsnowcurrently Nov 9, 2021
7f7b571
doc fixes
ericsnowcurrently Nov 9, 2021
0addec2
"all" and "default" are always valid groups.
ericsnowcurrently Nov 9, 2021
57e7070
Do not import pyperformance._manifest unless already installed.
ericsnowcurrently Nov 9, 2021
c55d690
Ensure we run in a venv when needed.
ericsnowcurrently Nov 9, 2021
01697d5
Do not re-install the shared venv.
ericsnowcurrently Nov 9, 2021
94ac28a
Only list the "all" and "default" groups once.
ericsnowcurrently Nov 9, 2021
f4b09bb
Add the manifest to the "compile" config.
ericsnowcurrently Nov 9, 2021
b56b25a
Adjust the stdlib_dir check.
ericsnowcurrently Nov 9, 2021
f8338ae
Be sure to set base_executable.
ericsnowcurrently Nov 9, 2021
dd4597b
Use the --venv opt to the "compile" command.
ericsnowcurrently Nov 15, 2021
e22eeb8
Separate the logic for create vs. recreate.
ericsnowcurrently Nov 15, 2021
6bb3a9e
Do not re-create the venv if already running in it.
ericsnowcurrently Nov 15, 2021
9c664ff
Ensure all requirements are always isntalled.
ericsnowcurrently Nov 15, 2021
91c09d3
Do not buffer stdout during tests.
ericsnowcurrently Nov 15, 2021
7a33956
Fix a check.
ericsnowcurrently Nov 15, 2021
62e2014
Do not buffer stdout during tests.
ericsnowcurrently Nov 15, 2021
d7ea256
Always switch to a venv if running out of the repo.
ericsnowcurrently Nov 15, 2021
8ed6fd5
Distinguish message from runtests.py.
ericsnowcurrently Nov 15, 2021
ce6d09e
Pass values into VirtualEnvironment instead of the options object.
ericsnowcurrently Nov 15, 2021
4048199
Do not buffer stdout during tests.
ericsnowcurrently Nov 15, 2021
df8cccf
Distinguish message from runtests.py.
ericsnowcurrently Nov 15, 2021
8719636
Print out the --venv option.
ericsnowcurrently Nov 15, 2021
3c5d7da
Print out the --venv option.
ericsnowcurrently Nov 15, 2021
14761b2
Print out the --venv option.
ericsnowcurrently Nov 15, 2021
b975c51
Do not add args directly to the "venv" command.
ericsnowcurrently Nov 16, 2021
062745b
Be explicit about "create".
ericsnowcurrently Nov 16, 2021
d918429
Drop debug messages.
ericsnowcurrently Nov 16, 2021
5842d48
Resolve the manifest file in the compile config.
ericsnowcurrently Nov 16, 2021
f1f9db1
Add a "dryrun" mode for testing "compile".
ericsnowcurrently Nov 16, 2021
35166b7
Add BenchmarkManifest.show().
ericsnowcurrently Nov 16, 2021
561d271
Use --manifest and --benchmarks when creating venv for "compile".
ericsnowcurrently Nov 16, 2021
6831508
Add the resolve_file() util.
ericsnowcurrently Nov 16, 2021
bb88341
Resolve the manifest file in includes.
ericsnowcurrently Nov 16, 2021
1e51f8e
Default BenchmarkRevision._dryrun to False.
ericsnowcurrently Nov 16, 2021
ef231fe
Set the default for --benchmarks manually.
ericsnowcurrently Nov 16, 2021
648bd18
Allow the "venv" command to not install benchmark requirements.
ericsnowcurrently Nov 16, 2021
7bb8d94
Use <NONE> as a marker for "no benchmarks".
ericsnowcurrently Nov 16, 2021
15fd559
Require --benchmarks (or default) for some commands.
ericsnowcurrently Nov 16, 2021
f041503
Do not always install the first benchmark venv.
ericsnowcurrently Nov 16, 2021
452b541
Separate creating venv from installing requirements.
ericsnowcurrently Nov 16, 2021
b049a4a
Only install per-benchmark requirements when running them.
ericsnowcurrently Nov 16, 2021
a083cd5
Print the benchmark number.
ericsnowcurrently Nov 16, 2021
7b18753
Factor out Python.resolve_program().
ericsnowcurrently Nov 17, 2021
c8a7789
Do not pass --benchmarks when creating venv for "compile".
ericsnowcurrently Nov 17, 2021
6f6df4d
Skip a benchmark if its requirements could not be installed.
ericsnowcurrently Nov 17, 2021
b379536
Set Python.program to None if the resolved path does not exist.
ericsnowcurrently Nov 17, 2021
686a96d
Factor out resolve_python().
ericsnowcurrently Nov 17, 2021
664a909
Add a blank line.
ericsnowcurrently Nov 17, 2021
9899813
Do not print a traceback for skipped benchmarks.
ericsnowcurrently Nov 17, 2021
da1b6c3
Fix a typo.
ericsnowcurrently Nov 17, 2021
09ffb6b
Merge main.
ericsnowcurrently Dec 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
335 changes: 335 additions & 0 deletions BENCHMARKS_FORMAT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,335 @@
# The pyperformance File Formats

`pyperformance` uses two file formats to identify benchmarks:

* manifest - a set of benchmarks
- metadata - a single benchmark

For each benchmark, there are two required files and several optional
ones. Those files are expected to be in a specific directory structure
(unless customized in the metadata).

The structure (see below) is such that it's easy to maintain
a benchmark (or set of benchmarks) on GitHub and distribute it on PyPI.
It also simplifies publishing a Python project's benchmarks.
The alternative is pointing people at a repo.

Benchmarks can inherit metadata from other metadata files.
This is useful for keeping common metadata for a set of benchmarks
(e.g. "version") in one file. Likewise, benchmarks for a Python
project can inherit metadata from the project's pyproject.toml.

Sometimes a benchmark will have one or more variants that run using
the same script. Variants like this are supported by `pyperformance`
without requiring much extra effort.


## Benchmark Directory Structure

Normally a benchmark is structured like this:

```
bm_NAME/
data/ # if needed
requirements.txt # lock file, if any
pyproject.toml
run_benchmark.py
```

(Note the "bm\_" prefix on the directory name.)

"pyproject.toml" holds the metadata. "run_benchmark.py" holds
the actual benchmark code. Both are necessary.

`pyperformance` treats the metadata file as the fundamental source of
information about a benchmark. A manifest for a set of benchmarks is
effectively a mapping of names to metadata files. So a metadata file
is essential. It can be located anywhere on disk. However, if it
isn't located in the structure described above then the metadata must
identify where to find the other files.

Other than that, only a benchmark script (e.g. "run_benchmark.py" above)
is required. All other files are optional.

When a benchmark has variants, each has its own metadata file next to
the normal "pyproject.toml", named "bm_NAME.toml". (Note the "bm\_" prefix.)
The format of variant metadata files is exactly the same. `pyperformance`
treats them the same, except that the sibling "pyproject.toml" is
inherited by default.


## Manifest Files

A manifest file identifies a set of benchmarks, as well as (optionally)
how they should be grouped. `pyperformance` uses the manifest to
determine which benchmarks are available to run (and thus which to run
by default).

A manifest normally looks like this:

```
[benchmarks]

name metafile
bench1 somedir/bm_bench1/pyproject.toml
bench2 somedir/pyproject.toml
bench3 ../anotherdir
```

The "benchmarks" section is a table with rows of tab-separated-values.
The "name" value is how `pyperformance` will identify the benchmark.
The "metafile" value is where `pyperformance` will look for the
benchmark's metadata. If a metafile is a directory then it looks
for "pyproject.toml" in that directory.


### Benchmark Groups

The other sections in the manifest file relate to grouping:

```
[benchmarks]

name metafile
bench1 somedir/bm_bench1
bench2 somedir/bm_bench2
bench3 anotherdir/mybench.toml

[groups]
tag1
tag2

[group default]
bench2
bench3

[group tricky]
bench2
```

The "groups" section specifies available groups that may be identified
by benchmark tags (see about tags in the metadata section below). Any
other group sections in the manifest are automatically added to the list
of available groups.

If no "default" group is specified then one is automatically added with
all benchmarks from the "benchmarks" section in it. If there is no
"groups" section and no individual group sections (other than "default")
then the set of all tags of the known benchmarks is treated as "groups".
A group named "all" as also automatically added which has all known
benchmarks in it.

Benchmarks can be excluded from a group by using a `-` (minus) prefix.
Any benchmark alraedy in the list (at that point) that matches will be
dropped from the list. If the first entry in the section is an
exclusion then all known benchmarks are first added to the list
before the exclusion is applied.

For example:

```
[benchmarks]

name metafile
bench1 somedir/bm_bench1
bench2 somedir/bm_bench2
bench3 anotherdir/mybench.toml

[group default]
-bench1
```

This means by default only "bench2" and "bench3" are run.


### Merging Manifests

To combine manifests, use the `[includes]` section in the manifest:

```
[includes]
project1/benchmarks/MANIFEST
project2/benchmarks/MANIFEST
<default>
```

Note that `<default>` is the same as including the manifest file
for the default pyperformance benchmarks.


### A Local Benchmark Suite

Often a project will have more than one benchmark that it will treat
as a suite. `pyperformance` handles this without any extra work.

In the dirctory holding the manifest file put all the benchmarks. Then
put `<local>` in the "metafile" column, like this:

```
[benchmarks]

name metafile
bench1 <local>
bench2 <local>
bench3 <local>
bench4 <local>
bench5 <local>
```

It will look for `DIR/bm_NAME/pyproject.toml`.

If there are also variants, identify the main benchmark
in the "metafile" value, like this:

```
[benchmarks]

name metafile
bench1 <local>
bench2 <local>
bench3 <local>
variant1 <local:bench3>
variant2 <local:bench3>
```

`pyperformance` will look for `DIR/bm_BASE/bm_NAME.toml`, where "BASE"
is the part after "local:".


### A Project's Benchmark Suite

A Python project can identify its benchmark suite by putting the path
to the manifest file in the project's top-level pyproject.toml.
Additional manifests can be identified as well.

```
[tool.pyperformance]
manifest = "..."
manifests = ["...", "..."]
```

(Reminder: that is the pyproject.toml, not the manifest file.)


## Benchmark Metadata Files

A benchmark's metadata file (usually pyproject.toml) follows the format
specified in [PEP 621](https://www.python.org/dev/peps/pep-0621) and
[PEP 518](https://www.python.org/dev/peps/pep-0518). So there are two
supported sections in the file: "project" and "tool.pyperformance".

A typical metadata file will look something like this:

```
[project]
version = "0.9.1"
dependencies = ["pyperf"]
dynamic = ["name"]

[tool.pyperformance]
name = "my_benchmark"
```

A highly detailed one might look like this:

```
[project]
name = "pyperformance_bm_json_dumps"
version = "0.9.1"
description = "A benchmark for json.dumps()"
requires-python = ">=3.8"
dependencies = ["pyperf"]
urls = {repository = "https://github.com/python/pyperformance"}
dynamic = ["version"]

[tool.pyperformance]
name = "json_dumps"
tags = "serialize"
runscript = "bench.py"
datadir = ".data-files/extras"
extra_opts = ["--special"]
```


### Inheritance

For one benchmark to inherit from another (or from common metadata),
the "inherits" field is available:

```
[project]
dependencies = ["pyperf"]
dynamic = ["name", "version"]

[tool.pyperformance]
name = "my_benchmark"
inherits = "../common.toml"
```

All values in either section of the inherited metadata are treated
as defaults, on top of which the current metadata is applied. In the
above example, for instance, a value for "version" in common.toml would
be used here.

If the "inherits" value is a directory (even for "..") then
"base.toml" in that directory will be inherited.

For variants, the base pyproject.toml is the default value for "inherits".


### Inferred Values

In some situations, omitted values will be inferred from other available
data (even for required fields).

* `project.name` <= `tool.pyperformance.name`
* `project.*` <= inherited metadata (except for "name" and "dynamic")
* `tool.pyperformance.name` <= metadata filename
* `tool.pyperformance.*` <= inherited metadata (except for "name" and "inherits")

When the name is inferred from the filename for a regularly structured
benchmark, the "bm\_" prefix is removed from the benchmark's directory.
If it is a variant that prefix is removed from the metadata filename,
as well as the .toml suffix.


### The `[project]` Section

| field | type | R | T | B | D |
|----------------------|-------|---|---|---|---|
| project.name | str | X | X | | |
| project.version | ver | X | | X | X |
| project.dependencies | [str] | | | X | |
| project.dynamic | [str] | | | | |

"R": required
"T": inferred from the tool section
"B": inferred from the inherited metadata
"D": for default benchmarks, inferred from pyperformance

"dynamic" is required by PEP 621 for when a field will be filled in
dynamically by the tool. This is especially important for required
fields.

All other PEP 621 fields are optional (e.g. `requires-python = ">=3.8"`,
`{repository = "https://github.com/..."}`).


### The `[tool.pyperformance]` Section

| field | type | R | B | F |
|-----------------|-------|---|---|---|
| tool.name | str | X | | X |
| tool.tags | [str] | | X | |
| tool.extra_opts | [str] | | X | |
| tool.inherits | file | | | |
| tool.runscript | file | | X | |
| tool.datadir | file | | X | |

"R": required
"B": inferred from the inherited metadata
"F": inferred from filename

* tags: optional list of names to group benchmarks
* extra_opts: optional list of args to pass to `tool.runscript`
* runscript: the benchmark script to use instead of run_benchmark.py.
6 changes: 6 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,16 @@ include COPYING
include MANIFEST.in
include README.rst
include TODO.rst
include requirements.in
include requirements.txt
include runtests.py
include pyperformance
include tox.ini

include doc/*.rst doc/images/*.png doc/images/*.jpg
include doc/conf.py doc/Makefile doc/make.bat

include pyperformance/data-files/requirements.txt
include pyperformance/data-files/benchmarks/MANIFEST
include pyperformance/data-files/benchmarks/base.toml
recursive-include pyperformance/data-files/benchmarks/bm_*/* *
1 change: 1 addition & 0 deletions benchmarks
3 changes: 3 additions & 0 deletions doc/benchmark.conf.sample
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ install = True
# Run "sudo python3 -m pyperf system tune" before running benchmarks?
system_tune = True

# --manifest option for 'pyperformance run'
manifest =

# --benchmarks option for 'pyperformance run'
benchmarks =

Expand Down
12 changes: 12 additions & 0 deletions pyperformance/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,14 @@
import os.path


VERSION = (1, 0, 3)
__version__ = '.'.join(map(str, VERSION))


PKG_ROOT = os.path.dirname(__file__)
DATA_DIR = os.path.join(PKG_ROOT, 'data-files')


def is_installed():
parent = os.path.dirname(PKG_ROOT)
return os.path.exists(os.path.join(parent, 'setup.py'))
Loading