Thread-Local Variables
DTrace provides the ability to declare variable storage that is local to each
operating system thread, as opposed to the global variables demonstrated earlier in this
chapter. Thread-local variables are useful in situations where you want to enable a
probe and mark every thread that fires the probe with some tag or
other data. Creating a program to solve this problem is easy in D
because thread-local variables share a common name in your D code but refer
to separate data storage associated with each thread. Thread-local variables are referenced by
applying the -> operator to the special identifier self:
syscall::read:entry
{
self->read = 1;
}
This D fragment example enables the probe on the read(2) system call
and associates a thread-local variable named read with each thread that fires the probe.
Similar to global variables, thread-local variables are created automatically on their first assignment
and assume the type used on the right-hand side of the first assignment
statement (in this example, int).
Each time the variable self->read is referenced in your D program, the data
object referenced is the one associated with the operating system thread that was
executing when the corresponding DTrace probe fired. You can think of a thread-local
variable as an associative array that is implicitly indexed by a tuple that
describes the thread's identity in the system. A thread's identity is unique over
the lifetime of the system: if the thread exits and the same operating
system data structure is used to create a new thread, this thread does
not reuse the same DTrace thread-local storage identity.
Once you have defined a thread-local variable, you can reference it for any
thread in the system even if the variable in question has not
been previously assigned for that particular thread. If a thread's copy of the thread-local
variable has not yet been assigned, the data storage for the copy is
defined to be filled with zeroes. As with associative array elements, underlying storage
is not allocated for a thread-local variable until a non-zero value is assigned
to it. Also as with associative array elements, assigning zero to a thread-local
variable causes DTrace to deallocate the underlying storage. Always assign zero to thread-local
variables that are no longer in use. See Chapter 16, Options and Tunables for other techniques to fine-tune
the dynamic variable space from which thread-local variables are allocated.
Thread-local variables of any type can be defined in your D program, including
associative arrays. Some example thread-local variable definitions are:
self->x = 123; /* integer value */
self->s = "hello"; /* string value */
self->a[123, 'a'] = 456; /* associative array */
Like any D variable, you don't need to explicitly declare thread-local variables before
using them. If you want to create a declaration anyway, you can place
one outside of your program clauses by prepending the keyword self:
self int x; /* declare int x as a thread-local variable */
syscall::read:entry
{
self->x = 123;
}
Thread-local variables are kept in a separate namespace from global variables so you
can reuse names. Remember that x and self->x are not the same variable if
you overload names in your program! The following example shows how to use
thread-local variables. In a text editor, type in the following program and save
it in a file named rtime.d:
Example 3-1 rtime.d: Compute Time Spent in read(2)
syscall::read:entry
{
self->t = timestamp;
}
syscall::read:return
/self->t != 0/
{
printf("%d/%d spent %d nsecs in read(2)\n",
pid, tid, timestamp - self->t);
/*
* We're done with this thread-local variable; assign zero to it to
* allow the DTrace runtime to reclaim the underlying storage.
*/
self->t = 0;
}
Now go to your shell and start the program running. Wait a
few seconds and you should start to see some output. If no output
appears, try running a few commands.
# dtrace -q -s rtime.d
100480/1 spent 11898 nsecs in read(2)
100441/1 spent 6742 nsecs in read(2)
100480/1 spent 4619 nsecs in read(2)
100452/1 spent 19560 nsecs in read(2)
100452/1 spent 3648 nsecs in read(2)
100441/1 spent 6645 nsecs in read(2)
100452/1 spent 5168 nsecs in read(2)
100452/1 spent 20329 nsecs in read(2)
100452/1 spent 3596 nsecs in read(2)
...
^C
#
rtime.d uses a thread-local variable named t to capture a timestamp on entry
to read(2) by any thread. Then, in the return clause, the program prints
out the amount of time spent in read(2) by subtracting self->t from the current
timestamp. The built-in D variables pid and tid report the process ID and thread
ID of the thread performing the read(2). Because self->t is no longer
needed once this information is reported, it is then assigned 0 to allow
DTrace to reuse the underlying storage associated with t for the current thread.
Typically you will see many lines of output without even doing anything because,
behind the scenes, server processes and daemons are executing read(2) all the
time even when you aren't doing anything. Try changing the second clause of
rtime.d to use the execname variable to print out the name of the process
performing a read(2) to learn more:
printf("%s/%d spent %d nsecs in read(2)\n",
execname, tid, timestamp - self->t);
If you find a process that's of particular interest, add a predicate to
learn more about its read(2) behavior:
syscall::read:entry
/execname == "Xsun"/
{
self->t = timestamp;
}