Skip to content

Commit 344e1e6

Browse files
committed
gh-98894: Fix dtrace tests in shared builds
Generate SystemTap probe definitions targeting libpython for shared builds and use centralized USDT probe object discovery for readelf and BPFTrace.
1 parent f5b3eef commit 344e1e6

3 files changed

Lines changed: 74 additions & 30 deletions

File tree

Lib/test/dtracedata/call_stack.stp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ function basename:string(path:string)
1010
return last_token;
1111
}
1212

13-
probe process.mark("function__entry")
13+
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry")
1414
{
1515
funcname = user_string($arg2);
1616

@@ -19,7 +19,8 @@ probe process.mark("function__entry")
1919
}
2020
}
2121

22-
probe process.mark("function__entry"), process.mark("function__return")
22+
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry"),
23+
@PYTHON_SYSTEMTAP_PROBE@("function__return")
2324
{
2425
filename = user_string($arg1);
2526
funcname = user_string($arg2);
@@ -31,7 +32,7 @@ probe process.mark("function__entry"), process.mark("function__return")
3132
}
3233
}
3334

34-
probe process.mark("function__return")
35+
probe @PYTHON_SYSTEMTAP_PROBE@("function__return")
3536
{
3637
funcname = user_string($arg2);
3738

Lib/test/dtracedata/gc.stp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
global tracing
22

3-
probe process.mark("function__entry")
3+
probe @PYTHON_SYSTEMTAP_PROBE@("function__entry")
44
{
55
funcname = user_string($arg2);
66

@@ -9,14 +9,15 @@ probe process.mark("function__entry")
99
}
1010
}
1111

12-
probe process.mark("gc__start"), process.mark("gc__done")
12+
probe @PYTHON_SYSTEMTAP_PROBE@("gc__start"),
13+
@PYTHON_SYSTEMTAP_PROBE@("gc__done")
1314
{
1415
if (tracing) {
1516
printf("%d\t%s:%ld\n", gettimeofday_us(), $$name, $arg1);
1617
}
1718
}
1819

19-
probe process.mark("function__return")
20+
probe @PYTHON_SYSTEMTAP_PROBE@("function__return")
2021
{
2122
funcname = user_string($arg2);
2223

Lib/test/test_dtrace.py

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import subprocess
66
import sys
77
import sysconfig
8+
import tempfile
89
import types
910
import unittest
1011

@@ -24,6 +25,31 @@ def abspath(filename):
2425
return os.path.abspath(findfile(filename, subdir="dtracedata"))
2526

2627

28+
def get_probe_binary():
29+
binary = sys.executable
30+
if sysconfig.get_config_var("Py_ENABLE_SHARED"):
31+
lib_dir = sysconfig.get_config_var("LIBDIR")
32+
if not lib_dir or sysconfig.is_python_build():
33+
lib_dir = os.path.abspath(os.path.dirname(sys.executable))
34+
35+
lib_names = []
36+
for name in (
37+
sysconfig.get_config_var("INSTSONAME"),
38+
sysconfig.get_config_var("LDLIBRARY"),
39+
):
40+
if name and name not in lib_names:
41+
lib_names.append(name)
42+
43+
if lib_dir:
44+
for name in lib_names:
45+
libpython_path = os.path.join(lib_dir, name)
46+
if os.path.exists(libpython_path):
47+
binary = libpython_path
48+
break
49+
50+
return binary
51+
52+
2753
def normalize_trace_output(output):
2854
"""Normalize DTrace output for comparison.
2955
@@ -179,6 +205,43 @@ class DTraceBackend(TraceBackend):
179205
class SystemTapBackend(TraceBackend):
180206
EXTENSION = ".stp"
181207
COMMAND = ["stap", "-g"]
208+
PROBE_PLACEHOLDER = "@PYTHON_SYSTEMTAP_PROBE@"
209+
210+
@staticmethod
211+
def _quote_systemtap_string(value):
212+
return value.replace("\\", "\\\\").replace('"', '\\"')
213+
214+
def _python_probe(self):
215+
executable = self._quote_systemtap_string(sys.executable)
216+
probe_binary = get_probe_binary()
217+
if probe_binary != sys.executable:
218+
probe_binary = self._quote_systemtap_string(probe_binary)
219+
return f'process("{executable}").library("{probe_binary}").mark'
220+
return f'process("{executable}").mark'
221+
222+
def _render_script(self, script_file):
223+
with open(script_file) as script:
224+
return script.read().replace(
225+
self.PROBE_PLACEHOLDER, self._python_probe()
226+
)
227+
228+
def trace(self, script_file, subcommand=None, *, timeout=None,
229+
check_returncode=False):
230+
with tempfile.NamedTemporaryFile(
231+
mode="w", encoding="utf-8", suffix=self.EXTENSION, delete=False
232+
) as script:
233+
script.write(self._render_script(script_file))
234+
generated_script_file = script.name
235+
236+
try:
237+
return super().trace(
238+
generated_script_file,
239+
subcommand,
240+
timeout=timeout,
241+
check_returncode=check_returncode,
242+
)
243+
finally:
244+
os.unlink(generated_script_file)
182245

183246

184247
class BPFTraceBackend(TraceBackend):
@@ -272,7 +335,7 @@ def run_case(self, name, optimize_python=None):
272335
python_flags.extend(["-O"] * optimize_python)
273336

274337
subcommand = [sys.executable] + python_flags + [python_file]
275-
program = self.PROGRAMS[name].format(python=sys.executable)
338+
program = self.PROGRAMS[name].format(python=get_probe_binary())
276339

277340
try:
278341
proc = create_process_group(
@@ -311,7 +374,7 @@ def run_case(self, name, optimize_python=None):
311374

312375
def assert_usable(self):
313376
# Check if bpftrace is available and can attach to USDT probes
314-
program = f'usdt:{sys.executable}:python:function__entry {{ printf("probe: success\\n"); exit(); }}'
377+
program = f'usdt:{get_probe_binary()}:python:function__entry {{ printf("probe: success\\n"); exit(); }}'
315378
try:
316379
proc = create_process_group(
317380
["bpftrace", "-e", program, "-c", f"{sys.executable} -c pass"],
@@ -453,28 +516,7 @@ def get_readelf_version():
453516
return int(match.group(1)), int(match.group(2))
454517

455518
def get_readelf_output(self):
456-
binary = sys.executable
457-
if sysconfig.get_config_var("Py_ENABLE_SHARED"):
458-
lib_dir = sysconfig.get_config_var("LIBDIR")
459-
if not lib_dir or sysconfig.is_python_build():
460-
lib_dir = os.path.abspath(os.path.dirname(sys.executable))
461-
462-
lib_names = []
463-
for name in (
464-
sysconfig.get_config_var("INSTSONAME"),
465-
sysconfig.get_config_var("LDLIBRARY"),
466-
):
467-
if name and name not in lib_names:
468-
lib_names.append(name)
469-
470-
if lib_dir:
471-
for name in lib_names:
472-
libpython_path = os.path.join(lib_dir, name)
473-
if os.path.exists(libpython_path):
474-
binary = libpython_path
475-
break
476-
477-
return run_readelf(["readelf", "-n", binary])
519+
return run_readelf(["readelf", "-n", get_probe_binary()])
478520

479521
def test_check_probes(self):
480522
readelf_output = self.get_readelf_output()

0 commit comments

Comments
 (0)