Skip to content

Commit 29a9d53

Browse files
committed
Merge branch 'master' into bump-3.26.0
2 parents a3fd478 + 77811f8 commit 29a9d53

15 files changed

Lines changed: 518 additions & 7 deletions

File tree

.gitattributes

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bench/testdata/* filter=lfs diff=lfs merge=lfs -text

.github/workflows/ci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ jobs:
2222
steps:
2323
- uses: actions/checkout@v4
2424

25+
# Skip installing package docs to avoid wasting time when installing valgrind
26+
# See: https://github.com/actions/runner-images/issues/10977#issuecomment-2810713336
27+
- name: Skip installing package docs
28+
if: runner.os == 'Linux'
29+
run: |
30+
sudo tee /etc/dpkg/dpkg.cfg.d/01_nodoc > /dev/null << 'EOF'
31+
path-exclude /usr/share/doc/*
32+
path-exclude /usr/share/man/*
33+
path-exclude /usr/share/info/*
34+
EOF
35+
2536
- name: Update apt-get cache
2637
run: sudo apt-get update
2738

.github/workflows/codspeed.yml

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: CodSpeed Benchmarks
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
pull_request:
8+
workflow_dispatch:
9+
10+
jobs:
11+
benchmarks:
12+
runs-on: codspeed-macro
13+
timeout-minutes: 20
14+
strategy:
15+
matrix:
16+
# IMPORTANT: The binary has to match the architecture of the runner!
17+
cmd:
18+
- testdata/take_strings-aarch64 varbinview_non_null
19+
- echo Hello, World!
20+
- ls bench.py
21+
- python3 testdata/test.py
22+
- stress-ng --cpu 1 --timeout 1s
23+
- stress-ng --cpu 4 --timeout 1s
24+
valgrind:
25+
- "3.26.0"
26+
- "3.25.1"
27+
- "local"
28+
steps:
29+
- uses: actions/checkout@v4
30+
with:
31+
lfs: true
32+
- uses: extractions/setup-just@v3
33+
34+
# Skip installing package docs to avoid wasting time when installing build dependencies
35+
# See: https://github.com/actions/runner-images/issues/10977#issuecomment-2810713336
36+
- name: Skip installing package docs
37+
if: runner.os == 'Linux'
38+
run: |
39+
sudo tee /etc/dpkg/dpkg.cfg.d/01_nodoc > /dev/null << 'EOF'
40+
path-exclude /usr/share/doc/*
41+
path-exclude /usr/share/man/*
42+
path-exclude /usr/share/info/*
43+
EOF
44+
45+
- name: Cache Valgrind build
46+
uses: actions/cache@v4
47+
id: valgrind-cache
48+
with:
49+
path: /tmp/valgrind-build
50+
key: valgrind-${{ matrix.valgrind }}-${{ runner.os }}-${{ matrix.valgrind == 'local' && hashFiles('coregrind/**', 'include/**', 'VEX/**', 'cachegrind/**', 'callgrind/**', 'dhat/**', 'drd/**', 'helgrind/**', 'lackey/**', 'massif/**', 'memcheck/**', 'none/**', 'exp-bbv/**', 'auxprogs/**', '*.ac', '*.am', '*.in', 'autogen.sh', 'configure*') || 'build' }}
51+
52+
# Build and install Valgrind
53+
- name: Update apt-get cache
54+
if: steps.valgrind-cache.outputs.cache-hit != 'true'
55+
run: |
56+
sudo apt-get update
57+
58+
# Remove existing Valgrind installation
59+
sudo apt-get remove -y valgrind || true
60+
61+
- name: Install build dependencies
62+
if: steps.valgrind-cache.outputs.cache-hit != 'true'
63+
run: |
64+
sudo apt-get install -y \
65+
build-essential \
66+
automake \
67+
autoconf \
68+
gdb \
69+
docbook \
70+
docbook-xsl \
71+
docbook-xml \
72+
xsltproc
73+
74+
- name: Build Valgrind (${{ matrix.valgrind }})
75+
if: steps.valgrind-cache.outputs.cache-hit != 'true'
76+
run: just build ${{ matrix.valgrind }}
77+
78+
- name: Install Valgrind (${{ matrix.valgrind }})
79+
run: |
80+
just install ${{ matrix.valgrind }}
81+
82+
# Ensure libc6-dev is installed for Valgrind to work properly
83+
sudo apt-get update
84+
sudo apt-get install -y libc6-dev stress-ng
85+
86+
- name: Verify Valgrind build
87+
run: /usr/local/bin/valgrind --version
88+
89+
# Setup benchmarks and run them
90+
- name: Install uv
91+
uses: astral-sh/setup-uv@v5
92+
93+
- name: Run the benchmarks
94+
uses: CodSpeedHQ/action@main
95+
env:
96+
CODSPEED_PERF_ENABLED: false
97+
with:
98+
working-directory: bench
99+
mode: walltime
100+
run: ./bench.py --cmd "${{ matrix.cmd }}" --valgrind-path /usr/local/bin/valgrind

Justfile

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Builds a specific valgrind version
2+
# Usage:
3+
# - just build 3.24.0: Downloads the specified version from sourceware.org, builds and installs it
4+
# - just build local: Builds the local source tree
5+
build version:
6+
#!/usr/bin/env bash
7+
set -euo pipefail
8+
9+
mkdir -p /tmp/valgrind-build
10+
rm -rf /tmp/valgrind-build/valgrind-{{ version }}*
11+
12+
if [ "{{ version }}" = "local" ]; then
13+
cp -r . /tmp/valgrind-build/valgrind-local
14+
else
15+
wget -q -O /tmp/valgrind-build/valgrind-{{ version }}.tar.bz2 \
16+
https://sourceware.org/pub/valgrind/valgrind-{{ version }}.tar.bz2
17+
tar -xjf /tmp/valgrind-build/valgrind-{{ version }}.tar.bz2 \
18+
-C /tmp/valgrind-build
19+
fi
20+
21+
just build-in "/tmp/valgrind-build/valgrind-{{ version }}"
22+
23+
build-in dir:
24+
#!/usr/bin/env bash
25+
set -euo pipefail
26+
cd "{{ dir }}"
27+
28+
# Check if we need to run autogen.sh (for git checkouts)
29+
if [ -f "autogen.sh" ] && [ ! -f "configure" ]; then
30+
./autogen.sh
31+
fi
32+
33+
./configure
34+
make include/vgversion.h
35+
make -j$(nproc) -C VEX
36+
make -j$(nproc) -C coregrind
37+
make -j$(nproc) -C callgrind
38+
39+
40+
install version:
41+
#!/usr/bin/env bash
42+
set -euo pipefail
43+
44+
cd "/tmp/valgrind-build/valgrind-{{ version }}"
45+
sudo make install
46+

bench/bench.py

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
#!/usr/bin/env -S uv run --script
2+
# /// script
3+
# requires-python = ">=3.9"
4+
# dependencies = [
5+
# "pytest>=8.4.2",
6+
# "pytest-codspeed>=4.2.0",
7+
# ]
8+
# ///
9+
10+
import argparse
11+
import shlex
12+
import subprocess
13+
14+
import pytest
15+
16+
17+
class ValgrindRunner:
18+
"""Run Valgrind with different configurations."""
19+
20+
def __init__(
21+
self,
22+
cmd: str,
23+
valgrind_path: str = "valgrind",
24+
):
25+
"""Initialize valgrind runner.
26+
27+
Args:
28+
cmd: Command to profile (can be a path or arbitrary shell command)
29+
valgrind_path: Path to valgrind executable
30+
"""
31+
self.cmd = cmd
32+
self.valgrind_path = valgrind_path
33+
34+
# Verify valgrind is available
35+
result = subprocess.run(
36+
[self.valgrind_path, "--version"],
37+
capture_output=True,
38+
text=True,
39+
)
40+
if result.returncode != 0:
41+
raise RuntimeError(f"Valgrind not found at: {self.valgrind_path}")
42+
self.valgrind_version = result.stdout.strip()
43+
44+
def run_valgrind(self, *args: str) -> None:
45+
"""Execute valgrind with given arguments.
46+
47+
Args:
48+
*args: Valgrind arguments
49+
"""
50+
51+
cmd = [
52+
self.valgrind_path,
53+
"--tool=callgrind",
54+
"--log-file=/dev/null",
55+
*args,
56+
*shlex.split(self.cmd),
57+
]
58+
59+
result = subprocess.run(
60+
cmd,
61+
capture_output=True,
62+
text=True,
63+
)
64+
if result.returncode != 0:
65+
raise RuntimeError(
66+
f"Valgrind execution failed with code {result.returncode}\n"
67+
f"Stdout:\n{result.stdout}\n"
68+
f"Stderr:\n{result.stderr}"
69+
)
70+
71+
72+
@pytest.fixture
73+
def runner(request):
74+
"""Fixture to provide runner instance to tests."""
75+
return request.config._valgrind_runner
76+
77+
78+
def pytest_generate_tests(metafunc):
79+
"""Parametrize tests with valgrind configurations."""
80+
if "valgrind_args" in metafunc.fixturenames:
81+
runner = getattr(metafunc.config, "_valgrind_runner", None)
82+
if not runner:
83+
return
84+
85+
# Define valgrind configurations
86+
configs = [
87+
(["--read-inline-info=no"], "no-inline"),
88+
(["--read-inline-info=yes"], "inline"),
89+
(
90+
[
91+
"--trace-children=yes",
92+
"--cache-sim=yes",
93+
"--I1=32768,8,64",
94+
"--D1=32768,8,64",
95+
"--LL=8388608,16,64",
96+
"--collect-systime=nsec",
97+
"--compress-strings=no",
98+
"--combine-dumps=yes",
99+
"--dump-line=no",
100+
"--read-inline-info=yes",
101+
],
102+
"full-with-inline",
103+
),
104+
(
105+
[
106+
"--trace-children=yes",
107+
"--cache-sim=yes",
108+
"--I1=32768,8,64",
109+
"--D1=32768,8,64",
110+
"--LL=8388608,16,64",
111+
"--collect-systime=nsec",
112+
"--compress-strings=no",
113+
"--combine-dumps=yes",
114+
"--dump-line=no",
115+
],
116+
"full-no-inline",
117+
),
118+
]
119+
120+
# Create test IDs with format: valgrind-version, command, config-name
121+
test_ids = [
122+
f"{runner.valgrind_version}, {runner.cmd}, {config_name}"
123+
for _, config_name in configs
124+
]
125+
126+
# Parametrize with just the args
127+
metafunc.parametrize(
128+
"valgrind_args",
129+
[args for args, _ in configs],
130+
ids=test_ids,
131+
)
132+
133+
134+
@pytest.mark.benchmark
135+
def test_valgrind(runner, valgrind_args):
136+
if runner:
137+
runner.run_valgrind(*valgrind_args)
138+
139+
140+
def main():
141+
parser = argparse.ArgumentParser(
142+
description="Benchmark Valgrind with pytest-codspeed",
143+
formatter_class=argparse.RawDescriptionHelpFormatter,
144+
epilog="""
145+
Examples:
146+
# Run with a binary path
147+
uv run bench.py --cmd /path/to/binary
148+
149+
# Run with an arbitrary command
150+
uv run bench.py --cmd 'echo "hello world"'
151+
152+
# Run with custom valgrind installation
153+
uv run bench.py --cmd /usr/bin/ls --valgrind-path /usr/local/bin/valgrind
154+
""",
155+
)
156+
157+
parser.add_argument(
158+
"--cmd",
159+
type=str,
160+
required=True,
161+
help="Command to profile (can be a path to a binary or any arbitrary command)",
162+
)
163+
parser.add_argument(
164+
"--valgrind-path",
165+
type=str,
166+
default="valgrind",
167+
help="Path to valgrind executable (default: valgrind)",
168+
)
169+
args = parser.parse_args()
170+
171+
# Create runner instance
172+
runner = ValgrindRunner(
173+
cmd=args.cmd,
174+
valgrind_path=args.valgrind_path,
175+
)
176+
print(f"Valgrind version: {runner.valgrind_version}")
177+
print(f"Command: {args.cmd}")
178+
179+
# Plugin to pass runner to tests
180+
class RunnerPlugin:
181+
def pytest_configure(self, config):
182+
config._valgrind_runner = runner
183+
184+
exit_code = pytest.main(
185+
[__file__, "-v", "--codspeed", "--codspeed-warmup-time=0", "--codspeed-max-time=5"],
186+
plugins=[RunnerPlugin()],
187+
)
188+
if exit_code != 0 and exit_code != 5:
189+
print(f"Benchmark execution returned exit code: {exit_code}")
190+
191+
192+
if __name__ == "__main__":
193+
main()

bench/pytest.ini

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[pytest]
2+
norecursedirs = testdata __pycache__ .pytest_cache *.egg-info
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:d241a1c2932e11d4b5226d193ecf7c120bb881f5f0108884071048dcd5bd6696
3+
size 282407216

bench/testdata/take_strings-x86_64

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:c184f81f7046a8a78cb272ac4a1c7ad616b5e3dd20dcc40638f1db485abc5b22
3+
size 272199232

bench/testdata/test.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:cf603e7740f7f7cbf211c7b240f8426c0bf602353290cdb3c9a52adbb0dfaec1
3+
size 22

coregrind/Makefile.am

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -365,6 +365,7 @@ COREGRIND_SOURCES_COMMON = \
365365
m_debuginfo/d3basics.c \
366366
m_debuginfo/debuginfo.c \
367367
m_debuginfo/image.c \
368+
m_debuginfo/inltab_lookup.c \
368369
m_debuginfo/minilzo-inl.c \
369370
m_debuginfo/readdwarf.c \
370371
m_debuginfo/readdwarf3.c \

0 commit comments

Comments
 (0)