DTrace は、集積関数の実行結果を「集積体」と呼ばれるオブジェクト内に格納します。この集積体の結果には、連想配列で使用するものとよく似た式の組でインデックスが付けられます。D の集積体の構文は、次のとおりです。
@name[ keys ] = aggfunc ( args );
name は集積体の名前、keys は複数の D 式をコンマで区切った形式のリスト、aggfunc は DTrace 集積関数、args は指定された集積関数の引数をコンマで区切った形式のリストです。集積体 name は、特殊文字 @ で始まる D 識別子です。D プログラム内で使用するすべての集積体は大域変数です。スレッド固有の集積体、節固有の集積体は存在しません。集積体名は、その他の D 大域変数とは別の識別子用名前空間に格納されます。名前を再利用する場合、a と @a はまったく別の変数になります。単純な D プログラム内で、名前のない集積体を指定するときは、特殊な集積体名 @ を使用します。D コンパイラは、この名前を集積体名 @_ の別名と解釈します。
以下の表に、DTrace の集積関数を示します。ほとんどの集積関数は、新しいデータを表す引数を 1 つとるだけです。
表 9–1 DTrace の集積関数|
関数名 |
引数 |
結果 |
|---|---|---|
|
count |
なし |
呼び出された回数。 |
|
sum |
スカラー式 |
指定された式の合計値。 |
|
avg |
スカラー式 |
指定された式の算術平均。 |
|
min |
スカラー式 |
指定された式のうちもっとも小さい値。 |
|
max |
スカラー式 |
指定された式のうちもっとも大きい値。 |
|
lquantize |
スカラー式、下限値、上限値、ステップ値 |
指定された式の値から成る、指定された範囲の線形度数分布。指定された式より小さい、最大バケット内の値を増分します。 |
|
quantize |
スカラー式 |
指定された式の値の二乗分布。指定された式より小さい、2 のべき乗の最大バケット内の値を増分します。 |
たとえば、システム内の write(2) システムコールの回数をカウントしたい場合、通知文字列をキーに指定して、集積関数 count() を使用します。
syscall::write:entry
{
@counts["write system calls"] = count();
}
デフォルトの設定では、dtrace コマンドを実行すると、プロセスの終了時に、それが明示的な END アクションの結果であるかユーザーが Control-C キーを押したためかにかかわらず、集積体の結果が出力されます。このコマンドを実行し、しばらく待ってから Control-C キーを押すと、次のような結果が得られます。
# dtrace -s writes.d dtrace: script './writes.d' matched 1 probe ^C write system calls 179 # |
プロセス名ごとのシステムコール数をカウントしたい場合は、集積体のキーとして execname 変数を指定します。
syscall::write:entry
{
@counts[execname] = count();
}
このコマンドを実行し、しばらく待ってから Control-C キーを押すと、次のような結果が得られます。
# dtrace -s writesbycmd.d dtrace: script './writesbycmd.d' matched 1 probe ^C dtrace 1 cat 4 sed 9 head 9 grep 14 find 15 tail 25 mountd 28 expr 72 sh 291 tee 814 def.dir.flp 1996 make.bin 2010 # |
書き込みの情報として、実行可能ファイルの名前とファイル記述子の両方を出力することもできます。ファイル記述子は、write(2) の最初の引数です。次の例では、キーとして execname と arg0 の両方が指定されています。
syscall::write:entry
{
@counts[execname, arg0] = count();
}
このコマンドを実行すると、実行可能ファイルの名前とファイル記述子を含む、次の例のような表が出力されます。
# dtrace -s writesbycmdfd.d dtrace: script './writesbycmdfd.d' matched 1 probe ^C cat 1 58 sed 1 60 grep 1 89 tee 1 156 tee 3 156 make.bin 5 164 acomp 1 263 macrogen 4 286 cg 1 397 acomp 3 736 make.bin 1 880 iropt 4 1731 # |
次の例では、write システムコールにかかった時間の平均が、プロセス名ごとに出力されます。この例では、平均を求める式を引数にとる集積関数 avg() が使用されています。したがって、システムコールにかかった時計時間の平均が求められます。
syscall::write:entry
{
self->ts = timestamp;
}
syscall::write:return
/self->ts/
{
@time[execname] = avg(timestamp - self->ts);
self->ts = 0;
}
このコマンドを実行し、しばらく待ってから Control-C キーを押すと、次のような結果が得られます。
# dtrace -s writetime.d dtrace: script './writetime.d' matched 2 probes ^C iropt 31315 acomp 37037 make.bin 63736 tee 68702 date 84020 sh 91632 dtrace 159200 ctfmerge 321560 install 343300 mcs 394400 get 413695 ctfconvert 594400 bringover 1332465 tail 1335260 # |
平均値はさまざまな場面で利用できる情報ですが、通常、この情報だけでは、データポイントの分布まではわかりません。データ分布の詳細を確認したい場合は、次の例のように、集積関数 quantize() を使用します。
syscall::write:entry
{
self->ts = timestamp;
}
syscall::write:return
/self->ts/
{
@time[execname] = quantize(timestamp - self->ts);
self->ts = 0;
}
このスクリプトの出力は、先ほどの例の出力よりも長くなります。これは、先ほどの例で 1 行に出力されていた内容が、度数分布表として出力されるからです。以下は、出力例の抜粋です。
lint
value ------------- Distribution ------------- count
8192 | 0
16384 | 2
32768 | 0
65536 |@@@@@@@@@@@@@@@@@@@ 74
131072 |@@@@@@@@@@@@@@@ 59
262144 |@@@ 14
524288 | 0
acomp
value ------------- Distribution ------------- count
4096 | 0
8192 |@@@@@@@@@@@@ 840
16384 |@@@@@@@@@@@ 750
32768 |@@ 165
65536 |@@@@@@ 460
131072 |@@@@@@ 446
262144 | 16
524288 | 0
1048576 | 1
2097152 | 0
iropt
value ------------- Distribution ------------- count
4096 | 0
8192 |@@@@@@@@@@@@@@@@@@@@@@@ 4149
16384 |@@@@@@@@@@ 1798
32768 |@ 332
65536 |@ 325
131072 |@@ 431
262144 | 3
524288 | 2
1048576 | 1
2097152 | 0
|
度数分布の行は必ず 2 のべき乗の値になります。各行の右側に表示されているカウントは、その行の左側に表示されている値以上、次の行の左側に表示されている値未満に対応する要素数です。たとえば、上の出力例からは、iropt が、8,192 ナノ秒から 16,383 ナノ秒の間に 4,149 回の書き込みを行なっていることがわかります。
quantize() は、データの内容を短時間で把握するには便利ですが、線形値の分布を調べたい場合もあります。線形値の分布を表示するには、集積関数 lquantize() を使用します。lquantize() 関数を使用するときは、D 式と 3 つの引数 (下限値、上限値、ステップ値) を指定します。たとえば、ファイル記述子別に書き込みの分布を調べたい場合、2 のべき乗の量子化では効率がよくありません。そこで、次のように、狭い範囲で線形量子化を行います。
syscall::write:entry
{
@fds[execname] = lquantize(arg0, 0, 100, 1);
}
このスクリプトは、数秒実行しただけで大量の情報を出力します。以下は、一般的な出力例の抜粋です。
mountd
value ------------- Distribution ------------- count
11 | 0
12 |@ 4
13 | 0
14 |@@@@@@@@@@@@@@@@@@@@@@@@@ 70
15 | 0
16 |@@@@@@@@@@@@ 34
17 | 0
xemacs-20.4
value ------------- Distribution ------------- count
6 | 0
7 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 521
8 | 0
9 | 1
10 | 0
make.bin
value ------------- Distribution ------------- count
0 | 0
1 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 3596
2 | 0
3 | 0
4 | 42
5 | 50
6 | 0
acomp
value ------------- Distribution ------------- count
0 | 0
1 |@@@@@ 1156
2 | 0
3 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 6635
4 |@ 297
5 | 0
iropt
value ------------- Distribution ------------- count
2 | 0
3 | 299
4 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 20144
5 | 0
|
集積関数 lquantize() では、過去のある時点からのデータを集積することもできます。このテクニックを使って、時間の経過とともに変化する動作を監視できます。以下の例では、date(1) コマンドを実行中のプロセスにおける、システムコールの動作の変化を確認できます。
syscall::exec:return,
syscall::exece:return
/execname == "date"/
{
self->start = vtimestamp;
}
syscall:::entry
/self->start/
{
/*
* We linearly quantize on the current virtual time minus our
* process's start time. We divide by 1000 to yield microseconds
* rather than nanoseconds. The range runs from 0 to 10 milliseconds
* in steps of 100 microseconds; we expect that no date(1) process
* will take longer than 10 milliseconds to complete.
*/
@a["system calls over time"] =
lquantize((vtimestamp - self->start) / 1000, 0, 10000, 100);
}
syscall::rexit:entry
/self->start/
{
self->start = 0;
}
このスクリプトでは、複数の date(1) プロセスを実行しているときのシステムコールの動作を詳しく調べることができます。結果を表示するには、D スクリプトの実行中に別のウィンドウで sh -c 'while true; do date >/dev/null; done' を実行します。すると、date(1) コマンドのシステムコールの動作の概要が出力されます。
# dtrace -s dateprof.d
dtrace: script './dateprof.d' matched 218 probes
^C
system calls over time
value ------------- Distribution ------------- count
< 0 | 0
0 |@@ 20530
100 |@@@@@@ 48814
200 |@@@ 28119
300 |@ 14646
400 |@@@@@ 41237
500 | 1259
600 | 218
700 | 116
800 |@ 12783
900 |@@@ 28133
1000 | 7897
1100 |@ 14065
1200 |@@@ 27549
1300 |@@@ 25715
1400 |@@@@ 35011
1500 |@@ 16734
1600 | 498
1700 | 256
1800 | 369
1900 | 404
2000 | 320
2100 | 555
2200 | 54
2300 | 17
2400 | 5
2500 | 1
2600 | 7
2700 | 0
|
この出力から、カーネルを必要とするサービスについて、date(1) コマンドのさまざまなフェーズの概要を確認できます。これらのフェーズの詳細を把握するには、いつ、どのシステムコールが呼び出されたかを確認します。この場合は、定数文字列ではなく変数 probefunc について集積するように、D スクリプトを変更します。