[DTrace-devel] [PATCH 03/16] cpc: Add some hardware-counter tests

eugene.loh at oracle.com eugene.loh at oracle.com
Fri Jan 27 02:23:16 UTC 2023


From: Eugene Loh <eugene.loh at oracle.com>

Test hardware counters using both "branches" and "instructions" events.
Use "perf list hw" to check if these events are supported on the test
system.

A few utilities are introduced for these and future tests:

  * workload_user.c is a simple, user-space-intensive
    workload with very simple performance behavior:

      * one branch per iteration

      * a number of instructions per iteration that
        is easily determined from its disassembly

  * workload_get_iterations.sh estimates how many iterations
    to use to run the desired number of seconds

  * workload_analyze_loop.sh analyzes the disassembly of
    the innermost loop of something like workload_user.c;
    reporting the number of instructions per iteration and
    which PCs are in the loop

  * check_result.sh checks an actual count against
    an expected count to within some margin

Signed-off-by: Eugene Loh <eugene.loh at oracle.com>
---
 dtrace.spec                           |  1 +
 test/unittest/cpc/tst.branches.sh     | 60 +++++++++++++++++++++++
 test/unittest/cpc/tst.branches.x      | 13 +++++
 test/unittest/cpc/tst.instructions.sh | 68 +++++++++++++++++++++++++++
 test/unittest/cpc/tst.instructions.x  | 13 +++++
 test/utils/.gitignore                 |  1 +
 test/utils/Build                      |  4 +-
 test/utils/check_result.sh            | 24 ++++++++++
 test/utils/workload_analyze_loop.sh   | 63 +++++++++++++++++++++++++
 test/utils/workload_get_iterations.sh | 54 +++++++++++++++++++++
 test/utils/workload_user.c            | 33 +++++++++++++
 11 files changed, 332 insertions(+), 2 deletions(-)
 create mode 100755 test/unittest/cpc/tst.branches.sh
 create mode 100755 test/unittest/cpc/tst.branches.x
 create mode 100755 test/unittest/cpc/tst.instructions.sh
 create mode 100755 test/unittest/cpc/tst.instructions.x
 create mode 100755 test/utils/check_result.sh
 create mode 100755 test/utils/workload_analyze_loop.sh
 create mode 100755 test/utils/workload_get_iterations.sh
 create mode 100644 test/utils/workload_user.c

diff --git a/dtrace.spec b/dtrace.spec
index d12a2a82..9e1a1948 100644
--- a/dtrace.spec
+++ b/dtrace.spec
@@ -148,6 +148,7 @@ Requires:     %{name}-devel = %{version}-%{release} perl gcc java
 Requires:     java-1.8.0-openjdk-devel perl-IO-Socket-IP xfsprogs
 Requires:     exportfs vim-minimal %{name}%{?_isa} = %{version}-%{release}
 Requires:     coreutils wireshark %{glibc32}
+Requires:     perf
 Autoreq:      0
 Group:	      Development/System
 
