Skip to Content Java Solaris コミュニティ パートナー My Sun ご購入について Japan Worldwide

DTrace
Solaris 10 システムから DTrace を使用する

本ガイドは、DTrace を使って Solaris 10 システムのシステムやアプリケーションの情報を収集し使用する方法を、新しいユーザ向けに説明することを目的としています。本ガイドでは、D スクリプトの構築方法について説明するほか、すべてのシステム管理者や開発者が、DTrace の学習や使用時に知っておくべき各種のプロバイダを紹介します。さらに、本ガイドには、DTrace の使用を始めるにあたって役に立ついくつかの例も含まれています。

このガイドを読み終えると、ユーザは、スクリプトを作成して稼動中のアプリケーションの有益な情報を収集できるようになります。この情報は、Solaris での性能の向上に役立ちます。

D 言語や DTrace の詳細については、『Solaris 動的トレースガイド』(http://docs.sun.com/app/docs/doc/819-0395) をご覧ください。

317KPDF[317K]
目次
 
 
 
 

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/) をご覧ください。

DTrace アーキテクチャとコンポーネントの概観

ページ先頭へ

 
 
 

D スクリプトの概要

最初に 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 つのフィールドからなります。

  • プロバイダ—使用する計測方法を指定します。たとえば、syscall プロバイダはシステム・コールの監視に、io プロバイダはディスク入出力の監視に使用されます。
  • モジュールおよび関数—監視するモジュールや関数を指定します。
  • 名前—通常は、関数内の場所を表します。たとえば、関数に入ったときに計測する entry を指定します。

さらに、* や ? などのワイルド・カードも使用できます。ブランク・フィールドはワイルド・カードとみなされます。表 1 はプローブ記述の例です。

プローブ記述 説明
syscall::open:entry
open システム・コールへの入口
syscall::open*:entry
open で始まる任意のシステム・コール (open や open64) への入口
syscall:::entry
呼び出された任意のシステムへの入口
syscall:::
システム・コール・プロバイダによって発行されたすべてのプローブ

表 1 — プローブ記述の例


述語

述語は、任意の D 式として表すことができます。表 2 は述語のいくつかの例です。アクションは、述語が真と判定されたときだけ実行されます。

述語 説明
cpu == 0
プローブが cpu0 に対して実行された場合、真です。
pid == 1029
プローブを起動する原因となったプロセスの pid が 1029 の場合、真です。
execname != "sched"
プローブがスケジューラ (sched) でない場合、真です。
ppid !=0 && arg0 == 0
親プロセスの id が 0 ではなく、最初の引数が 0 の場合、真です。

表 2 — 述語の例


アクション

アクション・セクションには、一連のアクション・コマンドをセミコロン (;) で区切って指定します。表 3 はアクションの例です。

アクション 説明
printf()
C スタイルの printf() コマンドを使って出力します。
ustack()
ユーザ・レベルのスタックを出力します。
trace
指定された変数を出力します。

表 3 — アクションの例

述語とアクション・ステートメントの指定は任意です。述語を指定しないと、アクションが常に実行されます。アクションを指定しないと、起動されたプローブの名前が出力されます。

この最小限の情報でも、有益な D スクリプトを作成できます。以下のセクションでは、アプリケーション開発者の観点から見た DTrace の使い方とシステム管理者の観点から見た DTrace の使い方をそれぞれ説明します。

ページ先頭へ

 
 
 

開発者にとっての DTrace

アプリケーション開発者にとって特に関心のある情報は、syscall、proc、pid、sdt、vminfo といったプロバイダを使用して取得できます。これらのプロバイダを使用すれば、開発者は動作中のプロセスのほかにも、プロセスの作成や終了、LWP の作成や終了、シグナル処理などを監視できます。このセクションでは、pid プロバイダについてのみ説明します。


pid プロバイダ

