Solaris 動的トレースガイド

構造体

いくつかの型のグループから成る新しい型を作成するときは、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 という名前のファイルに保存してください。


例 7–1 rwinfo.d: read(2) と write(2) の統計情報の収集

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
#