Structs
The D keyword struct, short for structure, is used to introduce a new type
composed of a group of other types. The new struct type can be
used as the type for D variables and arrays, enabling you to define
groups of related variables under a single name. D structs are the same
as the corresponding construct in C and C++. If you have programmed
in the Java programming language, think of a D struct as a class,
but one with data members only and no methods.
Let's suppose you want to create a more sophisticated system call tracing program
in D that records a number of things about each read(2) and
write(2) system call executed by your shell, such as the elapsed time, number
of calls, and the largest byte count passed as an argument. You could
write a D clause to record these properties in three separate associative arrays
as shown in the following example:
syscall::read:entry, syscall::write:entry
/pid == 12345/
{
ts[probefunc] = timestamp;
calls[probefunc]++;
maxbytes[probefunc] = arg2 > maxbytes[probefunc] ?
arg2 : maxbytes[probefunc];
}
However, this clause is inefficient because DTrace must create three separate associative arrays
and store separate copies of the identical tuple values corresponding to probefunc for
each one. Instead, you can conserve space and make your program easier to
read and maintain by using a struct. First, declare a new struct type
at the top of the program source file:
struct callinfo {
uint64_t ts; /* timestamp of last syscall entry */
uint64_t elapsed; /* total elapsed time in nanoseconds */
uint64_t calls; /* number of calls made */
size_t maxbytes; /* maximum byte count argument */
};
The struct keyword is followed by an optional identifier used to refer back
to our new type, which is now known as struct callinfo. The struct
members are then enclosed in a set of braces { } and the
entire declaration is terminated by a semicolon (;). Each struct member is
defined using the same syntax as a D variable declaration, with the type
of the member listed first followed by an identifier naming the member and
another semicolon (;).
The struct declaration itself simply defines the new type; it does not create
any variables or allocate any storage in DTrace. Once declared, you can use
struct callinfo as a type throughout the remainder of your D program, and each
variable of type struct callinfo will store a copy of the four variables described
by our structure template. The members will be arranged in memory in order
according to the member list, with padding space introduced between members as required
for data object alignment purposes.
You can use the member identifier names to access the individual member values
using the “.” operator by writing an expression of the form:
variable-name.member-name
The following example is an improved program using the new structure type. Go
to your editor and type in the following D program and save
it in a file named rwinfo.d:
Example 7-1 rwinfo.d: Gather read(2) and write(2) Statistics
struct callinfo {
uint64_t ts; /* timestamp of last syscall entry */
uint64_t elapsed; /* total elapsed time in nanoseconds */
uint64_t calls; /* number of calls made */
size_t maxbytes; /* maximum byte count argument */
};
struct callinfo i[string]; /* declare i as an associative array */
syscall::read:entry, syscall::write:entry
/pid == $1/
{
i[probefunc].ts = timestamp;
i[probefunc].calls++;
i[probefunc].maxbytes = arg2 > i[probefunc].maxbytes ?
arg2 : i[probefunc].maxbytes;
}
syscall::read:return, syscall::write:return
/i[probefunc].ts != 0 && pid == $1/
{
i[probefunc].elapsed += timestamp - i[probefunc].ts;
}
END
{
printf(" calls max bytes elapsed nsecs\n");
printf("------ ----- --------- -------------\n");
printf(" read %5d %9d %d\n",
i["read"].calls, i["read"].maxbytes, i["read"].elapsed);
printf(" write %5d %9d %d\n",
i["write"].calls, i["write"].maxbytes, i["write"].elapsed);
}
After you type in the program, run dtrace -q -s rwinfo.d, specifying one of your shell
processes. Then go type in a few commands in your shell and, when
you're done entering your shell commands, type Control-C in the dtrace terminal to
fire the END probe and print the results:
# dtrace -q -s rwinfo.d `pgrep -n ksh`
^C
calls max bytes elapsed nsecs
------ ----- --------- -------------
read 36 1024 3588283144
write 35 59 14945541
#