diff --git a/test/unittest/cpc/tst.branches.sh b/test/unittest/cpc/tst.branches.sh
new file mode 100755
index 00000000..87250c37
--- /dev/null
+++ b/test/unittest/cpc/tst.branches.sh
@@ -0,0 +1,60 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+# @@reinvoke-failure: 1
+
+utils=`pwd`/test/utils
+
+dtrace=$1
+DIRNAME=$tmpdir/tst.branches.$$
+mkdir $DIRNAME
+cd $DIRNAME
+
+# determine number of iterations for target number of seconds
+nsecs=1
+niters=`$utils/workload_get_iterations.sh workload_user $nsecs`
+if [ $niters -lt 0 ]; then
+	echo "workload_get_iterations.sh failed with workload_user"
+	exit 1
+fi
+
+# pick a sampling period
+period=$(($niters / 100))
+
+# run DTrace
+$dtrace $dt_flags -qn '
+BEGIN
+{
+	n = 0;
+}
+
+branches-all-'$period'
+/pid == $target/
+{
+	n++;
+}
+
+END
+{
+	printf("%d\n", n);
+}' -c "$utils/workload_user $niters" -o tmp.txt
+
+if [[ $? -ne 0 ]]; then
+	echo ERROR running DTrace
+	cat tmp.txt
+	exit 1
+fi
+
+# estimate actual count (sampling period * # of samples)
+actual=$(($period * `cat tmp.txt`))
+
+# determine expected count (one branch per interation)
+expect=$niters
+
+# check
+$utils/check_result.sh $actual $expect $(($expect / 4))
+exit $?
diff --git a/test/unittest/cpc/tst.branches.x b/test/unittest/cpc/tst.branches.x
new file mode 100755
index 00000000..a8e07af1
--- /dev/null
+++ b/test/unittest/cpc/tst.branches.x
@@ -0,0 +1,13 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+if ! perf list hw | grep -qw branches; then
+	echo 'no "branches" event on this system'
+	exit 2
+fi
+
+exit 0
diff --git a/test/unittest/cpc/tst.instructions.sh b/test/unittest/cpc/tst.instructions.sh
new file mode 100755
index 00000000..a4663837
--- /dev/null
+++ b/test/unittest/cpc/tst.instructions.sh
@@ -0,0 +1,68 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+# @@reinvoke-failure: 1
+
+utils=`pwd`/test/utils
+
+dtrace=$1
+DIRNAME=$tmpdir/tst.instructions.$$
+mkdir $DIRNAME
+cd $DIRNAME
+
+# determine number of iterations for target number of seconds
+nsecs=1
+niters=`$utils/workload_get_iterations.sh workload_user $nsecs`
+if [ $niters -lt 0 ]; then
+	echo "workload_get_iterations.sh failed with workload_user"
+	exit 1
+fi
+
+# determine the number of instructions per loop iteration
+ninstructions_per_iter=`$utils/workload_analyze_loop.sh workload_user | awk '{print $1; exit 0}'`
+if [ $ninstructions_per_iter -lt 0 ]; then
+	echo could not determine number of instructions per loop
+	exit 1
+fi
+echo $ninstructions_per_iter instructions per loop iteration
+
+# pick a sampling period
+period=$(($niters / 100))
+
+# run DTrace
+$dtrace $dt_flags -qn '
+BEGIN
+{
+	n = 0;
+}
+
+instructions-all-'$period'
+/pid == $target/
+{
+	n++;
+}
+
+END
+{
+	printf("%d\n", n);
+}' -c "$utils/workload_user $niters" -o tmp.txt
+
+if [[ $? -ne 0 ]]; then
+	echo ERROR running DTrace
+	cat tmp.txt
+	exit 1
+fi
+
+# estimate actual count (sampling period * # of samples)
+actual=$(($period * `cat tmp.txt`))
+
+# determine expected count
+expect=$(($niters * $ninstructions_per_iter))
+
+# check
+$utils/check_result.sh $actual $expect $(($expect / 4))
+exit $?
diff --git a/test/unittest/cpc/tst.instructions.x b/test/unittest/cpc/tst.instructions.x
new file mode 100755
index 00000000..d47b7d99
--- /dev/null
+++ b/test/unittest/cpc/tst.instructions.x
@@ -0,0 +1,13 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+if ! perf list hw | grep -qw instructions; then
+	echo 'no "instructions" event on this system'
+	exit 2
+fi
+
+exit 0
diff --git a/test/utils/.gitignore b/test/utils/.gitignore
index 27c8cfc6..d8e2ccb5 100644
--- a/test/utils/.gitignore
+++ b/test/utils/.gitignore
@@ -1,5 +1,6 @@
 # In-place built executables
 baddof
 badioctl
+workload_user
 print-stack-layout
 showUSDT
diff --git a/test/utils/Build b/test/utils/Build
index 992fca19..b57cdda8 100644
--- a/test/utils/Build
+++ b/test/utils/Build
@@ -1,9 +1,9 @@
 # Oracle Linux DTrace.
-# Copyright (c) 2011, 2022, Oracle and/or its affiliates. All rights reserved.
+# Copyright (c) 2011, 2023, 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.
 
-TEST_UTILS = baddof badioctl showUSDT print-stack-layout
+TEST_UTILS = baddof badioctl workload_user showUSDT print-stack-layout
 
 define test-util-template
 CMDS += $(1)
