Skip to content

Commit 77124f2

Browse files
authored
Merge pull request #382 from faster-cpython/refactor-to-python
Refactor to do more work in Python
2 parents 2147aad + 9684fa4 commit 77124f2

22 files changed

+711
-429
lines changed

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
## Unreleased
2+
3+
## v2.0.0
4+
5+
Most of the work has moved from GitHub Actions `.yml` files to Python code in `workflow.py`.
6+
In the future, this will allow supporting more workflow engines beyond just GitHub Actions.
7+
8+
**Migration note**: After running `python -m bench_runner install` to update your local files, but sure to add the new `workflow_bootstrap.py` file to your git repository.
9+
10+
### New configuration
11+
12+
Runners have a new configuration `use_cores` to control the number of CPU cores
13+
used to build CPython. By default, this will use all available cores, but some
14+
Cloud VMs require using fewer.

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,12 @@ If you don't want a machine to be included when the user selects "machine == 'al
9393
include_in_all = false
9494
```
9595

96+
You may limit the number of cores used to build Python with the `use_cores` option. This may be necessary, for example, on cloud VMs.
97+
98+
```
99+
use_cores = 2
100+
```
101+
96102
### Try a benchmarking run
97103

98104
There are instructions for running a benchmarking action already in the `README.md` of your repo. Look there and give it a try!

bench_runner/__main__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@
1414
"Get the merge base of the selected commit, and determine if it should run"
1515
),
1616
"install": "Install the workflow files into a results repository",
17+
"notify": "Send a notification about the completion of the workflow",
1718
"profiling_plot": "Generate the profiling plots from raw data",
1819
"purge": "Purge old results from a results repository",
1920
"remove_benchmark": "Remove specific benchmarks from the data set",
2021
"run_benchmarks": "Run benchmarks (in timing, pyperf or perf modes)",
21-
"should_run": "Determine whether we need to rerun results for the current commit",
2222
"synthesize_loops_file": "Create a loops file from multiple benchmark results",
23-
"notify": "Send a notification about the completion of the workflow",
23+
"workflow": "Run the full compile/benchmark workflow",
2424
}
2525

2626
if __name__ == "__main__":

bench_runner/benchmark_definitions.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
from __future__ import annotations
2+
3+
4+
import dataclasses
5+
import hashlib
6+
from pathlib import Path
7+
8+
9+
from . import git
10+
11+
12+
@dataclasses.dataclass
13+
class BenchmarkRepo:
14+
hash: str
15+
url: str
16+
dirname: str
17+
18+
19+
BENCHMARK_REPOS = [
20+
BenchmarkRepo(
21+
"56d12a8fd7cc1432835965d374929bfa7f6f7a07",
22+
"https://github.com/python/pyperformance.git",
23+
"pyperformance",
24+
),
25+
BenchmarkRepo(
26+
"265655e7f03ace13ec1e00e1ba299179e69f8a00",
27+
"https://github.com/pyston/python-macrobenchmarks.git",
28+
"pyston-benchmarks",
29+
),
30+
]
31+
32+
33+
def get_benchmark_hash() -> str:
34+
hash = hashlib.sha256()
35+
for repo in BENCHMARK_REPOS:
36+
if Path(repo.dirname).is_dir():
37+
current_hash = git.get_git_hash(Path(repo.dirname))
38+
else:
39+
current_hash = repo.hash
40+
hash.update(current_hash.encode("ascii")[:7])
41+
return hash.hexdigest()[:6]

bench_runner/config.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,29 @@
44

55
import functools
66
from pathlib import Path
7+
from typing import Any
78

89
try:
910
import tomllib
1011
except ImportError:
1112
import tomli as tomllib # type: ignore
1213

1314

15+
from . import runners
16+
17+
1418
@functools.cache
1519
def get_bench_runner_config(
1620
filepath: Path | str = Path("bench_runner.toml"),
1721
):
1822
with Path(filepath).open("rb") as fd:
1923
return tomllib.load(fd)
24+
25+
26+
def get_config_for_current_runner() -> dict[str, Any]:
27+
config = get_bench_runner_config()
28+
runner = runners.get_runner_for_hostname()
29+
all_runners = config.get("runners", [])
30+
if len(all_runners) >= 1:
31+
return all_runners[0].get(runner.nickname, {})
32+
return {}

bench_runner/git.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
from __future__ import annotations
33

44

5+
import contextlib
56
import datetime
67
from pathlib import Path
8+
import shutil
79
import subprocess
10+
import re
811

912

1013
import rich
@@ -128,3 +131,40 @@ def get_commits_between(dirname: PathLike, ref1: str, ref2: str) -> list[str]:
128131
def bisect_commits(dirname: PathLike, ref1: str, ref2: str) -> str:
129132
commits = get_commits_between(dirname, ref1, ref2)
130133
return commits[len(commits) // 2]
134+
135+
136+
def clone(
137+
dirname: PathLike,
138+
url: str,
139+
*,
140+
branch: str | None = None,
141+
depth: int = 1,
142+
) -> None:
143+
is_hash = re.match(r"^[0-9a-f]{40}$", branch) if branch else False
144+
145+
dirname = Path(dirname)
146+
if dirname.is_dir():
147+
if is_hash and (dirname / ".git").is_dir() and get_git_hash(dirname) == branch:
148+
# This is a git repo, and the hash matches
149+
return
150+
shutil.rmtree(dirname)
151+
152+
# Fetching a hash and fetching a branch require different approaches
153+
154+
if is_hash:
155+
assert branch is not None
156+
dirname.mkdir()
157+
with contextlib.chdir(dirname):
158+
subprocess.check_call(["git", "init"])
159+
subprocess.check_call(["git", "remote", "add", "origin", url])
160+
subprocess.check_call(
161+
["git", "fetch", "--depth", str(depth), "origin", branch]
162+
)
163+
subprocess.check_call(["git", "checkout", branch])
164+
else:
165+
args = ["git", "clone", url, str(dirname)]
166+
if branch is not None:
167+
args += ["--branch", branch]
168+
if depth is not None:
169+
args += ["--depth", str(depth)]
170+
subprocess.check_call(args)

bench_runner/result.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
from operator import itemgetter
1010
from pathlib import Path
1111
import re
12-
import socket
1312
import subprocess
1413
import sys
1514
from typing import Any, Callable, Iterable, Sequence
@@ -524,7 +523,7 @@ def from_scratch(
524523
flags: Iterable[str] | None = None,
525524
) -> "Result":
526525
result = cls(
527-
_clean(runners.get_nickname_for_hostname(socket.gethostname())),
526+
_clean(runners.get_nickname_for_hostname()),
528527
_clean(_get_architecture(python)),
529528
_clean_for_url(fork),
530529
_clean(ref[:20]),

bench_runner/runners.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
import functools
55
import os
6+
import socket
67

78

89
from . import config
@@ -80,13 +81,19 @@ def get_runners_by_nickname() -> dict[str, Runner]:
8081
return {x.nickname: x for x in get_runners()}
8182

8283

83-
def get_nickname_for_hostname(hostname: str) -> str:
84+
def get_nickname_for_hostname(hostname: str | None = None) -> str:
8485
# The envvar BENCHMARK_MACHINE_NICKNAME is used to override the machine that
8586
# results are reported for.
8687
if "BENCHMARK_MACHINE_NICKNAME" in os.environ:
8788
return os.environ["BENCHMARK_MACHINE_NICKNAME"]
88-
return get_runners_by_hostname().get(hostname, unknown_runner).nickname
89+
return get_runner_for_hostname(hostname).nickname
8990

9091

9192
def get_runner_by_nickname(nickname: str) -> Runner:
9293
return get_runners_by_nickname().get(nickname, unknown_runner)
94+
95+
96+
def get_runner_for_hostname(hostname: str | None = None) -> Runner:
97+
if hostname is None:
98+
hostname = socket.gethostname()
99+
return get_runners_by_hostname().get(hostname, unknown_runner)

bench_runner/scripts/generate_results.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,11 @@ def sort_runner_names(runner_names: Iterable[str]) -> list[str]:
126126
def sorter(val):
127127
if val is None:
128128
return ()
129-
return order.index(val.split()[0]), val
129+
try:
130+
idx = order.index(val.split()[0])
131+
except ValueError:
132+
idx = -1
133+
return idx, val
130134

131135
return sorted(runner_names, key=sorter)
132136

bench_runner/scripts/get_merge_base.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
import rich_argparse
77

88

9+
from bench_runner import benchmark_definitions
910
from bench_runner import flags as mflags
1011
from bench_runner import git
1112
from bench_runner.result import has_result
12-
from bench_runner import util
1313
from bench_runner.util import PathLike
1414

1515

@@ -55,7 +55,7 @@ def _main(
5555
machine,
5656
pystats,
5757
flags,
58-
util.get_benchmark_hash(),
58+
benchmark_definitions.get_benchmark_hash(),
5959
progress=False,
6060
)
6161
is None

0 commit comments

Comments
 (0)