[DTrace-devel] [PATCH 03/16] cpc: Add some hardware-counter tests
Kris Van Hees
kris.van.hees at oracle.com
Fri Feb 24 00:50:20 UTC 2023
Reviewed-by: Kris Van Hees <kris.van.hees at oracle.com>
On Thu, Jan 26, 2023 at 09:23:16PM -0500, eugene.loh--- via DTrace-devel wrote:
> 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
>
>
> _______________________________________________
> DTrace-devel mailing list
> DTrace-devel at oss.oracle.com
> https://oss.oracle.com/mailman/listinfo/dtrace-devel
More information about the DTrace-devel
mailing list