2.4 Predicate Examples

For runtime safety, one major difference between D and other
programming languages such as C, C++, and the Java programming
language is the absence of control-flow constructs such as
if-statements and loops. D program clauses are
written as single straight-line statement lists that trace an
optional, fixed amount of data. D does provide the ability to
conditionally trace data and modify control flow using logical
expressions called predicates. A predicate
expression is evaluated at probe firing time prior to executing
any of the statements associated with the corresponding clause. If
the predicate evaluates to true, represented by any non-zero
value, the statement list is executed. If the predicate is false,
represented by a zero value, none of the statements are executed
and the probe firing is ignored.

Type the following source code for the next example and save it in
a file named countdown.d:

This example uses the BEGIN probe to initialize
an integer i to 10 to begin the countdown.
Next, as in the previous example, the program uses the
tick-1sec probe to implement a timer that fires
once per second. Notice that in countdown.d,
the tick-1sec probe description is used in two
different clauses, each with a different predicate and action
list. The predicate is a logical expression surrounded by
enclosing slashes // that appears after the
probe name and before the braces {} that
surround the clause statement list.

The first predicate tests whether i is greater
than zero, indicating that the timer is still running:

profile:::tick-1sec
/i > 0/
{
trace(i--);
}

The relational operator > means
greater than and returns the integer value
zero for false and one for true. All of the C relational operators
are supported in D. For the complete list, see
Section 2.8, “Types, Operators, and Expressions”. If i is not
yet zero, the script traces i and then
decrements it by one using the -- operator.

The second predicate uses the == operator to
return true when i is exactly equal to zero,
indicating that the countdown is complete:

profile:::tick-1sec
/i == 0/
{
trace("blastoff!");
exit(0);
}

Similar to the first example, hello.d,
countdown.d uses a sequence of characters
enclosed in double quotes, called a string
constant, to print a final message when the countdown
is complete. The exit function is then used to
exit dtrace and return to the shell prompt.

If you look back at the structure of
countdown.d, you will see that by creating two
clauses with the same probe description but different predicates
and actions, we effectively created the logical flow:

i = 10
once per second,
if i is greater than zero
trace(i--);
if i is equal to zero
trace("blastoff!");
exit(0);

When you wish to write complex programs using predicates, try to
first visualize your algorithm in this manner, and then transform
each path of your conditional constructs into a separate clause
and predicate.

Now let us combine predicates with a new provider, the
syscall provider, and create our first real D
tracing program. The syscall provider permits
you to enable probes on entry to or return from any Oracle Linux
system call. The next example uses DTrace to observe every time
your shell performs a read() or
write() system call. First, open two windows,
one to use for DTrace and the other containing the shell process
that you are going to watch. In the second window, type the
following command to obtain the process ID of this shell:

# echo $$
2860

Now go back to your first window and type the following D program
and save it in a file named rw.d. As you type
in the program, replace the integer constant
2860 with the process ID of the shell that was
printed in response to your echo command.

syscall::read:entry,
syscall::write:entry
/pid == 2860/
{
}

Notice that the body of rw.d's probe clause is
left empty because the program is only intended to trace
notification of probe firings and not to trace any additional
data. Once you have typed in rw.d, use
dtrace to start your experiment and then go to
your second shell window and type a few commands, pressing return
after each command. As you type, you should see
dtrace report probe firings in your first
window, similar to the following example:

You are now watching your shell perform read()
and write() system calls to read a character
from your terminal window and echo back the result. This example
includes many of the concepts described so far and a few new ones
as well. First, to instrument read() and
write() in the same manner, the script uses a
single probe clause with multiple probe descriptions by separating
the descriptions with commas like this:

syscall::read:entry,
syscall::write:entry

For readability, each probe description appears on its own line.
This arrangement is not strictly required, but it makes for a more
readable script. Next the script defines a predicate that matches
only those system calls that are executed by your shell process:

/pid == 2860/

The predicate uses the predefined DTrace variable
pid, which always evaluates to the process ID
associated with the thread that fired the corresponding probe.
DTrace provides many built-in variable definitions for useful
things like the process ID. The following table lists a few DTrace
variables you can use to write your first D programs.

Variable Name

Data Type

Meaning

errno

int

Current errno value for system calls

execname

string

Name of the current process's executable file

pid

pid_t

Process ID of the current process

tid

id_t

Thread ID of the current thread

probeprov

string

Current probe description's provider field

probemod

string

Current probe description's module field

probefunc

string

Current probe description's function field

probename

string

Current probe description's name field

Now that you've written a real instrumentation program, try
experimenting with it on different processes running on your
system by changing the process ID and the system call probes that
are instrumented. Then, you can make one more simple change and
turn rw.d into a very simple version of a
system call tracing tool like strace. An empty
probe description field acts as a wildcard, matching any probe, so
change your program to the following new source code to trace any
system call executed by your shell:

syscall:::entry
/pid == 2860/
{
}

Try typing a few commands in the shell such as
cd, ls, and
date and see what your DTrace program reports.