[DTrace-devel] [PATCH v2 7/7] test: add tests for python bindings
Alan Maguire
alan.maguire at oracle.com
Wed May 27 18:50:03 UTC 2026
Add tests covering aggregation snapshot, update, handling of
stack keys. Also test process creation/grabbing, and ensure
we detect processes going away or exit() calls in dt.work().
Also ensure that multiple sessions can exist in the process
image.
Signed-off-by: Alan Maguire <alan.maguire at oracle.com>
---
test/unittest/python/tst.aggr-actions.sh | 133 ++++++++++++++++++++++
test/unittest/python/tst.aggr-change.sh | 75 ++++++++++++
test/unittest/python/tst.aggr-stack.sh | 61 ++++++++++
test/unittest/python/tst.aggr.sh | 58 ++++++++++
test/unittest/python/tst.exit.sh | 64 +++++++++++
test/unittest/python/tst.multi-session.sh | 58 ++++++++++
test/unittest/python/tst.proc-create.sh | 56 +++++++++
test/unittest/python/tst.proc-exit.sh | 57 ++++++++++
test/unittest/python/tst.proc-grab.sh | 81 +++++++++++++
9 files changed, 643 insertions(+)
create mode 100755 test/unittest/python/tst.aggr-actions.sh
create mode 100755 test/unittest/python/tst.aggr-change.sh
create mode 100755 test/unittest/python/tst.aggr-stack.sh
create mode 100755 test/unittest/python/tst.aggr.sh
create mode 100755 test/unittest/python/tst.exit.sh
create mode 100755 test/unittest/python/tst.multi-session.sh
create mode 100755 test/unittest/python/tst.proc-create.sh
create mode 100755 test/unittest/python/tst.proc-exit.sh
create mode 100755 test/unittest/python/tst.proc-grab.sh
diff --git a/test/unittest/python/tst.aggr-actions.sh b/test/unittest/python/tst.aggr-actions.sh
new file mode 100755
index 00000000..8fd22c8c
--- /dev/null
+++ b/test/unittest/python/tst.aggr-actions.sh
@@ -0,0 +1,133 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure aggregation actions return expected values.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession
+
+prog = r"""
+BEGIN
+{
+ @counts = count();
+ @counts = count();
+ @counts = count();
+ @sums = sum(100);
+ @sums = sum(50);
+ @sums = sum(32);
+ @avgs = avg(4);
+ @avgs = avg(5);
+ @mins = min(100);
+ @mins = min(99);
+ @mins = min(101);
+ @maxs = max(12);
+ @maxs = max(19);
+ @maxs = max(-4);
+ @stddevs = stddev(5000000000);
+ @stddevs = stddev(5000000100);
+ @stddevs = stddev(5000000200);
+ @stddevs = stddev(5000000300);
+ @stddevs = stddev(5000000400);
+ @stddevs = stddev(5000000500);
+ @stddevs = stddev(5000000600);
+ @stddevs = stddev(5000000700);
+ @stddevs = stddev(5000000800);
+ @stddevs = stddev(5000000900);
+ @quants = quantize(1);
+ @quants = quantize(2);
+ @quants = quantize(1);
+ @quants = quantize(3);
+ @quants = quantize(128);
+ @lquants = lquantize(1, 1, 10, 2);
+ @lquants = lquantize(3, 1, 10, 2);
+ @lquants = lquantize(1, 1, 10, 2);
+ @lquants = lquantize(9, 1, 10, 2);
+ @lquants = lquantize(9, 1, 10, 2);
+ @lquants = lquantize(1, 1, 10, 2);
+ @llquants = llquantize(1, 3, 1, 4, 3);
+ @llquants = llquantize(26, 3, 1, 4, 3);
+ @llquants = llquantize(2, 3, 1, 4, 3);
+ @llquants = llquantize(13, 3, 1, 4, 3);
+ @llquants = llquantize(6, 3, 1, 4, 3);
+ @llquants = llquantize(81, 3, 1, 4, 3);
+ exit(0);
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ pid = Popen(["/bin/sleep", "1"]).wait()
+ dt.stop()
+ dt.agg_snap()
+ rows = dt.agg_walk()
+
+if not rows:
+ raise SystemExit("no rows returned")
+
+for row in rows:
+ if row["name"] == "counts":
+ v = int(row["value"])
+ if v != 3:
+ raise SystemExit(f"unexpected count value {v}")
+ if row["name"] == "avgs":
+ v = float(row["value"])
+ if v != 4.5:
+ raise SystemExit(f"unexpected avg value {v}")
+ if row["name"] == "sums":
+ v = int(row["value"])
+ if v != 182:
+ raise SystemExit(f"unexpected sum value {v}")
+ if row["name"] == "mins":
+ v = int(row["value"])
+ if v != 99:
+ raise SystemExit(f"unexpected min value {v}")
+ if row["name"] == "maxs":
+ v = int(row["value"])
+ if v != 19:
+ raise SystemExit(f"unexpected max value {v}")
+ if row["name"] == "stddevs":
+ v = int(row["value"])
+ if v != 287:
+ raise SystemExit(f"unexecpted stddev value {v}")
+ if row["name"] == "quants":
+ v = row["value"]
+ if v != {1:2, 2:2, 128:1}:
+ raise SystemExit(f"unexecpted quantize value {v}")
+ if row["name"] == "lquants":
+ v = row["value"]
+ if v != {1:3, 3:1, 9:2}:
+ raise SystemExit(f"unexecpted lquantize value {v}")
+ if row["name"] == "llquants":
+ v = row["value"]
+ if v != {0:2, 9:1, 18:1, 27:1, 162:1}:
+ raise SystemExit(f"unexecpted llquantize value {v}")
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.aggr-change.sh b/test/unittest/python/tst.aggr-change.sh
new file mode 100755
index 00000000..318e17ba
--- /dev/null
+++ b/test/unittest/python/tst.aggr-change.sh
@@ -0,0 +1,75 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure aggregation values are updated across dt.work() calls.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession
+
+prog = r"""
+syscall:::entry
+{
+ @counts["syscalls"] = count();
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ pid = Popen(["/bin/sleep", "1"]).wait()
+ dt.work()
+ dt.agg_snap()
+ rows = dt.agg_walk()
+ if not rows:
+ raise SystemExit("no rows returned")
+ entry = rows[0]
+ val1 = int(entry["value"])
+ pid = Popen(["/bin/sleep", "1"]).wait()
+ dt.work()
+ dt.agg_snap()
+ rows = dt.agg_walk()
+ if not rows:
+ raise SystemExit("no rows returned")
+ entry = rows[0]
+ val2 = int(entry["value"])
+ if val2 <= val1:
+ raise SystemExit("aggr value unchanged")
+ dt.stop()
+
+ rows = dt.agg_walk()
+
+if not rows:
+ raise SystemExit("no rows returned")
+
+entry = rows[0]
+if "keys" not in entry or "value" not in entry:
+ raise SystemExit("missing keys or value")
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.aggr-stack.sh b/test/unittest/python/tst.aggr-stack.sh
new file mode 100755
index 00000000..a9259636
--- /dev/null
+++ b/test/unittest/python/tst.aggr-stack.sh
@@ -0,0 +1,61 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure aggregation can retrieve kernel stack key.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession
+
+prog = r"""
+syscall:::entry
+{
+ @counts[stack()] = count();
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ pid = Popen(["/bin/sleep", "1"]).wait()
+ dt.stop()
+ dt.agg_snap()
+ rows = dt.agg_walk()
+
+if not rows:
+ raise SystemExit("no rows returned")
+
+entry = rows[0]
+if "keys" not in entry or "value" not in entry:
+ raise SystemExit("missing keys or value")
+
+if len(entry["keys"][0]) < 2:
+ raise SystemExit("missing stack list")
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.aggr.sh b/test/unittest/python/tst.aggr.sh
new file mode 100755
index 00000000..15c46f0c
--- /dev/null
+++ b/test/unittest/python/tst.aggr.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure aggregation can be retrieved from simple D script.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession
+
+prog = r"""
+syscall:::entry
+{
+ @counts[execname, probefunc] = count();
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ pid = Popen(["/bin/sleep", "1"]).wait()
+ dt.stop()
+ dt.agg_snap()
+ rows = dt.agg_walk()
+
+if not rows:
+ raise SystemExit("no rows returned")
+
+entry = rows[0]
+if "keys" not in entry or "value" not in entry:
+ raise SystemExit("missing keys or value")
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.exit.sh b/test/unittest/python/tst.exit.sh
new file mode 100755
index 00000000..def6b0ec
--- /dev/null
+++ b/test/unittest/python/tst.exit.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure we see exit() from script with dt.status().
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession, DTRACE_STATUS_EXITED
+
+prog = r"""
+syscall:::entry
+{
+ c++;
+}
+
+syscall:::entry
+/ c > 1024 /
+{
+ exit(0);
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ exited = False
+ iters = 0
+ while iters < 10:
+ pid = Popen(["/bin/sleep", "1"]).wait()
+ if dt.status() == DTRACE_STATUS_EXITED:
+ exited = True
+ iters += 1
+ dt.stop()
+
+if exited != True:
+ raise SystemExit("missing DTRACE_STATUS_EXIT")
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.multi-session.sh b/test/unittest/python/tst.multi-session.sh
new file mode 100755
index 00000000..7389c126
--- /dev/null
+++ b/test/unittest/python/tst.multi-session.sh
@@ -0,0 +1,58 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure multiple sessions within same process image work.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession
+
+prog = r"""
+syscall::read:entry
+{
+ @counts["syscalls"] = count();
+}
+"""
+
+dts = {}
+max_sessions = 10
+
+try:
+ for i in range(max_sessions):
+ dts[i] = DTraceSession()
+ p = dts[i].compile(prog)
+ dts[i].enable(p)
+ dts[i].go()
+ dts[i].work()
+except Exception as exc:
+ raise SystemExit(f"got err with {i} multiple sessions: {exc}")
+finally:
+ for j in range(i):
+ dts[i].close()
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.proc-create.sh b/test/unittest/python/tst.proc-create.sh
new file mode 100755
index 00000000..e9d2d466
--- /dev/null
+++ b/test/unittest/python/tst.proc-create.sh
@@ -0,0 +1,56 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Validate tracing of a spawned command.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.py <<'EOF'
+#!/usr/bin/env python3
+from subprocess import Popen
+from dtrace import DTraceSession
+
+prog = r"""
+syscall::read:entry
+{
+ @calls[execname] = count();
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ proc = dt.proc_create(["/bin/echo", "hello"])
+ dt.proc_continue(proc)
+ dt.proc_release(proc)
+ dt.stop()
+ dt.agg_snap()
+ rows = dt.agg_walk()
+
+if not rows:
+ raise SystemExit("no aggregation rows")
+
+names = {tuple(entry["keys"]) for entry in rows}
+if not any("echo" in key for key in names):
+ raise SystemExit("expected echo entry in aggregation")
+EOF
+
+chmod +x "$tmpdir/script.py"
+
+$tmpdir/script.py
diff --git a/test/unittest/python/tst.proc-exit.sh b/test/unittest/python/tst.proc-exit.sh
new file mode 100755
index 00000000..d4962f37
--- /dev/null
+++ b/test/unittest/python/tst.proc-exit.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure we see stopped state when traced process goes away.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+cat >"$tmpdir"/script.d <<EOF
+#!/usr/bin/env python3
+from pathlib import Path
+from subprocess import Popen
+
+from dtrace import DTraceSession, DTRACE_STATUS_STOPPED
+
+prog = r"""
+syscall:::entry
+{
+ c++;
+}
+"""
+
+with DTraceSession() as dt:
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ proc = dt.proc_create(["/bin/sleep", "5"])
+ dt.proc_continue(proc)
+ Popen(["/bin/sleep", "10"]).wait()
+ dt.work()
+ status = dt.status()
+ dt.proc_release(proc)
+ dt.stop()
+
+if status != DTRACE_STATUS_STOPPED:
+ raise SystemExit(f"missing DTRACE_STATUS_STOPPED: got {status}")
+
+EOF
+
+chmod +x "$tmpdir/script.d"
+PATH="$tmpdir:$PATH"
+
+$tmpdir/script.d
diff --git a/test/unittest/python/tst.proc-grab.sh b/test/unittest/python/tst.proc-grab.sh
new file mode 100755
index 00000000..21faca65
--- /dev/null
+++ b/test/unittest/python/tst.proc-grab.sh
@@ -0,0 +1,81 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2026, Oracle and/or its affiliates. All rights reserved.
+# Licensed under the Universal Permissive License v 1.0 as shown at
+# http://oss.oracle.com/licenses/upl.
+#
+# Ensure Python bindings can grab an existing process and trace its syscalls.
+
+if [ $# -ne 1 ]; then
+ echo "usage: $0 <dtrace>" >&2
+ exit 2
+fi
+
+dtrace=$1
+
+tmpdir=$(mktemp -d)
+cleanup() {
+ rm -rf "$tmpdir"
+}
+trap cleanup EXIT
+
+# Start a long-lived helper shell to grab.
+sleep_inf="$tmpdir/sleep.sh"
+cat >"$sleep_inf" <<'EOF'
+#!/bin/bash
+while :; do
+ sleep 1
+done
+EOF
+chmod +x "$sleep_inf"
+"$sleep_inf" &
+helper=$!
+
+cat >"$tmpdir"/script.py <<'EOF'
+#!/usr/bin/env python3
+import os
+import sys
+from subprocess import Popen
+from dtrace import DTraceSession
+
+prog = r"""
+syscall:::entry
+/ pid == $target /
+{
+ @counts[execname, probefunc] = count();
+}
+"""
+
+pid = int(os.environ["TARGET_PID"])
+
+with DTraceSession() as dt:
+ proc = dt.proc_grab_pid(pid)
+ if proc.getpid() != pid:
+ raise SystemExit("grabbed PID mismatch")
+ p = dt.compile(prog)
+ dt.enable(p)
+ dt.go()
+ dt.proc_continue(proc)
+ Popen(["/bin/sleep", "5"]).wait()
+ dt.stop()
+ dt.proc_release(proc)
+ dt.agg_snap()
+ rows = dt.agg_walk()
+
+if not rows:
+ raise SystemExit("no rows returned")
+
+entry = rows[0]
+if "keys" not in entry or "value" not in entry:
+ raise SystemExit("missing keys or value")
+EOF
+
+chmod +x "$tmpdir/script.py"
+export TARGET_PID=$helper
+$tmpdir/script.py
+ret=$?
+
+kill $helper
+wait $helper 2>/dev/null || true
+exit $ret
--
2.43.5
More information about the DTrace-devel
mailing list