Solaris 動的トレースガイド

述語

D とその他のプログラミング言語 (C、C++、Java など) のもっとも顕著な違いは、D では、if 文やループに代表される制御フロー構文が使用されない点にあります。D プログラム節は、決まった量のデータ (オプション) をトレースする単一の文のリストとして記述されます。D では、プログラム節の前に置かれる「述語」という論理式を使って、条件付きでデータをトレースしたり、制御フローを変更したりできます。述語式は、プローブの起動時、対応する節に関連付けられた文が実行される前に評価されます。述語の評価の結果が真 (ゼロ以外の値) なら、文のリストが実行されます。述語の評価の結果が偽 (ゼロ) なら、文は実行されず、プローブの起動は無視されます。

次のソースコードを入力し、countdown.d という名前のファイルに保存してください。

dtrace:::BEGIN
{
	i = 10;
}

profile:::tick-1sec
/i > 0/
{
	trace(i--);
}

profile:::tick-1sec
/i == 0/
{
	trace("blastoff!");
	exit(0);
}

この D プログラムは、述語を使用することにより、10 秒間のカウントダウンを行うタイマーを実装しています。countdown.d を実行すると、10 からカウントダウンが開始され、メッセージが表示されたあと、プログラムは終了します。

# dtrace -s countdown.d
dtrace: script 'countdown.d' matched 3 probes
CPU     ID                    FUNCTION:NAME
	0  25499                       :tick-1sec        10
	0  25499                       :tick-1sec         9
	0  25499                       :tick-1sec         8
	0  25499                       :tick-1sec         7
	0  25499                       :tick-1sec         6
	0  25499                       :tick-1sec         5
	0  25499                       :tick-1sec         4
	0  25499                       :tick-1sec         3
	0  25499                       :tick-1sec         2
	0  25499                       :tick-1sec         1
	0  25499                       :tick-1sec   blastoff!
# 

このプログラムは、まず BEGIN プローブを使って、整数 i をカウントダウンの開始値 10 に初期化します。次に、前記の例と同じく tick-1sec プローブを使って、毎秒 1 回起動するタイマーを実装します。countdown.d では、tick-1sec プローブが述語とアクションリストの異なる 2 つの節に記述されている点に注目してください。述語は、プローブ名と節の文が入っている中括弧 ({ }) との間に置かれた論理式です。前後をスラッシュ (/ /) で囲まれた形式になっています。

最初の述語は、i がゼロより大きいかどうか (タイマーがまだ実行中かどうか) をテストします。

profile:::tick-1sec
/i > 0/
{
	trace(i--);
}

関係演算子 > は、「-より大きい」を意味し、偽の場合は整数値 0、真の場合は 1 を返します。D では、C の関係演算子がすべてサポートされます。使用可能な関係演算子のリストは、第 2 章型、演算子、および式に記載されています。i がまだゼロになっていない場合、スクリプトは i をトレースし、-- 演算子を使って、値を 1 減らします。

2 番目の述語は、== 演算子を使って、i が 0 (カウントダウンが完了した) の場合に真を返します。

profile:::tick-1sec
/i == 0/
{
	trace("blastoff!");
	exit(0);
}

最初の hello.d の例の場合と同様に、 countdown.d は、二重引用符で囲まれた文字列 (「文字列定数」) を使って、カウントダウン完了時のメッセージを出力します。次に、exit() 関数を使って dtrace を終了し、シェルプロンプトに戻ります。

countdown.d の構造をよく見ると、プローブの記述は同じであるが述語とアクションが異なる 2 つの節を作成することによって、次のような論理フローが作成されていることがわかります。

i = 10
毎秒 1 回
	i がゼロより大きい場合
		trace(i--);
	i がゼロの場合
		trace("blastoff!");
		exit(0);

述語を使って複雑なプログラムを作成するときは、まずこの方法でアルゴリズムを視覚化した上で、それぞれの条件構文を別々の節および述語に変換してみることをお勧めします。