pid プロバイダは、動作中のプロセスにある任意のユーザ・レベル関数の入口と出口を計測します。pid プロバイダでは、システム上の任意のプロセスの任意の命令をトレースできます。pid プロバイダの名前には、関心のある動作プロセスの pid を指定します。図 4 は、pid プロバイダを使用したプローブ記述の例です。

説明
pid2439:libc:malloc:entry
プロセス id 2439 の libc の malloc 関数への入口
pid1234:a.out:main:return
プロセス id 1234 の main からの出口
pid1234:a.out::entry
main から実行可能な、1234 の任意の関数への入口
pid1234:::entry
プロセス id 1234 の任意のライブラリの任意の関数への入口

表 4 — pid プロバイダとそれに対応するプローブの例

プロセス id 1234 が呼び出すすべての関数を出力するコマンドは、次のようになります。

# dtrace -n pid1234:::entry

この同じコマンドをスクリプトで使用する場合は、次のようになります。

#!/usr/sbin/dtrace -s
/* 上の行は、下のスクリプトを dtrace で解釈する必要がある
ことを示す。D は C スタイルのコマンドを使用する。*/
pid1234:::entry
{}


このコマンドまたはスクリプトには、1234 の代わりに、関心のあるプロセスの pid を指定できます。

pid プロバイダを使用した D スクリプトの構築手順は次の通りです。

  1. 上のコマンドまたはスクリプトを実行するとわかるように、これは汎用性に欠けます。
  2. プロセス id をパラメータとして受け取るようにスクリプトを変更します。スクリプトは次のようになります。

    #!/usr/sbin/dtrace -s
    pid$1:::entry
    {}


  3. これでプロセス id をスクリプトの引数として指定できます。このスクリプトの唯一の引数であるプロセス id を指定しないと、スクリプトからエラーメッセージが出され、実行されません。スクリプトには、実行権を忘れずに指定してください。

    このスクリプトを実行しても、メッセージが次々に出力されるため読み取れない場合があります。D 言語には、すべての情報をメモリに蓄積し、要約を出力する「aggregate (集約)」という優れた機能があります。集約機能を使えば、さまざまな情報テーブルをメモリに蓄積できます。集約の構文は次のとおりです。

    @name[table index(es)] =aggregate_function()

    例:

    @count_table[probefunc] = count() ;

    この例では、関数の名前をもつテーブルに情報を蓄積します (probefunc は関数の名前をもつ組み込み変数です)。集約関数 count() は、関数が呼び出された回数を保持します。そのほかの一般的な集約関数としては average、min、max、sum があります。

  4. 下の例では、作成中のスクリプトに集約機能を追加して、ユーザ関数の要約テーブルを作成します。

    #!/usr/sbin/dtrace -s
    pid$1:::entry
    {
       @count_table[probefunc]=count();
    }

    このスクリプトは、テーブルに情報を蓄積し、^c (Control c) が押されるまで実行を続けます。^c が押されると、情報テーブルを出力します。テーブルの出力には、特別なプログラムは必要ありません。DTrace が自動的に行います。

    スクリプトを実行すると、プローブが自動的に作成されることに気づいたと思います。プローブは、スクリプトの停止と同時に無効になります。プローブを無効にするために特別なことをする必要はありません。さらに、DTrace は、自身が割り当てたメモリを自動的に消去します。したがって、消去のための特別なコードを作成する必要もありません。

  5. インデックスを probemod, probefunc に変更するだけで、関数とライブラリ名のテーブルを簡単に指定できます。

    #!/usr/sbin/dtrace -s
    pid$1:::entry
    {
       @count_table[probemod,probefunc]=count();
    }


  6. 次に、組み込み変数 timestamp を使って各関数が費やした時間を調べます。それには、関数の入口と出口にプローブを作成し、その間の timestamp の差を計算します。timestamp 変数の時間単位はナノ秒です。変更後のスクリプトは次のようになります。

    #!/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 を設定すべきです。

  7. このスクリプトはほとんどの場合正常に動作しますが、特殊な例外がいくつかります。

    例外 1—関数の入口と出口を監視する場合
    稼動中のアプリケーションにプローブを作成するため、D スクリプトを実行したときには、何らかの関数が稼動中である可能性が高いといえます。そのため、関数の入口を認識していないのに出口だけを認識することがあります。この問題は、述語 /ts[probefunc] != 0/ を出口のプローブ・セクションに追加することで簡単に解決できます。この述語は上の問題を無視します。

    例外 2—マルチスレッド・アプリケーションの場合
    次の問題はマルチスレッド・アプリケーションに関係するもので、もう少し込み入っています。2 つのスレッドが同じ関数を同時に実行すると、競合が発生する可能性があります。この場合には、スレッドごとに ts[] のコピーが必要です。DTrace は self 変数を使ってこの問題を解決します。self 変数に追加するものはすべてスレッド内でのみ有効です。
  8. 上の 2 つの特殊な問題に対処するために修正したスクリプトは次の通りです。

    #!/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/ と同じです。

    非常に大きなアプリケーションの場合は、上のスクリプトによって多数の (場合によっては何万という) プローブが有効になります。個々のプローブの負荷はかなり軽いとはいえ、このような多数のプローブを稼動中のシステムに追加すると、アプリケーションの性能に影響があります。

  9. プローブ記述を変更すれば、有効となるプローブの数を制限できます。図 5 はその例です。

    プローブ記述 説明
    pid$1:libc::entry
    指定するライブラリに限定します。
    pid$1:a.out::entry
    プローブを非ライブラリ関数に限定します。
    pid$1:libc:printf:entry
    プローブを 1 つの関数に限定します。

    表 5 — プローブ記述の変更

  10. 次に、プロセスをその開始から終了まで監視する方法について説明します。それには、DTrace の $target 変数と -c オプションを使用します。このスクリプトでは、libc 関数が特定のアプリケーションから呼び出される回数をカウントします。

    #!/usr/sbin/dtrace -s
    pid$target:libc::entry
    {
       @[probefunc]=count();
    }


  11. このスクリプトを libc_func.d として保存し、実行します。

    # libc_func.d -c "cat /etc/hosts"

  12. 「cat /etc/hosts」を必要なコマンドで置き換えてください。

