Structs in DTrace
The D keyword struct,
short for structure, is used to introduce a new type composed of a group of other types. The 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.
Suppose you want to create a more sophisticated system call tracing program in D that records a number of things about each read
and write
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:
int maxbytes; /* declare maxbytes */ 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. Use a struct
type to conserve space and make the program easier to read and maintain. For example, declare a struct
type at the top of the program source file as follows:
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 the 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 the 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.
Example 2-8 Gathering read
and write
System Call Statistics
In a text editor type the following program and save as rwinfo.d
.
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
bash`
^C
calls max bytes elapsed nsecs
------ ----- --------- -------------
read 36 1024 3588283144
write 35 59 14945541