では次に、述語を新しいプロバイダ syscall と組み合わせて、実際に D トレースプログラムを作成してみましょう。syscall プロバイダには、任意の Solaris システムコールの開始時または終了時にプローブを有効にする機能があります。次の例では、DTrace を使って、シェルがシステムコール read(2) または write(2) を実行する様子を観察します。まず、端末ウィンドウを 2 つ開きます。1 つは DTrace 用、もう 1 つは監視するシェルプロセス用です。後者のウィンドウに次のコマンドを入力して、このシェルのプロセス ID を取得します。


# echo $$
12345

1 つ目の端末ウィンドウに戻り、次の D プログラムを入力して、rw.d という名前のファイルに保存します。プログラムを入力する際、12345 は、シェルのプロセス ID (echo コマンドの実行結果) で置き換えてください。

syscall::read:entry,
syscall::write:entry
/pid == 12345/
{

}

rw.d のプローブ節の本体には、何も入力しません。これは、このプログラムではプローブの起動通知をトレースするだけで、ほかには何もトレースしないからです。rw.d の内容を入力できたら、dtrace を使って計測を開始します。その後、2 つ目のウィンドウでいくつかのコマンドを実行します。なお、コマンドを 1 つ入力したら、そのたびに Return キーを押す必要があります。コマンドの実行に合わせて、1 つ目のウィンドウに、dtrace からのプローブ起動通知が表示されます。以下の出力例を参照してください。


# dtrace -s rw.d
dtrace: script 'rw.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
	0     34                      write:entry 
	0     32                       read:entry 
	0     34                      write:entry 
	0     32                       read:entry 
	0     34                      write:entry 
	0     32                       read:entry 
	0     34                      write:entry 
	0     32                       read:entry 
...

この出力から、シェルがシステムコール read(2)write(2) を実行することにより、端末ウィンドウから文字を読み取り、結果をエコーバックしていることがわかります。この例では、これまでに説明した概念のほかに、いくつかの新しい概念が含まれています。まず、read(2)write(2) を同じ方法で計測するため、複数のプローブ記述をコンマで区切った形式の単一のプローブ節が使用されています。

syscall::read:entry,
syscall::write:entry

読みやすさを考慮して、これらのプローブ記述は 2 行に分けて入力されています。この配置は必須ではありませんが、より読みやすいスクリプトを作成するのに役立ちます。次に、該当のシェルプロセスが実行するシステムコールだけを検出する述語が定義されています。

/pid == 12345/

この述語では、事前定義済みの DTrace 変数 pid (対応するプローブを起動したスレッドのプロセス ID が入る) が使用されています。DTrace には、プロセス ID のような使用頻度の高い情報を導き出す変数定義が多数組み込まれています。以下に、基本的な D プログラムの作成に役立つ DTrace 変数をいくつか紹介しておきます。

変数名 

データ型 

意味 

errno

int

システムコールの現在の errno

execname

string

現在のプロセスの実行可能ファイルの名前 

pid

pid_t

現在のプロセスのプロセス ID 

tid

id_t

現在のスレッドのスレッド ID 

probeprov

string

現在のプローブ記述のプロバイダフィールド 

probemod

string

現在のプローブ記述のモジュールフィールド 

probefunc

string

現在のプローブ記述の関数フィールド 

probename

string

現在のプローブ記述の名前フィールド 

作成した計測機能プログラムを使用し、プロセス ID と計測するシステムコールプローブを変更して、システム上で実行中のさまざまなプロセスを計測してみてください。その後、もう少し簡単な変更を加えることで、rw.dtruss(1) のような非常に単純なシステムコールトレースツールに変更できます。何も記述されていないプローブ記述フィールドは、「あらゆるプローブ」を表すワイルドカードとして機能します。上記のプログラムを次のような新しいソースコードに変更することによって、シェルで実行されるあらゆるシステムコールをトレースできるようになります。

syscall:::entry
/pid == 12345/
{

}

シェルに cdlsdate などのコマンドを入力して、DTrace プログラムからの報告内容を確認してください。