diff --git a/test/utils/check_result.sh b/test/utils/check_result.sh
new file mode 100755
index 00000000..9509512a
--- /dev/null
+++ b/test/utils/check_result.sh
@@ -0,0 +1,24 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+actual=$1
+expect=$2
+margin=$3
+
+printf " check %10s;  against %10s;  margin %10s:  " $actual $expect $margin
+
+if [ $actual -lt $(($expect - $margin)) ]; then
+	echo ERROR too small
+	exit 1
+fi
+if [ $actual -gt $(($expect + $margin)) ]; then
+	echo ERROR too large
+	exit 1
+fi
+
+echo success
+exit 0
diff --git a/test/utils/workload_analyze_loop.sh b/test/utils/workload_analyze_loop.sh
new file mode 100755
index 00000000..28f5fb4b
--- /dev/null
+++ b/test/utils/workload_analyze_loop.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+# Analyze the loop in the specified workload program ($1), in test/utils/.
+
+prog=`dirname $0`/$1
+if [ ! -e $prog ]; then
+	echo -1 -1 -1
+	exit 1
+fi
+
+objdump -d $prog | awk -v myarch=$(uname -m) '
+# decide whether to track instructions (which we number n = 1, 2, 3, ...) or not (n < 0)
+# specifically, do not track instructions until we find the disassembly for <main>
+BEGIN { n = -1; }
+/[0-9a-f]* <main>:$/ { n = 0; next }
+
+# skip over instructions we are not tracking and null instructions
+n < 0 { next }
+/^ *[0-9a-f]*:	[0 ]*$/ { next }
+
+# if we are tracking but hit a blank line, the <main> disassembly is over and we found no loop
+NF == 0 {
+	print -1, -1, -1;
+	exit 1;
+}
+
+# track this instruction
+{ n++; instr[n] = $1; }
+
+# remove the instruction hexcodes (between the first two tabs)
+{ sub("	[^	]*	", "	"); }
+
+# look for a conditional jump
+#   * x86: look for a j* instruction, but not ja or jmp
+#   * ARM: look for a b.* instruction
+( myarch == "x86_64" && ( /	j/ && !/	ja / && !/	jmp / ) ) ||
+( myarch == "aarch64" && /	b\./ ) {
+
+	# look for the jump target among past instructions
+	target = $3;
+	for (i = 1; i <= n; i++) {
+		if (index(instr[i], target)) {
+			break;
+		}
+	}
+
+	# if we found the target, we have identified a loop
+	if (i <= n) {
+		# report: num of instructions per loop, first PC, and last PC
+		print n + 1 - i, instr[i], instr[n];
+
+		# report: all the PCs in the loop (one per line)
+		for (i = i; i <= n; i++) print instr[i];
+		exit 0;
+	}
+}' | tr ":" " "      # and get rid of those extraneous ":"
+
+exit $?
diff --git a/test/utils/workload_get_iterations.sh b/test/utils/workload_get_iterations.sh
new file mode 100755
index 00000000..a2803186
--- /dev/null
+++ b/test/utils/workload_get_iterations.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+#
+# Oracle Linux DTrace.
+# Copyright (c) 2023, 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.
+
+# Get the number of iterations needed for the target program ($1)
+# to run in the specified number of seconds ($2).
+
+# The target program, in test/utils/, which takes some iteration count
+# as a command-line argument.
+prog=`dirname $0`/$1
+if [ ! -e $prog ]; then
+	echo -2
+	exit 1
+fi
+
+# The target number of seconds we want the program to run.
+nsecs=$2
+
+# For the target program and number of seconds, return an estimate of how
+# many iterations are needed.
+
+function time_me() {
+	# FIXME: exit this script if we encounter an error
+	/usr/bin/time -f "%e" $prog $1 2>&1
+}
+
+# Confirm that one iteration is not enough.
+t=$(time_me 1)
+if [ `echo "$t >= 1" | bc` -eq 1 ]; then
+	echo -1
+	exit 1
+fi
+
+# Increase estimate exponentially until we we are in range.
+n=1
+while [ `echo "$t < 0.1 * $nsecs" | bc` -eq 1 ]; do
+
+	# protect against run-away values
+	if [ `echo "$n > 30000000000" | bc` -eq 1 ]; then
+		echo -$n
+		exit 1
+	fi
+
+	# increase n
+	n=`echo "$n * 10" | bc`
+	t=$(time_me $n)
+done
+
+# At this point, we should be close enough that we can extrapolate.
+echo "$nsecs * $n / $t" | bc
+exit 0
diff --git a/test/utils/workload_user.c b/test/utils/workload_user.c
new file mode 100644
index 00000000..4ce623b4
--- /dev/null
+++ b/test/utils/workload_user.c
@@ -0,0 +1,33 @@
+/*
+ * Oracle Linux DTrace.
+ * Copyright (c) 2023, 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.
+ */
+
+#include <stdlib.h>
+
+/*
+ * The command-line argument specifies how many iterations to execute
+ * in this user-space-intensive loop to run.
+ */
+
+int
+main(int argc, const char **argv)
+{
+	double x = 0.;
+	long long n;
+
+	if (argc < 2)
+		return 1;
+	n = atoll(argv[1]);
+
+	for (long long i = 0; i < n; i++)
+		x = 0.5 * x + 1.;
+
+	/*
+	 * Check the result (albeit simplistically)
+	 * so compiler won't optimize away the loop.
+	 */
+	return (x > 5.) ? 1 : 0;
+}
-- 
2.18.4




More information about the DTrace-devel mailing list