そのほかの便利なスクリプト

ここに示すいくつかのサンプル・スクリプトではシステムの追加情報を取得します。システム・コールの回数やそのスタックの深さ、誰が何を行なっているかがわかれば、作業負荷の状態を理解し、起こりえるボトルネックを見つけ出すことができます。

  1. 次のスクリプトは、プログラムが write システム・コールを行うときにスタック・トレースを見つけます。このスクリプトは -c オプションで実行してください。

    #!/usr/sbin/dtrace -s
    syscall::write:entry
    {
       @[ustack()]=count();
    }


  2. 次のスクリプトは、さまざまなプロセスが特定の CPU で実行される回数をカウントします。sysinfo:::pswitch プローブは、プロセスがこの CPU に切り替えられ実行されると起動されます。数分が経過したら必ず ^c (Control c) を押してください。

    #!/usr/sbin/dtrace -s
    sysinfo:::pswitch
    {
       @[execname] = count();
    }


  3. 次のスクリプトは、新しいプロセスがシステムで起動されると、そのプロセス名、pid、uid を出力します。proc:::exec-success は、新しいプロセスが正常に起動されると起動されます。

    #!/usr/sbin/dtrace -qs
    proc:::exec-success
    {
       printf("%s(pid=%d) started by uid - %d\n",execname, pid, uid);
    }

ページ先頭へ

 
 
 

システム管理者にとっての DTrace

