いくつかの型のグループから成る新しい型を作成するときは、D のキーワード struct (「structure (構造体)」の略) を使用します。この新しい構造体型を D の変数や配列の型として使用することにより、関連性のある複数の変数を単一の名前で定義できます。D の構造体は、C や C++ の対応する構造体と同じです。D の構造体は、Java プログラミングにおけるクラスに似ていますが、クラスとは違ってメソッドを持たず、データメンバーだけを備えています。
以下では、D を使って、シェルでシステムコール read(2) または write(2) が実行されるたびに、経過時間、呼び出し回数、引数として渡される最大バイト数などのデータを記録する、複雑なシステムコールトレースプログラムを作成してみましょう。次の例のように、3 つの連想配列を使ってプロパティを記録する D 節を記述できます。
syscall::read:entry, syscall::write:entry /pid == 12345/ { ts[probefunc] = timestamp; calls[probefunc]++; maxbytes[probefunc] = arg2 > maxbytes[probefunc] ? arg2 : maxbytes[probefunc]; }
ただし、この節では、DTrace は 3 つの連想配列を作成し、それぞれに probefunc に対応する同一の組の値の各コピーを格納するため、効率面で問題があります。構造体を使用すると、読みやすく管理もしやすい、よりコンパクトなプログラムになります。まず、プログラムソースファイルの冒頭で新しい構造体型を宣言します。
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 の後ろに、この新しい型を参照する識別子 (オプション) を付けて、struct callinfo としています。続く中括弧 ({ }) 内には構造体のメンバーが記述され、宣言全体はセミコロン (;) で終わります。 構造体のメンバーの定義には、D 変数宣言と同じ構文を使用します。最初にメンバーの型を入力し、次に識別子名、最後にセミコロン (;) を入力します。
構造体の宣言には、新しい型を定義する働きしかありません。この宣言自体が、変数を作成したり、DTrace 内の記憶域を割り当てたりすることはありません。宣言後は、D プログラム内で struct callinfo を型として使用できます。構造体テンプレートを使用して、struct callinfo 型の変数 1 つにつき、4 つの変数のコピーが格納されます。メンバーは、メンバーリストの順序に従ってメモリー内に配置されます。データオブジェクトの配置に必要な場合は、メンバー間にパディングスペースが挿入されます。
個々のメンバーの値にアクセスするには、演算子「.」とメンバーの識別子名を使って、次のような式を作成します。
variable-name.member-name
以下に、新しい構造体型を使った改良版のプログラム例を示します。エディタで以下の D プログラムを入力し、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); }
プログラムを入力したら、シェルプロセスを 1 つ指定して、dtrace -q -s rwinfo.d を実行します。次に、シェルコマンドをいくつか入力します。シェルコマンドを入力し終わったら、dtrace 端末ウィンドウ内で Control-C キーを押します。すると、END プローブが起動し、結果が出力されます。
# dtrace -q -s rwinfo.d `pgrep -n ksh` ^C calls max bytes elapsed nsecs ------ ----- --------- ------------- read 36 1024 3588283144 write 35 59 14945541 # |