2.12.1 Structs

The D keyword struct, short for structure, is used to introduce a new type that is 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 previously, think of a D struct as a class that contains only data members 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 that is executed by your shell, for example, 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[string]; /* declare maxbytes */ 
syscall::read:entry, syscall::write:entry
/pid == 12345/
{
  ts[probefunc] = timestamp;
  calls[probefunc]++;
  maxbytes[probefunc] = arg2 > maxbytes[probefunc] ?
        arg2 : maxbytes[probefunc];
}

This clause, however, 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 D 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 that is 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 by 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 simply defines the new type. It does not create any variables or allocate any storage in DTrace. When declared, you can use struct callinfo as a type throughout the remainder of your D program. Each variable of type struct callinfo stores a copy of the four variables that are described by our structure template. The members are 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 following form:

variable-name.member-name

The following example is an improved program that uses the new structure type. In a text editor, type the following D program and save it in a file named 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);
}

When you have typed the program, run the dtrace -q -s rwinfo.d command, specifying one of your shell processes. Then, type a few commands in your shell. When you have finished typing the shell commands, type Ctrl-C to fire the END probe and print the results:

# dtrace -q -s rwinfo.d `pgrep -n bash`
^C
       calls max bytes elapsed nsecs
------ ----- --------- -------------
  read    25      1024 8775036488
 write    33        22 1859173