システム管理者が避けて通れない作業の中に、所定の環境で稼動するアプリケーションの正常または異常な動作に関するものがあります。このような情報は、syscall、proc、io、sched、sysinfo、vminfo、lockstat、profile といったプロバイダを通して取得できます。これらのうち syscall、proc、io、sched は、初めての管理者でも容易に使用できるプロバイダです。システム管理者は、これらのプロバイダを通して、プロセスやスレッド、スタックのステータスなど、カーネルのさまざまな基本要素に関する情報を取得できます。まずは、syscall、proc、io、sched プロバイダから使用してみてください。


syscall プロバイダ

おそらく、最初に学習し使用すべき最も重要なプロバイダはこのプロバイダでしょう。なぜならシステム・コールは、ユーザ・レベルのアプリケーションとカーネルを結ぶ主要な通信チャネルだからです。どのシステム・コールが使用されているかを知ることは、とりわけ、システムの使用状況を把握し、異常動作の兆候を見つける上で重要です。syscall プロバイダを使用すれば、誰が何を実行し、特定の操作がどれだけの時間を要しているのかを容易に知ることができます。これは、システムの異常動作の根本原因を見つけるのに役立ちます。

  1. 次のスクリプトでは、プローブが起動された際にそのプローブのすべてのオカレンス (発生) をリストするとともに、close(2) システム・コールを実行しているシステム・コールの情報をシステムへの入口で取得します。

    # dtrace -n syscall::close:entry

  2. このスクリプトでは、kill(2) シグナルを特定のプロセスに送信したプロセスを識別できるようにします。

    #!/usr/sbin/dtrace -s
    syscall::kill:entry
    {
       trace(pid);
       trace(execname);
    }


  3. 次のスクリプトでは、Web サーバが read(2) にどれだけの時間を費やしているのかを明らかにします。このスクリプトをほかのプロセス用に変更するのは簡単です。

    #!/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;
    }

proc プロバイダ

このプロバイダは、プロセスや、スレッドの作成および終了のほか、シグナルに対しても起動されます。これは、単純な kill プローブへのアプローチとしてはより精巧なものです。あるシグナル (3head) をどのユーザがどのプロセスに送信したかがわかります。

  1. このシステムで動作しているプロセスに送信されたすべてのシグナルをトレースします。

    #!/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);
    }

  2. 条件ステートメント (/args[2] == SIGKILL/) をスクリプトに追加し、SIGKILL シグナルをさまざまなユーザからさまざまなプロセスに送信します。

    #!/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 動的トレースガイド』をご覧ください。


sched プロバイダ

このプロバイダは、スケジュールのイベントを動的にトレースします。このプロバイダを使えば、スレッドがスリープしたり、実行されたり、優先順位を変更したり、ほかのスレッドを呼び出したりした時期や理由を知ることができます。

  1. 次のスクリプトでは、CPU が I/O 待ち時間や I/O 動作に費やした時間を調べます。さらに、I/O プロセスを停止し、I/O 待ち時間の間に StarSuite によってデータが読み取られたことを示します。このスクリプトは、ユーザの状況に応じて簡単に変更できます。

    #!/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;
    }

io プロバイダ

このプロバイダは、ディスク入出力 (I/O) サブシステムの情報を収集します。io プロバイダを使用すれば、iostat(1M) 出力をより正確に解釈できます。

io プローブは、NFS サービスなどすべてのディスク要求に対して起動されます。ただし、メタデータ要求の場合を除きます。

  1. 次の例は /usr/demo/dtrace/iosnoop.d スクリプトをベースにしています。この例は、どのデバイスのどのファイルがアクセスされているかをトレースしたり、実行されているタスクが読み取りか書き込みかを知りたいときに使用します。

    #!/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 の簡単な紹介にすぎません。

DTrace の詳細については次の Web サイトをご覧ください。

DTrace ディスカッション・グループに加わり、Solaris のこの革新的な新機能に関する継続的な議論やその開発に参加してください。

ページ先頭へ

 
 
お問い合わせ 会社情報 ニュース 採用情報 プライバシー 利用規定 商標 Copyright  Sun Microsystems, Inc.