Union Types in DTrace
Unions are another kind of composite type supported by ANSI-C and D, and are closely related to structs. A union is a composite type where a set of members of different types are defined and the member objects all occupy the same region of storage. A union is therefore an object of variant type, where only one member is valid at any given time, depending on how the union has been assigned. Typically, some other variable or piece of state is used to indicate which union member is currently valid. The size of a union is the size of its largest member, and the memory alignment used for the union is the maximum alignment required by the union members.
The Oracle Solaris kstat
framework defines a struct containing a union that is used in the following example to illustrate and observe C and D unions. The kstat
framework is used to export a set of named counters representing kernel statistics such as memory usage and I/O throughput. The framework is used to implement utilities such as mpstat
and iostat
. This framework uses struct kstat_named
to represent a named counter and its value and is defined as follows:
struct kstat_named { char name[KSTAT_STRLEN]; /* name of counter */ uchar_t data_type; /* data type */ union { char c[16]; int32_t i32; uint32_t ui32; long l; ulong_t ul; ... } value; /* value of counter */ };
The examined declaration is shortened for illustrative purposes. The complete structure definition can be found in the <sys/kstat.h>
header file and is described in kstat_named
man page. The preceding declaration is valid in both ANSI-C and D, and defines a struct containing as one of its members a union value with members of various types, depending on the type of the counter. Notice that since the union itself is declared inside of another type, struct kstat_named
, a formal name for the union type is omitted. This declaration style is known as an anonymous union. The member named value
is of a union type described by the preceding declaration, but this union type itself has no name because it does not need to be used anywhere else. The struct member data_type
is assigned a value that indicates which union member is valid for each object of type struct kstat2_named
. A set of C preprocessor tokens are defined for the values of data_type. For example, the token KSTAT_DATA_CHAR
is equal to zero and indicates that the member value.c
is where the value is currently stored. For more information, see the kstat2_named
(9S) man page.
The kstat
counters can be sampled from a user process using the kstat_data_lookup
() function, which returns a pointer to a struct kstat_named
. For more information, see the kstat_lookup
(3KSTAT) man page. The mpstat
utility calls this function repeatedly as it executes in order to sample the latest counter values. Go to your shell and try running mpstat 1
and observe the output. Press Control-C in your shell to abort mpstat
after a few seconds. To observe counter sampling, enable a probe that fires each time the mpstat
command calls the kstat_data_lookup
() function in libkstat
. To do so, make use of a new DTrace provider: pid
. The pid
provider enables you to dynamically create probes in user processes at C symbol locations such as function entry points. You can ask the pid
provider to create a probe at a user function entry and return sites by writing probe descriptions of the form:
pidprocess-ID:object-name:function-name:entry pidprocess-ID:object-name:function-name:return
For example, if you wanted to create a probe in process ID 12345 that fires on entry to kstat_data_lookup
, you would write the following probe description:
pid12345:libkstat:kstat_data_lookup:entry
The pid
provider inserts dynamic instrumentation into the specified user process at the program location corresponding to the probe description. The probe implementation forces each user thread that reaches the instrumented program location to trap into the operating system kernel and enter DTrace, firing the corresponding probe. So although the instrumentation location is associated with a user process, the DTrace predicates and actions you specify still execute in the context of the operating system kernel. The pid
provider is described in further detail in pid Provider.
To apply your D program to different processes, use macro variables. Macro variables are evaluated at compile time and are replaced with additional dtrace
command-line arguments. Macro variables are specified using a dollar sign $
followed by an identifier or digit. If you execute the command dtrace -s script foo bar baz
, the D compiler will automatically define the macro variables $1
, $2
, and $3
to be the tokens foo
, bar
, and baz
respectively. You can use macro variables in D program expressions or in probe descriptions.
For more information about macro variables and reusable scripts, see Scripting in DTrace. Now that you know how to instrument user processes using their process ID, return to sampling unions.
Example 2-10 Tracing Calls to kstat_data_lookup
Type the following source code in a text editor and save it as kstat.d
:
pid$1:libkstat:kstat_data_lookup:entry { self->ksname = arg1; } pid$1:libkstat:kstat_data_lookup:return /self->ksname != NULL && arg1 != NULL/ { this->ksp = (kstat_named_t *) copyin(arg1, sizeof (kstat_named_t)); printf("%s has ui64 value %u\n", copyinstr(self->ksname), this->ksp->value.ui64); } pid$1:libkstat:kstat_data_lookup:return /self->ksname != NULL && arg1 == NULL/ { self->ksname = NULL; }
Now go to one of your shells and execute the command zonestat
to start zonestat
running in a mode where it samples statistics and reports them once per second. Once zonestat
is running, execute the command dtrace -q -s kstat.d `pgrep zonestatd`
in your other shell. You will see output corresponding to the statistics that are being accessed. Press Control-C to abort dtrace
and return to the shell prompt.
# dtrace -q -s kstat.d `pgrep
zonestatd`
hat_fault has ui64 value 0
as_fault has ui64 value 48053
maj_fault has ui64 value 1144
xcalls has ui64 value 123832170
intr has ui64 value 165264090
intrthread has ui64 value 124094974
pswitch has ui64 value 840625
inv_swtch has ui64 value 1484
cpumigrate has ui64 value 36284
mutex_adenters has ui64 value 35574
rw_rdfails has ui64 value 2
rw_wrfails has ui64 value 2
...
^C
#
If you capture the output in each terminal window and subtract each value from the value reported by the previous iteration through the statistics, you should be able to correlate the dtrace
output with the mpstat
output. The example program records the counter name pointer on entry to the lookup function, and then performs most of the tracing work on return from kstat_data_lookup
. The D built-in functions copyinstr
() and copyin
() copy the function results from the user process back into DTrace when arg1
(the return value) is not NULL
. Once the kstat
data has been copied, the example reports the ui64
counter value from the union. This simplified example assumes that mpstat
samples counters that use the value.ui64
member. As an exercise, try recoding kstat.d
to use multiple predicates and print out the union member corresponding to the data_type
member. You can also try to create a version of kstat.d
that computes the difference between successive data values and actually produces output similar to mpstat
.