いくつかの型のグループから成る新しい型を作成するときは、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
#
|