Thread-Local Variables

In DTrace, you can declare a variable storage that is local to each operating system thread. 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 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 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 zeros. 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. For other techniques to fine-tune the dynamic variable space, see DTrace Options and Tunables.

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 are not required 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.

Example 2-4 Computing Time Spent in read

In a text editor, type in the following program and save it in a file named rtime.d.

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);
        /*
         * Done with the thread-local variable; assign zero to it to
         * allow the DTrace runtime to reclaim the underlying storage.
         */
        self->t = 0;
}

Go to your shell and type the following command to see a similar output.

# 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 to capture a timestamp on entry to read by any thread. Then, in the return clause, the program prints out the amount of time spent in read 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. 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 all the time. Try changing the second clause of rtime.d to use the execname variable to print out the name of the process performing a read to learn more:

printf("%s/%d spent %d nsecs in read(2)\n",
    execname, tid, timestamp - self->t);

If you find a process that is of particular interest, add a predicate to learn more about its read behavior:

syscall::read:entry
/execname == "Xsun"/
{
        self->t = timestamp;
}

For more information about read system call, see the read(2) man page.