これまでの回でDTraceが実施するDスクリプトにおける変数は、
しかし、
今回は、
呼び出し頻度の採取
まずは統計情報採取の簡単な例として、
集積体
DTrace での統計情報採取には、
集積体は以下の様な点で通常の変数と異なります。
- 変数名の冒頭に必ずアットマーク
(" @")が付く - 変数名を省略可能
(省略した場合は " @_" 相当とみなされます) - 大域変数としてしか定義できない
- 集積体に代入できる値は
(後述する) 集積関数の戻り値のみ
目下の目標である
watch_count.d )pid$target:$1:$2:entry
{
@total = count();
}
リスト1は、dtraceコマンドの引数として、$1および$2と置換)。
それでは実際に実行してみましょう。ここでは実行例として、lsコマンドにおけるformat_関数の呼び出し回数を採取してみます。
$ dtrace -s watch_count.d \
-c '/usr/bin/ls -l' \
ls format_time
dtrace: script 'watch_count.d' matched 1 probe
total 2659
:
:
dtrace: pid 744 has exited
16
$
最後に表示される "16" がformat_ 関数の呼び出し回数です。
統計結果の表示
先ほどの実行例では、dtraceコマンド終了時点で、@totalの格納値が自動的に出力されました。
これは、dtrace コマンド終了におけるデフォルトの処理が、
しかしたとえば複数の集積体を使用している場合などに、
そこで、printa()アクションを使用して、
watch_count2.d )pid$target:$1:$2:entry
{
@total = count();
}
END
{
printa("total=%@u\n", @total);
}
ENDなる記述がありますが、dtraceプロバイダによって提供されているものです。
このプローブは、
- "
-c" オプション指定で実行されたプロセスが終了した場合 - "
-p" オプション指定で監視しているプロセスが終了した場合 - "
exit()" アクションで明示的に終了した場合 - Ctrl-C等により
dtraceコマンドの実行を中断した場合
Dスクリプトの開始時点で実施する処理を記述するためのBEGINプローブもあります。
さて、printa()アクションで実施することができます。
printa()の機能はおおむねprintf()と同じですが、%" ではなく "%@"を冒頭に持つ書式指定を使用してください。
実行例を以下に示します。
$ dtrace -s watch_count.d \
-c '/usr/bin/ls -l' \
ls format_time
dtrace: script 'watch_count2.d' matched 2 probes
total 2665
:
:
dtrace: pid 784 has exited
CPU ID FUNCTION:NAME
0 2 :END total=16
$
集積体による統計情報取得
集積関数
実のところ、count() 以外にもDTraceは以下のような集積関数を提供しています。
| 集積関数 | 算出対象 |
|---|---|
sum() |
指定された値の合計値 |
avg() |
指定された値の平均値 |
min() |
指定された値の最小値 |
max() |
指定された値の最大値 |
lquantize() |
指定された値の線形度数分布 |
quantize() |
指定された値の二乗分布 |
たとえば、arg0)
pid$target:$1:$2:entry
{
@val = avg(arg0);
}
通常の変数を用いて同等の処理をしようとする場合、
- 総呼び出し回数
- 各呼び出しごとの指定値
キーによる分類
実際に統計情報採取の機能を利用する場合には、
DTraceの集積体は、
たとえば、
pid$target:$1::entry
{
@total[probefunc] = count();
}
なお、printa()を使った出力書式指定は、
printa("%s = %@u\n", @total);
集積体がキーを持つ場合、printa()に指定された出力書式によるフォーマットは、
その際に、%@~"部分が集積体の値で置換される点は変わりありませんが、%s")probefuncなので文字列)
リスト4では、probefunc値=関数名をキーに使用しましたが、
また、ustack()ustack()ですが、
watch_count_ustack.d )pid$target:$1::entry
{
@total[ustack()] = count();
}
それでは実際に実行してみましょう。
$ dtrace -s watch_count_ustack.d \
-c '/usr/bin/ls -l' \
ls
dtrace: script 'watch_count_ustack.d' matched 32 probes
total 2664
:
:
dtrace: pid 782 has exited
:
ls`format_time
ls`pentry+0x39a
ls`pem+0x181
ls`main+0x915
ls`_start+0x7d
4
ls`format_time
ls`pentry+0x39a
ls`pem+0x18c
ls`main+0x915
ls`_start+0x7d
4
ls`format_time
ls`pentry+0x39a
ls`pem+0x197
ls`main+0x915
ls`_start+0x7d
4
:
....
ustack()をキーに使用することで、
上記の例では、ls`pem 関数におけるls`pentry 呼び出し位置の違いによって、
連想配列
連想配列とは
「連想配列とは?」
連想配列の記述形式は、
キーに指定する値は、ustackの戻り値を用いることもできます。
また、
たとえば、valueを、
pid$target:libxxxx::entry,
pid$target:commandz::entry
{
self->value[probemod, probefunc] = arg0;
}
前提条件指定における再帰呼び出し対応
リスト8は、arg0)
pid$target:XXXX:xxxx:entry
{
self->arg0 = arg0;
}
pid$target:XXXX:xxxx:return
/arg1 != 0/
{
printf("arg0=%p", self->arg0);
}
しかし対象関数XXXXモジュールのxxxx関数)
入れ子の深さに応じて呼び出し引数を格納できれば良いわけですから、
この例での連想配列 self->arg0 は、
self int depth;
pid$target:XXXX:xxxx:entry
{
self->depth += 1;
self->arg0[self->depth] = arg0;
}
pid$target:XXXX:xxxx:return
/arg1 != 0/
{
printf("arg0=%p", self->arg0[self->depth]);
}
/*
* 関数からの復帰における depth 減算は
* 戻り値に関わらず実施する必要があるので
* 戻り値に応じた処理を実施する節とは独立させる
*/
pid$target:XXXX:xxxx:return
{
self->depth -= 1;
}
採取対象関数が複数である場合でも、self->depthを連想配列self->arg0のキーを2次元
次回予告
集積体を用いた DTrace の統計情報採取機能には、
次回は、