|
| Japan Worldwide |
DTrace
|
|||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
目次
|
|
|
DTrace は、Solaris に組み込まれている包括的な動的トレース機能です。管理者や開発者は DTrace を使用することによって、ユーザ・プログラムの動作だけでなくオペレーティング・システム自体の動作も調べることができます。したがって、DTrace を使用すればシステムがどのように動いているかを理解したり、さまざまなソフトウェア層に渡る性能の問題を追跡したり、異常動作の原因を突き止めたりすることができます。DTrace は、本稼動中のシステムでも安全に使用でき、システムやアプリケーションを再起動する必要もありません。
DTrace は、オペレーティング・システム・カーネルやユーザ・プロセスに動的に変更を加えることで、ユーザの関心がある場所 (「プローブ」と呼ばれる) にデータを記録します。プローブとは、DTrace が一連のアクション (スタック・トレースやタイムスタンプ、関数の引数を記録するなど) の実行要求を結合する場所またはアクティビティを指します。プローブは、Solaris システム全体に渡って配置されたプログラミング可能なセンサーとして機能します。DTrace プローブは、「プロバイダ」と呼ばれる一連のカーネル・モジュールから提供されます。それぞれのプロバイダは、プローブを作成する上で特定の計測を行います。
DTrace には、動的トレース機能のために設計された D と呼ばれる新しいスクリプト言語が含まれています。D を使用すれば、プローブを動的に起動し、情報を収集し、処理するスクリプトを簡単に作成できます。さらに、D スクリプトを使用すると、ユーザ相互間で知識や問題解決方法を容易に共有できるようになります。Solaris 10 には有益な D スクリプトがいくつも含まれていますが、そのほかのスクリプトについての詳細は、Sun の BigAdmin サイト (sun.com/bigadmin/content/dtrace/) や OpenSolaris プロジェクト・サイト (opensolaris.org/os/community/dtrace/) をご覧ください。
最初に D スクリプトの構造について簡単に説明します。D スクリプトは、以下のように、probe description (プローブ記述)、predicate (述語) および actions (アクション) から構成されています。
probe description
/predicate/
{
actions
}
D スクリプトを実行すると、「プローブ記述」で表されるプローブが有効になります。プローブが起動され、述語が真と判定されると、アクション・ステートメントが実行されます。プローブはイベントのようなものです。イベントが発生するとプローブが起動されます。次の図は簡単な D スクリプトの例です。
syscall::write:entry
/execname == "bash"/
{
printf("bash with pid %d called write system call\n",pid);
}
プローブ記述は syscall::write:entry で、write システム・コールを表しています。述語は /execname =="bash"/ で、write システム・コールを呼び出している実行可能プログラムが bash シェルであるかチェックします。アクションは printf ステートメントで、bash が write システム・コールを呼び出すたびに実行されます。
それではもうすこし詳しくみてみましょう。プローブは、プロバイダ、モジュール、関数、名前の 4 つのフィールドからなります。
さらに、* や ? などのワイルド・カードも使用できます。ブランク・フィールドはワイルド・カードとみなされます。表 1 はプローブ記述の例です。
|
表 1 — プローブ記述の例
述語は、任意の D 式として表すことができます。表 2 は述語のいくつかの例です。アクションは、述語が真と判定されたときだけ実行されます。
|
表 2 — 述語の例
アクション・セクションには、一連のアクション・コマンドをセミコロン (;) で区切って指定します。表 3 はアクションの例です。
|
表 3 — アクションの例
述語とアクション・ステートメントの指定は任意です。述語を指定しないと、アクションが常に実行されます。アクションを指定しないと、起動されたプローブの名前が出力されます。
この最小限の情報でも、有益な D スクリプトを作成できます。以下のセクションでは、アプリケーション開発者の観点から見た DTrace の使い方とシステム管理者の観点から見た DTrace の使い方をそれぞれ説明します。
アプリケーション開発者にとって特に関心のある情報は、syscall、proc、pid、sdt、vminfo といったプロバイダを使用して取得できます。これらのプロバイダを使用すれば、開発者は動作中のプロセスのほかにも、プロセスの作成や終了、LWP の作成や終了、シグナル処理などを監視できます。このセクションでは、pid プロバイダについてのみ説明します。
pid プロバイダは、動作中のプロセスにある任意のユーザ・レベル関数の入口と出口を計測します。pid プロバイダでは、システム上の任意のプロセスの任意の命令をトレースできます。pid プロバイダの名前には、関心のある動作プロセスの pid を指定します。図 4 は、pid プロバイダを使用したプローブ記述の例です。
|
表 4 — pid プロバイダとそれに対応するプローブの例
プロセス id 1234 が呼び出すすべての関数を出力するコマンドは、次のようになります。
# dtrace -n pid1234:::entry
この同じコマンドをスクリプトで使用する場合は、次のようになります。
#!/usr/sbin/dtrace -s
/* 上の行は、下のスクリプトを dtrace で解釈する必要がある
ことを示す。D は C スタイルのコマンドを使用する。*/
pid1234:::entry
{}
このコマンドまたはスクリプトには、1234 の代わりに、関心のあるプロセスの pid を指定できます。
pid プロバイダを使用した D スクリプトの構築手順は次の通りです。
#!/usr/sbin/dtrace -s
pid$1:::entry
{}
@name[table index(es)] =aggregate_function()例:
@count_table[probefunc] = count() ; この例では、関数の名前をもつテーブルに情報を蓄積します (probefunc は関数の名前をもつ組み込み変数です)。集約関数 count() は、関数が呼び出された回数を保持します。そのほかの一般的な集約関数としては average、min、max、sum があります。
#!/usr/sbin/dtrace -s
pid$1:::entry
{
@count_table[probefunc]=count();
}
このスクリプトは、テーブルに情報を蓄積し、^c (Control c) が押されるまで実行を続けます。^c が押されると、情報テーブルを出力します。テーブルの出力には、特別なプログラムは必要ありません。DTrace が自動的に行います。
スクリプトを実行すると、プローブが自動的に作成されることに気づいたと思います。プローブは、スクリプトの停止と同時に無効になります。プローブを無効にするために特別なことをする必要はありません。さらに、DTrace は、自身が割り当てたメモリを自動的に消去します。したがって、消去のための特別なコードを作成する必要もありません。
#!/usr/sbin/dtrace -s
pid$1:::entry
{
@count_table[probemod,probefunc]=count();
}
#!/usr/sbin/dtrace -s
pid$1:::entry
{
ts[probefunc] = timestamp;
}
pid$1:::return
{
@func_time[probefunc] = sum(timestamp - ts[probefunc]);
ts[probefunc] = 0;
}
ts[] は配列です。この配列は自動的に宣言され、初期化されます。一般には、メモリを節約するために変数に 0 を設定すべきです。
#!/usr/sbin/dtrace -s
pid$1:::entry
{
self->ts[probefunc] = timestamp;
}
pid$1:::return
/self->ts[probefunc]/
{
@func_time[probefunc] = sum(timestamp - self->ts[probefunc]);
self->ts[probefunc] = 0;
}
注: /self->ts[probefunc]/ は /self->ts[probefunc] != 0/ と同じです。
非常に大きなアプリケーションの場合は、上のスクリプトによって多数の (場合によっては何万という) プローブが有効になります。個々のプローブの負荷はかなり軽いとはいえ、このような多数のプローブを稼動中のシステムに追加すると、アプリケーションの性能に影響があります。
|
表 5 — プローブ記述の変更
#!/usr/sbin/dtrace -s
pid$target:libc::entry
{
@[probefunc]=count();
}
# libc_func.d -c "cat /etc/hosts" ここに示すいくつかのサンプル・スクリプトではシステムの追加情報を取得します。システム・コールの回数やそのスタックの深さ、誰が何を行なっているかがわかれば、作業負荷の状態を理解し、起こりえるボトルネックを見つけ出すことができます。
#!/usr/sbin/dtrace -s
syscall::write:entry
{
@[ustack()]=count();
}
#!/usr/sbin/dtrace -s
sysinfo:::pswitch
{
@[execname] = count();
}
#!/usr/sbin/dtrace -qs
proc:::exec-success
{
printf("%s(pid=%d) started by uid - %d\n",execname, pid, uid);
}
システム管理者が避けて通れない作業の中に、所定の環境で稼動するアプリケーションの正常または異常な動作に関するものがあります。このような情報は、syscall、proc、io、sched、sysinfo、vminfo、lockstat、profile といったプロバイダを通して取得できます。これらのうち syscall、proc、io、sched は、初めての管理者でも容易に使用できるプロバイダです。システム管理者は、これらのプロバイダを通して、プロセスやスレッド、スタックのステータスなど、カーネルのさまざまな基本要素に関する情報を取得できます。まずは、syscall、proc、io、sched プロバイダから使用してみてください。
おそらく、最初に学習し使用すべき最も重要なプロバイダはこのプロバイダでしょう。なぜならシステム・コールは、ユーザ・レベルのアプリケーションとカーネルを結ぶ主要な通信チャネルだからです。どのシステム・コールが使用されているかを知ることは、とりわけ、システムの使用状況を把握し、異常動作の兆候を見つける上で重要です。syscall プロバイダを使用すれば、誰が何を実行し、特定の操作がどれだけの時間を要しているのかを容易に知ることができます。これは、システムの異常動作の根本原因を見つけるのに役立ちます。
# dtrace -n syscall::close:entry
#!/usr/sbin/dtrace -s
syscall::kill:entry
{
trace(pid);
trace(execname);
}
#!/usr/sbin/dtrace -qs
BEGIN
{
printf("size\ttime\n");
}
syscall::read:entry
/execname == "httpd"/
{
self->start = timestamp;
}
syscall::read:return
/self->start/
{
printf("%d\t%d\n", arg0, timestamp - self->start);
self->start = 0;
}
このプロバイダは、プロセスや、スレッドの作成および終了のほか、シグナルに対しても起動されます。これは、単純な kill プローブへのアプローチとしてはより精巧なものです。あるシグナル (3head) をどのユーザがどのプロセスに送信したかがわかります。
#!/usr/sbin/dtrace -wqs
proc:::signal-send
{
printf("%d was sent to %s by ", args[2], args[1]->pr_fname);
system("getent passwd %d | cut -d: -f5", uid);
}
#!/usr/sbin/dtrace -wqs
proc:::signal-send
/args[2] == SIGKILL/
{
printf("SIGKILL was sent to %s by ", args[1]->pr_fname);
system("getent passwd %d | cut -d: -f5", uid);
}
これらの例には pr_fname が導入されているのがわかります。pr_fname は、受信側プロセスの psinfo_t の構造の一部です。詳細については、『Solaris 動的トレースガイド』をご覧ください。
このプロバイダは、スケジュールのイベントを動的にトレースします。このプロバイダを使えば、スレッドがスリープしたり、実行されたり、優先順位を変更したり、ほかのスレッドを呼び出したりした時期や理由を知ることができます。
#!/usr/bin/dtrace -sq
sched:::on-cpu
/execname == "soffice.bin"/
{
self->on = vtimestamp;
}
sched:::off-cpu
/self->on/
{
@time[""] = sum(vtimestamp self->on);
self->on = 0;
}
このプロバイダは、ディスク入出力 (I/O) サブシステムの情報を収集します。io プロバイダを使用すれば、iostat(1M) 出力をより正確に解釈できます。
io プローブは、NFS サービスなどすべてのディスク要求に対して起動されます。ただし、メタデータ要求の場合を除きます。
#!/usr/bin/dtrace -qs
BEGIN
{
printf("%10s %58s %2s\n", "DEVICE", "FILE", "RW");
}
io:::start
{
printf("%10s %58s %2s\n", args[1]->dev_statname, args[2]->fi_pathname,
  args[0]->b_flags & B_READ ? "R" : "W");
}
DTrace ディスカッション・グループに加わり、Solaris のこの革新的な新機能に関する継続的な議論やその開発に参加してください。
| ||||||||||||||||||||||||||||||||||||||||||||