11 DTraceプロバイダ

この章では、既存のDTraceプロバイダの一部について説明します。この章で説明するプロバイダのリストではすべてが網羅されているわけではありません。システムで使用可能なプロバイダを表示するには、dtrace -lコマンドを使用します。重要なデータ構造体用のトランスレータの詳細は、/usr/lib64/dtrace/version/*.dのファイルを参照してください。

dtraceプロバイダ

dtraceプロバイダには、DTrace自体に関連するプローブが含まれています。これらのプローブを使用して、トレースの開始前に状態を初期化し、トレースの完了後にその状態を処理して、他のプローブでの予期しない実行エラーを処理できます。

BEGINプローブ

BEGINプローブは、他のどのプローブより先に起動します。すべてのBEGIN節が完了するまで、他のプローブは起動しません。このプローブを使用すると、他のプローブで必要なすべての状態を初期化できます。次の例は、BEGINプローブを使用して、mmapの保護ビットとテキスト表現の間をマップする連想配列の初期化を示しています。

BEGIN
{
  prot[0] = "---";
  prot[1] = "r--";
  prot[2] = "-w-";
  prot[3] = "rw-";
  prot[4] = "--x";
  prot[5] = "r-x";
  prot[6] = "-wx";
  prot[7] = "rwx";
}

syscall::mmap:entry
{
  printf("mmap with prot = %s", prot[arg2 & 0x7]);
}

BEGINプローブは、未指定のコンテキストで起動します。つまり、stackustackの出力と、コンテキスト固有の変数(execnameなど)の値はすべて任意です。これらの値に依存したり、これらの値を有意な情報として解釈しないようにしてください。BEGINプローブに、引数は定義されていません。

ENDプローブ

ENDプローブは、他のどのプローブより後に起動します。このプローブは、他のプローブ節がすべて完了するまで起動しません。このプローブを使用すると、収集された状態の処理や、出力のフォーマットが可能です。そのため、ENDプローブではprintaアクションが多用されます。次のように、BEGINプローブとENDプローブを併用すると、トレースにかかった合計時間を測定できます。

BEGIN
{
  start = timestamp;
}

/*
 * ... other tracing actions...
 */

END
{
  printf("total time: %d secs", (timestamp - start) / 1000000000);
}

ENDプローブのその他の使用方法は、「データの正規化」および「printaアクション」を参照してください。

BEGINプローブと同じく、ENDプローブにも引数は定義されていません。ENDプローブが起動するコンテキストは任意であるため、これに依存するプログラムは作成しないでください。

bufpolicyオプションをfillに設定してトレースを行うと、ENDプローブでトレースされるレコードに対応できるように適切な空間が予約されます。詳細は、「fillポリシーおよびENDプローブ」を参照してください。

ノート:

exitアクションは、トレースを停止してENDプローブを起動させます。ただし、exitアクションが呼び出されてからENDプローブが起動するまでには多少の遅延が発生します。この遅延の間は、どのプローブも起動しません。プローブがexitアクションを呼び出した後は、exitがコールされたことをDTraceコンシューマが判断してトレースを停止するまで、ENDプローブは起動しません。exitのステータスをチェックする間隔は、statusrateオプションで設定できます。詳細は、「オプションおよびチューニング可能パラメータ」を参照してください。

ERRORプローブ

ERRORプローブは、DTraceプローブの節を実行中にランタイム・エラーが発生した場合に起動します。次の例のように、NULLポインタを間接参照する節がある場合、ERRORプローブが起動します。error.dという名前のファイルに保存します。

BEGIN
{
  *(char *)NULL;
}

ERROR
{
  printf("Hit an error!");
}

このプログラムを実行すると、次のような出力が表示されます。

# dtrace -s error.d 
dtrace: script 'error.d' matched 2 probes
CPU     ID                    FUNCTION:NAME
  1      3                           :ERROR Hit an error!
dtrace: error on enabled probe ID 1 (ID 1: dtrace:::BEGIN):
invalid address (0x0) in action #1 at DIF offset 16
^C

前述の出力では、ERRORプローブが起動したことと、dtraceがエラーをレポートしたことが示されます。dtraceは独自の方法でERRORプローブを有効にし、エラーのレポートを可能にします。ERRORプローブを使用すると、独自のエラー処理を作成できます。

次の表では、ERRORプローブの引数について説明します。

引数 説明

arg1

エラーの原因となったプローブで有効化されたプローブID (EPID)

arg2

フォルトの原因となったアクションのインデックス

arg3

そのアクションへのDIFオフセット(該当しない場合は-1)

arg4

フォルト・タイプ。

arg5

フォルト・タイプに固有の値。

次の表に、arg4で指定できる様々なフォルト・タイプと、各フォルト・タイプに対してarg5がとる値を示します。

arg4の値 説明 arg5の意味

DTRACEFLT_UNKNOWN

不明なフォルト・タイプ

なし

DTRACEFLT_BADADDR

マップされていないアドレスまたは無効なアドレスへのアクセス

アクセスされるアドレス

DTRACEFLT_BADALIGN

境界違反のメモリー・アクセス

アクセスされるアドレス

DTRACEFLT_ILLOP

不正または無効な操作

なし

DTRACEFLT_DIVZERO

整数のゼロ除算

なし

DTRACEFLT_NOSCRATCH

スクラッチ割当てに対応できるスクラッチ・メモリーの不足

なし

DTRACEFLT_KPRIV

カーネル・アドレスまたはプロパティのアクセスが試行されたが、十分な権限がない

アクセスされるアドレス(該当しない場合は0)

DTRACEFLT_UPRIV

ユーザー・アドレスまたはプロパティのアクセスが試行されたが、十分な権限がない

アクセスされるアドレス(該当しない場合は0)

DTRACEFLT_TUPOFLOW

DTrace内部パラメータのスタック・オーバーフロー

なし

DTRACEFLT_BADSTACK

ユーザー・プロセス・スタックが無効

無効なスタック・ポインタのアドレス

ERRORプローブで実行されたアクションがエラーの原因になっている場合、そのエラーはメッセージなしに削除されます。ERRORプローブは、再帰的には呼び出されません。

dtraceの安定性

dtraceプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

安定

安定

共通

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

安定

安定

共通

引数

安定

安定

共通

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。

profileプロバイダ

profileプロバイダでは、指定の時間間隔で定期的に起動する割込みに関連するプローブを使用できます。このようなプローブは、特定の実行ポイントではなく非同期割込みイベントに関連付けられます。これらのプローブを使用して、特定の観点からシステム状態をサンプリングし、そのサンプルを使用してシステム動作を推測できます。サンプリング・レートが高い、またはサンプリング時間が長いと、正確な推測が可能になります。DTraceアクションを使用すると、profileプロバイダでシステムのあらゆる側面をサンプリングできます。たとえば、現在のスレッドの状態、CPUの状態、現在のマシン命令などの標本をサンプリングできます。

profile-nプローブ

profile-nプローブは、すべてのアクティブなCPUで、割込みレベルが高く、固定間隔で起動します。nのデフォルトの単位は秒当たりの起動レートとして表される周波数ですが、表11-1に示すように、時間間隔または周波数のいずれかを指定するオプションの接尾辞を値に付けることもできます。次の表では、tick- nプローブで有効な時間の接尾辞について説明します。

表11-1 有効な時間接尾辞

接尾辞 時間の単位

nsec or ns

ナノ秒

usec or us

マイクロ秒

msec or ms

ミリ秒

sec or s

min or m

hour or h

時間

day or d

hz

ヘルツ(秒当たりのレートとして表される周波数)

tick-nプローブ

tick-nプローブは一定の間隔で、高い割込みレベルで間隔ごとに1つのCPUのみで起動します。すべてのCPUで起動するprofile-nプローブとは異なり、tick-nプローブは間隔ごとに1つのCPUのみで起動し、起動するCPUは時間の経過とともに変化する可能性があります。nのデフォルトの単位は秒当たりの起動レートとして表される周波数ですが、表11-1に示すように、時間間隔または周波数のいずれかを指定するオプションの時間接尾辞を値に付けることもできます。

tick-nプローブには、定期的な出力や定期的なアクションの実行など、いくつかの用途があります。

ノート:

デフォルトでは、サポートされている最も高い周波数は5000 Hzです(tick-5000)。

profileプローブの引数

次の表では、profileプローブの引数について説明します。

表11-2 profileプローブの引数

プローブ arg0 arg1 arg2

profile-n

pc

upc

nsecs

tick-n

pc

upc

引数は次のとおりです。

  • pc: カーネル・プログラム・カウンタ

  • upc: ユーザー空間プログラム・カウンタ

  • nsecs: ナノ秒の経過時間

profileプローブの作成

他のプロバイダとは異なり、profileプロバイダは、必要に応じて動的にプローブを作成します。そのため、(たとえばdtrace -l -P profileコマンドの使用時に)すべてのプローブをリストしたとき、必要なプローブが表示されない場合がありますが、このプローブは明示的に有効にすると作成されます。

時間間隔が短すぎると、マシンは時間ベースの割込みを継続的に処理することになり、マシン上のサービスが拒否されます。profileプロバイダは、間隔が200ミリ秒未満になるプローブの作成をメッセージなしに拒否します。

profの安定性

profileプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの安定性の値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

共通

モジュール

変更の可能性あり

変更の可能性あり

不明

関数

非公開

非公開

不明

名前

発展中

発展中

共通

引数

発展中

発展中

共通

詳細は、「DTraceの安定性機能」を参照してください。

fbtプロバイダ

fbt (関数境界トレース)プロバイダには、Oracle Linuxカーネル内のほとんどの関数の開始および終了に関連するプローブが含まれています。そのため、数万のfbtプローブが存在する可能性が高いです。

fbtプロバイダがプロセッサのアーキテクチャで使用可能であることを確認するには、fbtインストゥルメンテーションを提供するモジュールをロードし、いくつかのプローブを正常にリストできる必要があります。このようなプローブの数が多いため、このプロセスには数秒かかることがあります。たとえば、rootとして実行される次のコマンドについて考えてみます。

# dtrace -l -P fbt | wc -l
dtrace: failed to match fbt:::: No probe matches description
1
# modprobe fbt
# dtrace -l -P fbt | wc -l
88958

前述の例では、最初のdtraceコマンドは、/etc/dtrace-modulesにリストされているモジュールを自動的にロードしますが、fbtがその中にはなかったことも確認します。fbtを手動でロードすると、多くのfbtプローブが表示されます。詳細は、「モジュールのロードとfbt」を参照してください。

他のDTraceプロバイダと同様に、関数境界トレース(FBT)は、明示的に有効にしていないと、プローブ効果はありません。有効にすると、FBTはプローブされた関数でのみプローブ効果をもたらします。FBT実装は命令セット・アーキテクチャにきわめて特異的なものですが、FBTはx86と64ビットの両方のARMプラットフォームに実装されています。各命令セットには、他の関数をコールせず、コンパイラによって高度に最適化される少数のleaf関数があり、これはFBTではインストゥルメントできません。これらの関数のプローブは、DTraceに存在しません。

FBTプローブを効果的に使用するには、オペレーティング・システム実装に関する知識が必要です。したがって、FBTは、カーネル・ソフトウェアの開発時、またはその他のプロバイダが不十分な場合にのみ使用することをお薦めします。syscallschedprocioなどの他のDTraceプロバイダを使用すると、オペレーティング・システムの実装知識を必要とせずに、ほとんどのシステム分析の問題の回答を導き出すことができます。

fbtプローブ

FBTは、それぞれentryおよびreturnという名前の、カーネル内のほとんどの関数の開始時と終了時のプローブを提供します。すべてのFBTプローブには、関数名とモジュール名があります。

fbtプローブの引数

entryプローブの引数は、対応するオペレーティング・システムのカーネル関数の引数と同じです。これらの引数には、arg0arg1arg2...の変数を使用して、int64_t値としてアクセスできます。

関数に戻り値がある場合、戻り値はreturnプローブのarg1に格納されます。関数に戻り値がない場合、arg1は定義されません。

特定の関数の開始ポイントは1つのみですが、コール元に戻るポイントは多数ある場合があります。FBTは、関数の複数のリターン・サイトを単一のreturnプローブに収集します。正確なリターン・パスを知る必要がある場合は、関数テキスト内の戻り命令のバイト単位のオフセットを示す、returnプローブのarg0値を調べることができます。

fbtの例

カーネルの実装を調べるために、簡単にfbtプロバイダを使用できます。次のスクリプト例では、clockプロセスからの最初のgettimeofdayコールを記録し、カーネルを介した後続のコード・パスに従います。次のDソース・コードを入力し、xgettimeofday.dという名前のファイルに保存します。

/*
 * To make the output more readable, indent every function entry
 * and unindent every function return.  This is done by setting the
 * "flowindent" option.
 */
#pragma D option flowindent

syscall::gettimeofday:entry
/execname == "clock" && guard++ == 0/
{
        self->traceme = 1;
        printf("start");
}

fbt:::
/self->traceme/
{}

syscall::gettimeofday:return
/self->traceme/
{
        self->traceme = 0;
        exit(0);
}

このスクリプトを実行すると、次のような出力が得られます。

# dtrace -s ./xgettimeofday.d
dtrace: script './xgettimeofday.d' matched 92115 probes
CPU FUNCTION
  0  => gettimeofday                          start
  0    -> SyS_gettimeofday
  0      -> getnstimeofday64
  0        -> __getnstimeofday64
  0        <- __getnstimeofday64
  0      <- getnstimeofday64
  0      -> _copy_to_user
  0      <- _copy_to_user
  0    <- SyS_gettimeofday
  0  <= gettimeofday

前述の出力は、gettimeofdayシステム・コールの実行時にコールされる内部カーネル関数を示しています。

モジュールのロードとfbt

Oracle Linuxカーネルはカーネル・モジュールを動的にロードおよびアンロードできますが、fbtプローブの場合は、インストゥルメンテーションをサポートするためにfbtカーネル・モジュールをロードする必要があります。カーネル・モジュールのロードの詳細は、「DTraceスタート・ガイド」のノートを参照してください。fbt/etc/dtrace-modulesにリストされていない場合、またはdtrace -lコマンドでfbtプローブがリストされない場合は、次のコマンドを使用します。

# modprobe fbt

反対に、次のコマンドを使用してfbtインストゥルメンテーションをアンロードできます。

# modprobe -r fbt

fbtモジュールがロードされると、FBTは、動的にロードされる新しいモジュールを含め、ロードされた他のすべてのモジュールをインストゥルメントするプローブを自動的に提供します。ロードされるモジュールに有効なFBTプローブがない場合、モジュールがアンロードされる場合があり、モジュールがアンロードされると、対応するプローブが破棄されます。ロードされるモジュールに有効なFBTプローブがある場合、モジュールはビジーとみなされ、アンロードできません。

fbtの安定性

fbtプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの安定性の値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

共通

モジュール

非公開

非公開

不明

関数

非公開

非公開

ISA

名前

発展中

発展中

共通

引数

非公開

非公開

ISA

詳細は、「DTraceの安定性機能」を参照してください。

syscallプロバイダ

syscallプロバイダは、システムのすべてのシステム・コールに対して開始時と終了時のプローブを使用可能にします。システム・コールは、ユーザー・レベルのアプリケーションとオペレーティング・システム・カーネルの間の主要なインタフェースのため、syscallプロバイダからは、システムに関連するアプリケーション動作について多くの洞察を得ることができます。

syscallプローブ

syscallプロバイダは、システム・コールごとに1組のプローブを提供します。1つはシステム・コールに入る前に起動するentryプローブ、もう1つはシステム・コールが完了してから制御がユーザー・レベルに戻るまでの間に起動するreturnプローブです。どのsyscallプローブでも、関数名はインストゥルメントされるシステム・コールの名前として設定されます。

多くの場合、syscallが提供するシステム・コールの名前は、マニュアル・ページの項2の名前と対応しています。ただし、syscallプロバイダのプローブの中には、文書化されたシステム・コールに直接対応していないものもあります。この矛盾のいくつかの主な理由について、次の項で説明します。

古いシステム・コール

syscallプロバイダで提供されるシステム・コールの名前には、古い実装の詳細が反映されている場合があります。

サブコード化されたシステム・コール

システム・コールの中には、他のシステム・コールの下位の操作として実装されるものもあります。たとえば、socketcall(),はソケット・システム・コールの共通カーネル・エントリ・ポイントです。

新しいシステム・コール

Oracle Linuxでは、たとえば次のようにat接頭辞の付いたシステム・インタフェースが個別のシステム・コールとして実装されています。

  • faccessat()

  • fchmodat()

  • fchownat()

  • fstatat64()

  • futimensat()

  • linkat()

  • mkdirat()

  • mknodat()

  • name_to_handle_at()

  • newfstatat()

  • open_by_handle_at()

  • openat()

  • readlinkat()

  • renameat()

  • symlinkat()

  • unlinkat()

  • utimensat()

これらのシステム・コールは、at接尾辞の付いていない古い形の機能のスーパーセットです。これらは、追加の第1引数としてオープン・ディレクトリのファイル記述子をとります。この場合、相対パス名に対する操作が特定のディレクトリと相対的に実行されます。または、予約値であるAT_FDCWDをとり、操作が現在の作業ディレクトリと相対的に実行されます。

置き換えられたシステム・コール

Oracle Linuxでは、次の古いシステム・コールが置き換えられ、新しいglibcインタフェースではコールされなくなります。旧バージョンのインタフェースは残りますが、本来のシステム・コール自体としてではなく、新しいシステム・コールに対するコールとして再実装されています。次の表に、旧バージョンのコールと、それに相当する新しいコールをリストします。

旧バージョンのシステム・コール 新しいシステム・コール

access(p, m)

faccessat(AT_FDCWD, p, m, 0)

chmod(p, m)

fchmodat(AT_FDCWD, p, m, 0)

chown(p, u, g)

fchownat(AT_FDCWD, p, u, g, 0)

creat(p, m)

openat(AT_FDCWD, p, O_WRONLY|O_CREAT|O_TRUNC, m)

fchmod(fd, m)

fchmodat(fd, NULL, m, 0)

fchown(fd, u, g)

fchownat(fd, NULL, u, g, 0)

fstat(fd, s)

fstatat(fd, NULL, s, 0)

lchown(p, u, g)

fchownat(AT_FDCWD, p, u, g, AT_SYMLINK_NOFOLLOW)

link(p1, p2)

linkat(AT_FDCWD, p1, AT_FDCWD, p2, 0)

lstat(p, s)

fstatat(AT_FDCWD, p, s, AT_SYMLINK_NOFOLLOW)

mkdir(p, m)

mkdirat(AT_FDCWD, p, m)

mknod(p, m, d)

mknodat(AT_FDCWD, p, m, d)

open(p, o, m)

openat(AT_FDCWD, p, o, m)

readlink(p, b, s)

readlinkat(AT_FDCWD, p, b, s)

rename(p1, p2)

renameat(AT_FDCWD, p1, AT_FDCWD, p2)

rmdir(p)

unlinkat(AT_FDCWD, p, AT_REMOVEDIR)

stat(p, s)

fstatat(AT_FDCWD, p, s, 0)

symlink(p1, p2)

symlinkat(p1, AT_FDCWD, p2)

unlink(p)

unlinkat(AT_FDCWD, p, 0)

 大規模ファイルのシステム・コール

2GBを超える大規模ファイルをサポートする32ビット・プログラムは、64ビット・ファイルのオフセットを処理できる必要があります。大規模ファイルは大規模なオフセットを必要とするため、大規模ファイルの操作には複数のシステム・インタフェースを並行して利用します。次の表に、大規模ファイルのシステム・コール・インタフェースに対応するsyscallプローブの一部をリストします。

表11-3 syscall大規模ファイル・プローブ

大規模ファイルのsyscallプローブ システム・コール

getdents64

getdents()

pread64 *

pread()

pwrite64 *

pwrite()

非公開システム・コール

一部のシステム・コールは、ユーザーとカーネルの間の境界にまたがるOracle Linuxサブシステムの非公開実装です。

syscallプローブの引数

entryプローブの場合、引数arg0 ... argnはシステム・コールの引数です。returnプローブの場合、arg0arg1の両方に戻り値が格納されます。D変数errnoの値がゼロ以外の場合は、システム・コールが失敗であることを示します。

syscallの安定性

syscallプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの安定性の値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

共通

モジュール

非公開

非公開

不明

関数

非公開

非公開

命令セット・アーキテクチャ(ISA)

名前

発展中

発展中

共通

引数

非公開

非公開

ISA

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。

sdtプロバイダ

静的定義トレース(SDT)プロバイダ(sdt)は、ソフトウェア・プログラマが正式に指定した位置にプローブを作成します。プログラマは、SDTメカニズムを使用して、DTraceユーザーにとって重要な位置を意識的に選択し、それぞれの位置のセマンティクス情報をプローブ名によって伝えることができます。

重要なこととして、SDTはメタプロバイダとしても機能し、プローブを登録することにより、独自の専用モジュールを持たないioprocおよびschedなどの他のプロバイダからのプローブのように表示することもできます。したがって、SDTプロバイダは主に、新しいプロバイダの開発者のにとってのみ関心の対象になります。ほとんどのユーザーは、他のプロバイダを使用して間接的にのみSDTにアクセスします。

ノート:

Oracle Linuxカーネルのsdtプローブの定義は、今後変更される可能性があるため、ここにはリストされていません。プローブの名前の安定性とデータの安定性はどちらも非公開ですが、これはカーネルの実装を反映しており、これらのインタフェースが維持されることを確約するという意味ではありません。詳細は、「DTraceの安定性機能」を参照してください。

sdtプローブの作成

デバイス・ドライバの開発者であれば、使用しているOracle Linuxドライバ用に独自のsdtプローブを作成することに関心がある場合があります。SDTの無効化されたプローブの影響は、基本的にいくつかのno-opマシン命令のコストになります。したがって、必要に応じてsdtプローブをデバイス・ドライバに追加するようにしてください。これらのプローブは、パフォーマンスにマイナスの影響を及ぼすことがないかぎり、出荷コードに残しておくことができます。「カーネル・モジュールの静的定義トレース」を参照してください。

DTraceには、アプリケーション開発者がユーザー空間の静的なプローブを定義するためのメカニズムも用意されています。「ユーザー・アプリケーションの静的定義トレース」を参照してください。

プローブの宣言

sdtプローブは、<linux/sdt.h>DTRACE_PROBEマクロを使用して宣言されます。

SDTベースのプローブのモジュール名および関数名はそれぞれ、プローブのカーネル・モジュールおよび関数に対応しています。DTraceでは、カーネル・モジュール名および関数名がプローブを識別するタプルの一部として含まれているため、名前空間の衝突を防ぐためにこの情報をプローブ名に含める必要はありません。ドライバ・モジュールに、インストール済のプローブとDTraceユーザーに表示されるフル・ネームをリストするには、dtrace -l -m moduleコマンドを使用します。

プローブの名前は、DTRACE_PROBEマクロに指定された名前に依存します。その名前に連続する2つのアンダースコア(__)が含まれていない場合、プローブの名前はマクロに記述されたとおりです。名前に連続する2つのアンダースコアが含まれている場合、プローブ名では連続するアンダースコアが単一のダッシュ(-)に変換されます。たとえば、DTRACE_PROBEマクロでtransaction__startが指定されている場合、SDTプローブはtransaction-startという名前になります。この置換により、有効なC識別子ではないマクロ名を文字列を指定せずにCコードで指定できます。

また、SDTはメタプロバイダとしても機能し、プローブを登録することにより、独自の専用モジュールを持たないioprocおよびschedなどの他のプロバイダからのプローブのように表示することもできます。たとえば、kernel/exit.cには、<linux/sdt.h>に次のように定義されている、DTRACE_PROCマクロのコールが含まれています。

# define DTRACE_PROC(name) \
         DTRACE_PROBE(__proc_##name);

このようなマクロを使用するプローブは、sdt以外のプロバイダからのプローブのように表示されます。name引数に含まれる先頭の2つのアンダースコア、プロバイダ名および末尾のアンダースコアは、プロバイダを一致させるために使用され、プローブ名には含まれません。DTraceにハードコーディングされているプロバイダ以外のプロバイダのプローブを作成する機能は、現在使用できません。

sdtプローブの引数

sdtプローブの引数は、対応するDTRACE_PROBEマクロ参照のカーネル・ソース・コードに指定された引数です。sdtプローブを宣言するとき、プローブ引数でポインタの間接解除やグローバル変数からのロードを行わないことで、無効化されたプローブの影響を最小限にできます。ポインタの間接解除もグローバル変数のロードも、プローブを有効化するDアクションで安全に実行できるため、DTraceユーザーは必要なときにのみこれらのアクションをリクエストできます。

sdtの安定性

sdtプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

ISA

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

非公開

非公開

ISA

引数

非公開

非公開

ISA

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。

pidプロバイダ

pidプロバイダは、そのpidで指定されたように、任意のユーザー・プロセスのトレースを有効にします。

pidプロバイダは、ユーザー・プログラムでトレース関数の開始と終了を有効にします(fbtプロバイダがカーネルに対してその機能を提供するのと同様です)。このガイドでは、fbtプロバイダを使用してカーネル関数のコールをトレースするほとんどの例は、ユーザー・プロセスに適用するために若干変更できます。

pidプロバイダは、絶対アドレスまたは関数オフセットで指定されたように、任意の命令のトレースも有効にします。

プローブが有効になっていない場合、pidプロバイダにはプローブ効果がありません。プローブが有効な場合、プローブはトレースされるプロセスでのみプローブ効果をもたらします。

ノート:

コンパイラが関数をインライン化する場合、pidプロバイダのプローブは起動しません。特定のC関数をインライン化しないようにコンパイルするには、次のいずれかの方法を使用します。

  • Sun Studio: #pragma no_inline (funcname[, funcname])

  • gcc: funcname __attribute__ ((noinline))

更新については、コンパイラのドキュメントを参照してください。

pidプローブの命名

pidプロバイダは、実際にはプロバイダのクラスを定義します。各プロセスは、独自の関連付けられたpidプロバイダを持つ可能性があります。たとえば、ID 123のプロセスは、pid123プロバイダを使用してトレースされます。

プローブ記述のモジュール部分は、対応するプロセスのアドレス空間にロードされるオブジェクトを参照します。my_exec用にロードされるオブジェクト、またはプロセスID 123用にロードされるオブジェクトを確認するには、次のコマンドを使用します。

# ldd my_exec
...
# pldd 123
123:  /tmp/my_exec
linux-vdso.so.1
/lib64/libc.so.6
/lib64/ld-linux-x86-64.so.2p

プローブ記述では、フルパス名ではなく、ファイルの名前でオブジェクトに名前を付けます。接尾辞.6またはso.6を省略することもできます。次の例では、すべて同じプローブに名前を付けます。

pid123:libc.so.6:strcpy:entry
pid123:libc.so:strcpy:entry
pid123:libc:strcpy:entry

最初の例は、プローブの実際の名前です。その他の例は、内部でフル・ロード・オブジェクト名に置き換えられる便利な別名です。

実行可能ファイルのロード・オブジェクトについては、a.outの別名を使用できます。次の2つのプローブ記述は、同じプローブに名前を付けます。

pid123:my_exec:main:return
pid123:a.out:main:return

プローブ記述の関数フィールドは、モジュール内の関数に名前を付けます。ユーザー・アプリケーション・バイナリには、同じ関数に対する複数の名前がある場合があります。たとえば、__gnu_get_libc_versionは、libc.so.6の関数gnu_get_libc_versionの代替名になります。DTraceは、このような関数に対して1つの正規名を選択し、その名前を内部的に使用します。

次の例は、DTraceがモジュール名と関数名を正規の形式に内部的に再マップする仕組みを示しています。

# dtrace -q -n 'pid123:libc:__gnu_get_libc_version:
    { printf("%s\n%s\n", probemod, probefunc)}'
libc.so.6
gnu_get_libc_version

pidプロバイダを効果的に使用する方法の例は、「ユーザー・プロセスのトレース」を参照してください。

pidプローブの引数

entryプローブは、トレースされた関数が呼び出されたときに起動します。entryプローブの引数は、トレースされた関数の引数の値です。

returnプローブは、トレースされた関数が戻るか、別の関数への末尾コールを行うと起動します。arg1プローブの引数は、関数の戻り値を保持します。

オフセット・プローブは、関数内の指定されたオフセットの命令に実行が到達したときに起動します。たとえば、アドレス4バイトの命令を関数mainにトレースするには、pid123:a.out:main:4を使用できます。オフセット・プローブの引数は定義されていません。uregs[]配列は、これらのプローブ・サイトでプロセスの状態を調べるときに役立ちます。「uregs[]配列」を参照してください。

pidの安定性

pidプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

ISA

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

発展中

発展中

ISA

引数

非公開

非公開

不明

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。

procプロバイダ

procプロバイダは、プロセスの作成と終了、LWPの作成と終了、新しいプログラム・イメージの実行、およびシグナルの送信と処理に関連するプローブを使用可能にします。

procプローブ

次の表に、procプロバイダのプローブを示します。

表11-4 procプローブ

プローブ 説明

create

fork()またはvfork() (どちらもclone()を呼び出す)を使用してプロセス(またはプロセス・スレッド)が作成されるときに起動します。新しい子プロセスに対応するpsinfo_tは、args[0]によってポイントされます。

exec

プロセスがexecve()システム・コールのバリアントを使用して、新しいプロセス・イメージをロードするときに起動します。execプローブが起動するのは、プロセス・イメージがロードされる前です。したがって、execnamecurpsinfoなどのプロセス変数には、イメージがロードされるより前のプロセス状態が格納されます。execプローブの起動後しばらくしてから、同じスレッドでexec-failureプローブまたはexec-successプローブが起動します。新しいプロセス・イメージのパスは、args[0]によってポイントされます。

exec-failure

exec()のバリアントが失敗したときに起動します。exec-failureプローブは常に、同じスレッドでexecプローブが起動した後で起動します。errno値は、args[0]で指定されます。

exec-success

exec()のバリアントが成功したときに起動します。exec-failureプローブと同様、exec-successプローブは常に、同じスレッドでexecプローブが起動した後に起動します。exec-successプローブが起動したときにはすでに、execnamecurpsinfoなどのプロセス変数に、新しいプロセス・イメージがロードされた後のプロセス状態が格納されています。

exit

現在のプロセスが終了しようとするときに起動します。終了の理由が、いずれかのSIGCHLD <asm-generic/signal.h>コードとしてargs[0]に格納されます。

lwp-create

プロセス・スレッドの作成時に起動します。後者は通常、pthread_create()の結果として作成されます。新しいスレッドに対応するlwpsinfo_tは、args[0]によってポイントされます。スレッドを作成したプロセスを表すpsinfo_tは、args[1]によってポイントされます。

lwp-exit

シグナル、またはexitpthread_exit()の明示的なコールによって、プロセスまたはプロセス・スレッドが終了する直前に起動します。

lwp-start

新しく作成されたプロセスまたはプロセス・スレッドのコンテキストで起動します。lwp-startプローブは、ユーザー・レベル命令が実行されるより先に起動します。スレッドがプロセスで最初に作成される場合は、まずstartプローブが起動し、続いてlwp-startプローブが起動します。

signal-clear

ターゲット・スレッドがsigwait()sigwaitinfo()またはsigtimedwait()でシグナルを待機しているために、保留中のシグナルがクリアされたときに起動します。この条件になると、保留中のシグナルはクリアされ、コール元にはシグナル番号が返されます。シグナル番号はargs[0]に格納され、signal-clearは、それ以前に待機していたスレッドのコンテキストで起動します。

signal-discard

シングルスレッド・プロセスにシグナルが送信され、シグナルがプロセスによってブロック解除されて無視されたときに起動します。この条件では、シグナルは作成後すぐに破棄されます。ターゲット・プロセスおよびスレッドのlwpsinfo_tpsinfo_tは、それぞれargs[0]args[1]に格納されます。シグナル番号はargs[2]に格納されます。

signal-handle

スレッドがシグナルを処理する直前に起動します。signal-handleプローブは、シグナルを処理するスレッドのコンテキストで起動します。シグナル番号はargs[0]に格納されます。シグナルに対応するsiginfo_t構造体へのポインタは、args[1]に格納されます。プロセスにおけるシグナル・ハンドラのアドレスは、args[2]に格納されます。

signal-send

プロセス、またはプロセスによって作成されたスレッドにシグナルが送信されたときに起動します。signal-sendプローブは、送信側のプロセスまたはスレッドのコンテキストで起動します。受信側プロセスおよびスレッドのlwpsinfo_tpsinfo_tは、それぞれargs[0]args[1]に格納されます。シグナル番号はargs[2]に格納され、必ず受信側プロセスおよびスレッドでsignal-handleまたはsignal-clearが続きます。

start

新しく作成されたプロセスのコンテキストで起動します。startプローブは、そのプロセスで他のどのユーザー・レベル命令が実行されるより先に起動します。

ノート:

Linuxでは、プロセスと、プロセスによって作成されるスレッドとの間の根本的な違いはありません。プロセスのスレッドはリソースを共有できるように設定されますが、各スレッドはプロセス表に独自のエントリを持ち、プロセスIDも独自です。

procプローブの引数

次の表は、procプローブの引数の型を示しています。引数の詳細は、表11-4を参照してください。

表11-5 procプローブの引数

プローブ args[0] args[1] args[2]

create

psinfo_t *

exec

char *

exec-failure

int

exec-success

exit

int

lwp-create

lwpsinfo_t *

psinfo_t *

lwp-exit

lwp-start

signal-clear

int

signal-discard

lwpsinfo_t *

psinfo_t *

int

signal-handle

int

siginfo_t *

void (*)(void)

signal-send

lwpsinfo_t *

psinfo_t *

int

start

lwpsinfo_t

一部のprocプローブは、lwpsinfo_t型の引数をとります。このデータ構造体の詳細は、/usr/lib64/dtrace/version/procfs.dを参照してください。DTraceコンシューマで使用可能なlwpsinfo_t構造体の定義は、次のとおりです。

typedef struct lwpsinfo {
  int pr_flag;                /* flags */
  id_t pr_lwpid;              /* thread id */
  uintptr_t pr_addr;          /* internal address of thread */
  uintptr_t pr_wchan;         /* wait addr for sleeping lwp (NULL on Linux) */
  char pr_stype;              /* sync event type (0 on Linux) */
  char pr_state;              /* numeric thread state */
  char pr_sname;              /* printable character for pr_state */
  int pr_pri;                 /* priority, high value = high priority */
  char pr_name[PRCLSZ];       /* scheduling class name */
  processorid_t pr_onpro;     /* processor which last ran this thread */
} lwpsinfo_t;

ノート:

Linuxには、軽量プロセスが存在しません。かわりに、Oracle Linuxのタスク・リストでは、プロセスとスレッドはstruct task_struct型のプロセス記述子によって表されます。DTraceは、lwpsinfo_tのメンバーを、Oracle Linuxプロセスのtask_structから変換します。

スレッドが停止する場合、pr_flag1に設定されます。そうでない場合、0に設定されます。

Oracle Linuxでは、pr_stypeフィールドはサポートされていないため、常に0です。

次の表では、pr_stateがとれる値と、pr_snameの対応する文字値について説明します。

表11-6 pr_stateの値

pr_stateの値 pr_sname Value 説明

SRUN (2)

R

スレッドは実行可能ですが、現在CPUで実行されていません。sched:::enqueueプローブは、スレッドの状態がSRUNに移行する直前に起動します。sched:::on-cpuプローブは、スレッドが実行を開始した直後に起動します。

Oracle Linuxで同等のタスク状態は、TASK_RUNNINGです。

SSLEEP (1)

S

スレッドはスリープ中です。sched:::sleepプローブは、スレッドの状態がSSLEEPに移行する直前に起動します。

Oracle Linuxで同等のタスク状態は、TASK_INTERRUPTABLEまたはTASK_UNINTERRUPTABLEです。

SSTOP (4)

T

スレッドは、明示的なprocディレクティブまたはその他の停止メカニズムにより停止しています。

Oracle Linuxで同等のタスク状態は、__TASK_STOPPEDまたは__TASK_TRACEDです。

SWAIT (7)

W

スレッドは待機キューで待機中です。sched:::cpucaps-sleepプローブは、スレッドの状態がSWAITに移行する直前に起動します。

Oracle Linuxで同等のタスク状態は、TASK_WAKEKILLまたはTASK_WAKINGです。

SZOMB (3)

Z

スレッドはゾンビです。

Oracle Linuxで同等のタスク状態は、EXIT_ZOMBIEEXIT_DEADまたはTASK_DEADです。

psinfo_t

一部のprocプローブは、psinfo_t型の引数をとります。このデータ構造体の詳細は、/usr/lib64/dtrace/version/procfs.dを参照してください。DTraceコンシューマで使用可能なpsinfo_t構造体の定義は、次のとおりです。

typedef struct psinfo {
        int pr_nlwp;                    /* not supported */
        pid_t pr_pid;                   /* unique process id */
        pid_t pr_ppid;                  /* process id of parent */
        pid_t pr_pgid;                  /* pid of process group leader */
        pid_t pr_sid;                   /* session id */
        uid_t pr_uid;                   /* real user id */
        uid_t pr_euid;                  /* effective user id */
        uid_t pr_gid;                   /* real group id */
        uid_t pr_egid;                  /* effective group id */
        uintptr_t pr_addr;              /* address of process */
        size_t pr_size;                 /* not supported */
        size_t pr_rssize;               /* not supported */
        struct tty_struct *pr_ttydev;   /* controlling tty (or -1) */
        ushort_t pr_pctcpu;             /* not supported */
        ushort_t pr_pctmem;             /* not supported */
        timestruc_t pr_start;           /* not supported */
        timestruc_t pr_time;            /* not supported */
        timestruc_t pr_ctime;           /* not supported */
        char pr_fname[16];              /* name of exec'ed file */
        char pr_psargs[80];             /* initial chars of arg list */
        int pr_wstat;                   /* not supported */
        int pr_argc;                    /* initial argument count */
        uintptr_t pr_argv;              /* address of initial arg vector */
        uintptr_t pr_envp;              /* address of initial env vector */
        char pr_dmodel;                 /* data model */
        taskid_t pr_taskid;             /* not supported */
        projid_t pr_projid;             /* not supported */
        int pr_nzomb;                   /* not supported */
        poolid_t pr_poolid;             /* not supported */
        zoneid_t pr_zoneid;             /* not supported */
        id_t pr_contract;               /* not supported */
        lwpsinfo_t pr_lwp;              /* not supported */
} psinfo_t;

ノート:

Linuxには、軽量プロセスが存在しません。Oracle Linuxのタスク・リストでは、プロセスとスレッドはstruct task_struct型のプロセス記述子によって表されます。DTraceは、psinfo_tのメンバーを、Oracle Linuxプロセスのtask_structから変換します。

pr_dmodelは、32ビット・プロセスを表すPR_MODEL_ILP32か、64ビット・プロセスを表すPR_MODEL_LP64に設定されます。

procの例

次の例は、procプロバイダによって公開されるプローブの使用を示しています。

exec

次の例は、execプローブを使用して、どのプログラムが誰によって実行されているかを容易に判別する方法を示しています。次のDソース・コードを入力し、whoexec.dという名前のファイルに保存します。

#pragma D option quiet

proc:::exec
{
  self->parent = execname;
}

proc:::exec-success
/self->parent != NULL/
{
  @[self->parent, execname] = count();
  self->parent = NULL;
}

proc:::exec-failure
/self->parent != NULL/
{
  self->parent = NULL;
}

END
{
  printf("%-20s %-20s %s\n", "WHO", "WHAT", "COUNT");
  printa("%-20s %-20s %@d\n", @);
}

短時間この例のスクリプトを実行すると、次のような出力が得られます。

# dtrace -s ./whoexec.d
^C
WHO                  WHAT                 COUNT
abrtd                abrt-handle-eve      1
firefox              basename             1
firefox              mkdir                1
firefox              mozilla-plugin-      1
firefox              mozilla-xremote      1
firefox              run-mozilla.sh       1
firefox              uname                1
gnome-panel          firefox              1
kworker/u:1          modprobe             1
modprobe             modprobe.ksplic      1
mozilla-plugin-      plugin-config        1
mozilla-plugin-      uname                1
nice                 sosreport            1
run-mozilla.sh       basename             1
run-mozilla.sh       dirname              1
run-mozilla.sh       firefox              1
run-mozilla.sh       uname                1
sh                   abrt-action-sav      1
sh                   blkid                1
sh                   brctl                1
sh                   cut                  1
...
startプローブとexitプローブ

プログラムが作成されてから終了するまでの実行時間を調べる場合は、次の例で示すようにstartプローブとexitプローブを有効にします。progtime.dという名前のファイルに保存します。

proc:::start
{
  self->start = timestamp;
}

proc:::exit
/self->start/
{
  @[execname] = quantize(timestamp - self->start);
  self->start = 0;
}

ビルド・サーバー上で数秒間この例のスクリプトを実行すると、次のような出力が得られます。

# dtrace -s ./progtime.d
dtrace: script ’./progtime.d’ matched 2 probes
^C
...
cc
          value  ------------- Distribution ------------- count
       33554432 |                                         0
       67108864 |@@@                                      3
      134217728 |@                                        1
      268435456 |                                         0
      536870912 |@@@@                                     4
     1073741824 |@@@@@@@@@@@@@@                           13
     2147483648 |@@@@@@@@@@@@                             11
     4294967296 |@@@                                      3
     8589934592 |                                         0

sh
          value  ------------- Distribution ------------- count
         262144 |                                         0
         524288 |@                                        5
        1048576 |@@@@@@@                                  29
        2097152 |                                         0
        4194304 |                                         0
        8388608 |@@@                                      12
       16777216 |@@                                       9
       33554432 |@@                                       9
       67108864 |@@                                       8
      134217728 |@                                        7
      268435456 |@@@@@                                    20
      536870912 |@@@@@@                                   26
     1073741824 |@@@                                      14
     2147483648 |@@                                       11
     4294967296 |                                         3
     8589934592 |                                         1
    17179869184 |                                         0
...
signal-send

次の例は、signal-sendプローブを使用して、シグナルに関連付けられたプロセスの送受信を確認する方法を示しています。次のDソース・コードを入力し、sig.dという名前のファイルに保存します。

#pragma D option quiet

proc:::signal-send
{
  @[execname, stringof(args[1]->pr_fname), args[2]] = count();
}

END
{
  printf("%20s %20s %12s %s\n",
      "SENDER", "RECIPIENT", "SIG", "COUNT");
  printa("%20s %20s %12d %@d\n", @);
}

このスクリプトを実行すると、次のような出力が得られます。

# dtrace -s sig.d
^C
              SENDER            RECIPIENT          SIG COUNT
         gnome-panel                 Xorg           29 1
         kworker/0:2               dtrace            2 1
                Xorg                 Xorg           29 3
                java                 Xorg           29 6
             firefox                 Xorg           29 14
         kworker/0:0                 Xorg           29 1135

procの安定性

procプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

ISA

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

発展中

発展中

ISA

引数

発展中

発展中

ISA

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。

schedプロバイダ

schedプロバイダは、CPUスケジューリングに関連するプローブを使用可能にします。CPUはすべてのスレッドが必ず使用する単一のリソースであるため、schedプロバイダは、体系的な動作を把握するために非常に役立ちます。たとえば、schedプロバイダを使用すると、スレッドのスリープ、実行、優先順位の変更、別のスレッドの呼出しなどが実行されたタイミングと理由を調べることができます。

schedプローブ

次の表では、schedプロバイダのプローブについて説明します。

表11-7 schedプローブ

プローブ 説明

change-pri

スレッドの優先順位が変更される直前に起動します。スレッドを表すlwpsinfo_tは、args[0]によってポイントされます。スレッドの現在の優先順位は、この構造体のpr_priフィールドに格納されます。スレッドを含むプロセスを表すpsinfo_tは、args[1]によってポイントされます。スレッドの新しい優先順位は、args[2]に格納されます。

dequeue

実行可能スレッドが実行キューからデキューされる直前に起動します。デキューされるスレッドを表すlwpsinfo_tは、args[0]によってポイントされます。スレッドを含むプロセスを表すpsinfo_tは、args[1]によってポイントされます。スレッドがデキューされる元のCPUを表すcpuinfo_tは、args[2]によってポイントされます。特定のCPUに関連付けられていない実行キューからスレッドがデキューされる場合、この構造体のcpu_idメンバーの値は-1になります。

enqueue

実行可能スレッドが実行キューにエンキューされる直前に起動します。エンキューされるスレッドを表すlwpsinfo_tは、args[0]によってポイントされます。スレッドを含むプロセスを表すpsinfo_tは、args[1]によってポイントされます。スレッドをキューに入れるCPUを表すcpuinfo_tは、args[2]によってポイントされます。特定のCPUに関連付けられていない実行キューにスレッドがエンキューされる場合、この構造体のcpu_idメンバーの値は-1になります。args[3]は、スレッドを実行キューの先頭に入れるかどうかを示すブール値です。この値は、スレッドを実行キューの先頭に入れる場合はゼロ以外の値に、実行キューの末尾に入れる場合はゼロになります。

off-cpu

現在のCPUがスレッドの実行を終了する直前に起動します。curcpu変数は、現在のCPUを表します。curlwpsinfo変数は、実行を終了しようとしているスレッドを表します。現在のCPUが次に実行するスレッドを表すlwpsinfo_tは、args[0]によってポイントされます。次のスレッドを含むプロセスを表すpsinfo_tは、args[1]によってポイントされます。

on-cpu

CPUがスレッドの実行を開始した直後に起動します。curcpu変数は、現在のCPUを表します。curlwpsinfo変数は、実行を開始しようとしているスレッドを表します。curpsinfo変数は、現在のスレッドが含まれているプロセスを説明します。

preempt

現在のスレッドが専有される直前に起動します。このプローブの起動後、現在のスレッドが実行するスレッドを選択し、現在のスレッドに対してoff-cpuプローブが起動します。場合によっては、1つのCPU上のスレッドが専有されても、専有しているスレッドが別のCPU上で実行されることがあります。この場合もpreemptプローブは起動しますが、ディスパッチャは実行する優先順位の高いスレッドを見つけることができず、off-cpuプローブではなくremain-cpuプローブが起動します。

remain-cpu

スケジューリングに関する決定がなされても、ディスパッチャが現在のスレッドの実行を続行しようとしたときに起動します。curcpu変数は、現在のCPUを表します。curlwpsinfo変数は、実行を開始しようとしているスレッドを表します。curpsinfo変数は、現在のスレッドが含まれているプロセスを説明します。

sleep

現在のスレッドが同期オブジェクトでスリープする直前に起動します。同期オブジェクトの型は、curlwpsinfoがポイントしているlwpsinfo_tpr_stypeメンバーに格納されます。同期オブジェクトのアドレスは、curlwpsinfoがポイントしているlwpsinfo_tpr_wchanメンバーに格納されます。このアドレスの意味は非公開の実装詳細ですが、アドレス値は同期オブジェクトに固有のトークンとして機能します。

surrender

CPUが、別のCPUから、スケジューリングに関する決定を下すよう命令されたときに起動します。この命令は通常、より優先順位の高いスレッドが実行可能になったときに発行されます。現在のスレッドを表すlwpsinfo_tは、args[0]によってポイントされます。スレッドを含むプロセスを表すpsinfo_tは、args[1]によってポイントされます。

tick

クロック刻みベースのアカウンティングの一環として起動します。クロック刻みベースのアカウンティングでは、固定間隔の割込みが発生したとき、どのスレッドとプロセスが実行されていたかを調べることによって、CPUのアカウンティングが実行されます。CPU時間を割り当てられているスレッドに対応するlwpsinfo_tは、args[0]によってポイントされます。スレッドを含むプロセスに対応するpsinfo_tは、args[1]によってポイントされます。

wakeup

現在のスレッドが同期オブジェクトでスリープしているスレッドを呼び出す直前に起動します。スリープ中のスレッドを表すlwpsinfo_tは、args[0]によってポイントされます。スリープ中のスレッドを含むプロセスを表すpsinfo_tは、args[1]によってポイントされます。同期オブジェクトの型は、スリープ中のスレッドのlwpsinfo_tpr_stypeメンバーに格納されます。同期オブジェクトのアドレスは、スリープ中のスレッドのlwpsinfo_tpr_wchanメンバーに格納されます。このアドレスの意味は非公開の実装詳細ですが、アドレス値は同期オブジェクトに固有のトークンとして機能します。

schedプローブの引数

次の表では、schedプローブの引数の型について説明します。引数の詳細は、表11-7を参照してください。

表11-8 schedプローブの引数

プローブ args[0] args[1] args[2] args[3]

change-pri

lwpsinfo_t *

psinfo_t *

int

dequeue

lwpsinfo_t *

psinfo_t *

cpuinfo_t *

enqueue

lwpsinfo_t *

psinfo_t *

cpuinfo_t *

int

off-cpu

lwpsinfo_t *

psinfo_t *

on-cpu

preempt

remain-cpu

sleep

surrender

lwpsinfo_t *

psinfo_t *

tick

lwpsinfo_t *

psinfo_t *

wakeup

lwpsinfo_t *

psinfo_t *

cpuinfo_t

cpuinfo_t構造体は、CPUを定義します。表11-8に示すように、enqueueプローブとdequeueプローブの両方の引数には、cpuinfo_tへのポインタが含まれています。また、現在のCPUに対応するcpuinfo_tは、curcpu変数によってポイントされます。

cpuinfo_t構造体の定義は、次のとおりです。

typedef struct cpuinfo {
  processorid_t cpu_id;      /* CPU identifier */
  psetid_t cpu_pset;         /* not supported */
  chipid_t cpu_chip;         /* chip identifier */
  lgrp_id_t cpu_lgrp;        /* not supported */
  cpuinfo_arch_t *cpu_info;  /* CPU information */
} cpuinfo_t;

cpu_id: プロセッサ識別子です。

cpu_chip: 物理チップの識別子です。物理チップには、複数のCPUコアが含まれる場合があります。

cpu_info: CPUに関連付けられたcpuinfo_arch_t構造体へのポインタです。

schedの例

次の例は、schedプローブの使用方法を示しています。

on-cpuプローブとoff-cpuプローブ

どのCPUが、どのくらいの時間スレッドを実行しているかを調べる必要がある場合がたびたびあります。次の例は、on-cpuプローブとoff-cpuプローブを使用して、システム全体でこの問題の回答を簡単に導き出すことができる方法を示しています。次のDソース・コードを入力し、where.dという名前のファイルに保存します。

sched:::on-cpu
{
  self->ts = timestamp;
}

sched:::off-cpu
/self->ts/
{
  @[cpu] = quantize(timestamp - self->ts);
  self->ts = 0;
}

前述のスクリプトを実行すると、次のような出力が得られます。

# dtrace -s ./where.d
dtrace: script ’./where.d’ matched 2 probes
^C
        0
          value  ------------- Distribution ------------- count
           2048 |                                         0
           4096 |@@                                       37
           8192 |@@@@@@@@@@@@@                            212
          16384 |@                                        30
          32768 |                                         10
          65536 |@                                        17
         131072 |                                         12
         262144 |                                         9
         524288 |                                         6
        1048576 |                                         5
        2097152 |                                         1
        4194304 |                                         3
        8388608 |@@@@                                     75
       16777216 |@@@@@@@@@@@@                             201
       33554432 |                                         6
       67108864 |                                         0

        1
          value  ------------- Distribution ------------- count
           2048 |                                         0
           4096 |@                                        6
           8192 |@@@@                                     23
          16384 |@@@                                      18
          32768 |@@@@                                     22
          65536 |@@@@                                     22
         131072 |@                                        7
         262144 |                                         5
         524288 |                                         2
        1048576 |                                         3
        2097152 |@                                        9
        4194304 |                                         4
        8388608 |@@@                                      18
       16777216 |@@@                                      19
       33554432 |@@@                                      16
       67108864 |@@@@                                     21
      134217728 |@@                                       14
      268435456 |                                         0

前述の出力は、CPU 1上のスレッドが連続的に131072ナノ秒未満(100マイクロ秒ほど)実行されるか、8388608から134217728ナノ秒(約10から100ミリ秒)実行される傾向にあることを示しています。ヒストグラムから、2つのデータ・クラスタ間に顕著な差異があることがわかります。また、どのCPUが特定のプロセスを実行しているかを知りたい場合もあります。

この問題に答える場合にも、on-cpuプローブとoff-cpuプローブを使用します。次のスクリプトでは、指定されたアプリケーションを10秒間以上実行するCPUが表示されます。whererun.d.という名前のファイルに保存します。

#pragma D option quiet
dtrace:::BEGIN
{
  start = timestamp;
}

sched:::on-cpu
/execname == $$1/
{
  self->ts = timestamp;
}

sched:::off-cpu
/self->ts/
{
  @[cpu] = sum(timestamp - self->ts);
  self->ts = 0;
}

profile:::tick-1sec
/++x >= 10/
{
  exit(0);
}

dtrace:::END
{
  printf("CPU distribution over %d seconds:\n\n",
    (timestamp - start) / 1000000000);
  printf("CPU microseconds\n--- ------------\n");
  normalize(@, 1000);
  printa("%3d %@d\n", @);
}

前述のスクリプトを大規模なメール・サーバー上で実行し、IMAPデーモンを指定すると、次のような出力が得られます。

# dtrace -s ./whererun.d imapd
CPU distribution of imapd over 10 seconds:

CPU microseconds
--- ------------
 15 10102
 12 16377
 21 25317
 19 25504
 17 35653
 13 41539
 14 46669
 20 57753
 22 70088
 16 115860
 23 127775
 18 160517

Oracle Linuxでは、スリープ時間が短かったスレッドは、移行しにくい傾向にあるため、スレッドを実行するCPUを選択する際に、スレッドがスリープしていた時間が考慮されます。off-cpuプローブとon-cpuプローブを使用して、この動作を監視します。次のソース・コードを入力し、howlong.dという名前のファイルに保存します。

sched:::off-cpu
/curlwpsinfo->pr_state == SSLEEP/
{
 self->cpu = cpu;
 self->ts = timestamp;
}

sched:::on-cpu
/self->ts/
{
 @[self->cpu == cpu ?
   "sleep time, no CPU migration" : "sleep time, CPU migration"] =
   lquantize((timestamp - self->ts) / 1000000, 0, 500, 25);
 self->ts = 0;
 self->cpu = 0;
}

前述のスクリプトを約30秒間実行すると、次のような出力が得られます。

# dtrace -s ./howlong.d
dtrace: script ’./howlong.d’ matched 2 probes
^C
 sleep time, CPU migration
          value  ------------- Distribution ------------- count
            < 0 |                                         0
              0 |@@@@@@@                                  6838
             25 |@@@@@                                    4714
             50 |@@@                                      3108
             75 |@                                        1304
            100 |@                                        1557
            125 |@                                        1425
            150 |                                         894
            175 |@                                        1526
            200 |@@                                       2010
            225 |@@                                       1933
            250 |@@                                       1982
            275 |@@                                       2051
            300 |@@                                       2021
            325 |@                                        1708
            350 |@                                        1113
            375 |                                         502
            400 |                                         220
            425 |                                         106
            450 |                                         54
            475 |                                         40
         >= 500 |@                                        1716

 sleep time, no CPU migration
          value  ------------- Distribution ------------- count
            < 0 |                                         0
              0 |@@@@@@@@@@@@                             58413
             25 |@@@                                      14793
             50 |@@                                       10050
             75 |                                         3858
            100 |@                                        6242
            125 |@                                        6555
            150 |                                         3980
            175 |@                                        5987
            200 |@                                        9024
            225 |@                                        9070
            250 |@@                                       10745
            275 |@@                                       11898
            300 |@@                                       11704
            325 |@@                                       10846
            350 |@                                        6962
            375 |                                         3292
            400 |                                         1713
            425 |                                         585
            450 |                                         201
            475 |                                         96
         >= 500 |                                         3946

前述の出力から、移行よりも、移行が行われていない回数の方が多いことがわかります。また、スリープ時間が長くなるほど、移行が発生しやすくなっています。100ミリ秒以下のときは分布状態に顕著な差異が見られますが、スリープ時間が長くなるほど、この差異はほとんど見られなくなります。この結果から、一定のしきい値を超えると、スケジューリングに関する決定の際にスリープ時間は考慮されなくなるものと推測できます。

enqueueプローブとdequeueプローブ

どのCPUプロセスとスレッドが実行を待機しているかについて知る必要がある場合があります。enqueueプローブをdequeueプローブとともに使用すると、この問題の回答を導き出すことができます。次のソース・コードを入力し、qtime.dという名前のファイルに保存します。

sched:::enqueue
{
  a[args[0]->pr_lwpid, args[1]->pr_pid, args[2]->cpu_id] =
  timestamp;
}

sched:::dequeue
/a[args[0]->pr_lwpid, args[1]->pr_pid, args[2]->cpu_id]/
{
  @[args[2]->cpu_id] = quantize(timestamp -
    a[args[0]->pr_lwpid, args[1]->pr_pid, args[2]->cpu_id]);
  a[args[0]->pr_lwpid, args[1]->pr_pid, args[2]->cpu_id] = 0;
}

前述のスクリプトを数秒間実行すると、次のような出力が得られます。

# dtrace -s qtime.d 
dtrace: script 'qtime.d' matched 16 probes
^C

        1
           value  ------------- Distribution ------------- count    
            8192 |                                         0        
           16384 |                                         1        
           32768 |@                                        47       
           65536 |@@@@@@@                                  365      
          131072 |@@@@@@@@@@@@                             572      
          262144 |@@@@@@@@@@@@                             570      
          524288 |@@@@@@@                                  354      
         1048576 |@                                        57       
         2097152 |                                         7        
         4194304 |                                         1        
         8388608 |                                         1        
        16777216 |                                         0        

        0
           value  ------------- Distribution ------------- count    
            8192 |                                         0        
           16384 |                                         6        
           32768 |@                                        49       
           65536 |@@@@@                                    261      
          131072 |@@@@@@@@@@@@@                            753      
          262144 |@@@@@@@@@@@@                             704      
          524288 |@@@@@@@@                                 455      
         1048576 |@                                        74       
         2097152 |                                         9        
         4194304 |                                         2        
         8388608 |                                         0

待機時間を検索するのではなく、一定時間における実行キューの長さを調べる必要がある場合もあります。enqueueプローブとdequeueプローブを使用すると、キューの長さを追跡する連想配列を設定できます。次のソース・コードを入力し、qlen.dという名前のファイルに保存します。

sched:::enqueue
{
  this->len = qlen[args[2]->cpu_id]++;
  @[args[2]->cpu_id] = lquantize(this->len, 0, 100);
}

sched:::dequeue
/qlen[args[2]->cpu_id]/
{
  qlen[args[2]->cpu_id]--;
}

ほとんどの場合にアイドル状態である単一プロセッサ・システム上で、前述のスクリプトを約30秒間実行すると、次のような出力が得られます。

# dtrace -s qlen.d 
dtrace: script 'qlen.d' matched 16 probes
^C

        1
           value  ------------- Distribution ------------- count    
             < 0 |                                         0        
               0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@        8124     
               1 |@@@@@@                                   1558     
               2 |@                                        160      
               3 |                                         51       
               4 |                                         24       
               5 |                                         13       
               6 |                                         11       
               7 |                                         9        
               8 |                                         6        
               9 |                                         0        

        0
           value  ------------- Distribution ------------- count    
             < 0 |                                         0        
               0 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@           8569     
               1 |@@@@@@@@@                                2429     
               2 |@                                        292      
               3 |                                         25       
               4 |                                         8        
               5 |                                         5        
               6 |                                         4        
               7 |                                         4        
               8 |                                         1        
               9 |                                         0

アイドル状態のシステムでは、ほぼ予想どおりの出力が得られます。実行可能スレッドがエンキューされる時間の大部分では、実行キューはきわめて短くなります(長さは3スレッド以下)。ただし、このシステムがほとんどアイドル状態にあることを考えると、表の下部に例外データ・ポイントが出力されることは予想外になります。たとえば、この実行キューが実行可能スレッド8個分だったのはなぜなのでしょうか。この問題をさらに詳しく調べるには、実行キューが長いときに実行キューの内容を表示するDスクリプトを作成します。この問題が複雑なのは、Dの有効化をデータ構造に対して繰り返し実行できず、したがって実行キュー全体に対しても繰り返し実行できないためです。たとえDの有効化でこれが可能になるとしても、カーネルの内部データ構造に依存することは避ける必要があります。

この種のスクリプトを実行する場合は、enqueueプローブとdequeueプローブを有効にし、投機と連想配列の両方を使用します。スレッドがエンキューされると、スクリプトによってキューの長さが増分され、スレッドによりキー付けされた連想配列でタイムスタンプが記録されます。この場合、スレッドは別のスレッドによってエンキューされた可能性があるため、スレッド・ローカルの変数は使用できません。次に、スクリプトによってキューの長さが最大値を超えているかどうかが確認され、それを超えていた場合は新しい投機が開始され、タイムスタンプと新しい最大長が記録されます。次に、スレッドがデキューされると、enqueueのタイムスタンプと最大長のタイムスタンプが比較され、スレッドが最大長のタイムスタンプより前にエンキューされた場合、このスレッドは最大長のタイムスタンプが記録されとき、キューにあったことになります。この場合、このスレッドの情報が投機的にトレースされます。最大長のタイムスタンプの時点でエンキューされていた最後のスレッドがカーネルによってデキューされると、スクリプトによって投機データがコミットされます。次のソース・コードを入力し、whoqueue.dという名前のファイルに保存します。

#pragma D option quiet
#pragma D option nspec=4
#pragma D option specsize=100k

int maxlen;
int spec[int];
sched:::enqueue
{
  this->len = ++qlen[this->cpu = args[2]->cpu_id];
  in[args[0]->pr_addr] = timestamp;
}

sched:::enqueue
/this->len > maxlen && spec[this->cpu]/
{
  /*
   * There is already a speculation for this CPU. We just set a new
   * record, so we’ll discard the old one.
   */
  discard(spec[this->cpu]);
}

sched:::enqueue
/this->len > maxlen/
{
  /*
   * We have a winner. Set the new maximum length and set the timestamp
   * of the longest length.
   */
  maxlen = this->len;
  longtime[this->cpu] = timestamp;
  /*
   * Now start a new speculation, and speculatively trace the length.
   */
  this->spec = spec[this->cpu] = speculation();
  speculate(this->spec);
  printf("Run queue of length %d:\n", this->len);
}

sched:::dequeue
/(this->in = in[args[0]->pr_addr]) &&
  this->in <= longtime[this->cpu = args[2]->cpu_id]/
{
  speculate(spec[this->cpu]);
  printf(" %d/%d (%s)\n",
    args[1]->pr_pid, args[0]->pr_lwpid,
    stringof(args[1]->pr_fname));
}

sched:::dequeue
/qlen[args[2]->cpu_id]/
{
  in[args[0]->pr_addr] = 0;
  this->len = --qlen[args[2]->cpu_id];
}

sched:::dequeue
/this->len == 0 && spec[this->cpu]/
{
  /*
   * We just processed the last thread that was enqueued at the time
   * of longest length; commit the speculation, which by now contains
   * each thread that was enqueued when the queue was longest.
   */
  commit(spec[this->cpu]);
  spec[this->cpu] = 0;
}

前述のスクリプトを同じシステムに対して実行すると、次のような出力が得られます。

# dtrace -s whoqueue.d
Run queue of length 1:
 2850/2850 (java)
Run queue of length 2:
 4034/4034 (kworker/0:1)
 16/16 (sync_supers)
Run queue of length 3:
 10/10 (ksoftirqd/1)
 1710/1710 (hald-addon-inpu)
 25350/25350 (dtrace)
Run queue of length 4:
 2852/2852 (java)
 2850/2850 (java)
 1710/1710 (hald-addon-inpu)
 2099/2099 (Xorg)
Run queue of length 5:
 3149/3149 (notification-da)
 2417/2417 (gnome-settings-)
 2437/2437 (gnome-panel)
 2461/2461 (wnck-applet)
 2432/2432 (metacity)
Run queue of length 9:
 3685/3685 (firefox)
 3149/3149 (notification-da)
 2417/2417 (gnome-settings-)
 2437/2437 (gnome-panel)
 2852/2852 (java)
 2452/2452 (nautilus)
 2461/2461 (wnck-applet)
 2432/2432 (metacity)
 2749/2749 (gnome-terminal)
^C
sleepプローブとwakeupプローブ

次の例は、wakeupプローブを使用して、特定のプロセスが何によって呼び出されているか、またその期間はいつかを特定する方法を示しています。次のソース・コードを入力し、gterm.dという名前のファイルに保存します。

#pragma D option quiet

dtrace:::BEGIN
{
  start = timestamp;
}

sched:::wakeup
/stringof(args[1]->pr_fname) == "gnome-terminal"/
{
  @[execname] = lquantize((timestamp - start) / 1000000000, 0, 10);
}

profile:::tick-1sec
/++x == 10/
{
  exit(0);
}

このスクリプトを実行した出力は、次のとおりです。

# dtrace -s gterm.d

  Xorg                                              
           value  ------------- Distribution ------------- count    
             < 0 |                                         0        
               0 |@@@@@@@@@@@@@@@                          69       
               1 |@@@@@@@@                                 35       
               2 |@@@@@@@@@                                42       
               3 |                                         2        
               4 |                                         0        
               5 |                                         0        
               6 |                                         0        
               7 |@@@@                                     16       
               8 |                                         0        
               9 |@@@                                      15       
           >= 10 |                                         0  

この出力では、システムの対話中に、Xサーバーがgnome-terminalプロセスを起動していることがわかります。

また、sleepプローブとwakeupプローブを併用すると、どのアプリケーションが他のアプリケーションをどのくらいの時間ブロックしているかを判定できます。次のソース・コードを入力し、whofor.dという名前のファイルに保存します。

#pragma D option quiet
sched:::sleep
{
  bedtime[curlwpsinfo->pr_addr] = timestamp;
}

sched:::wakeup
/bedtime[args[0]->pr_addr]/
{
  @[stringof(args[1]->pr_fname), execname] =
    quantize(timestamp - bedtime[args[0]->pr_addr]);
  bedtime[args[0]->pr_addr] = 0;
}

END
{
  printa("%s sleeping on %s:\n%@d\n", @);
}

デスクトップ・システムで前述の例のスクリプトを数秒間実行すると、出力の末尾は次のようになります。

# dtrace -s whofor.d
                        ^C
...
Xorg sleeping on metacity:

           value  ------------- Distribution ------------- count    
           65536 |                                         0        
          131072 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 2        
          262144 |                                         0        

gnome-power-man sleeping on Xorg:

           value  ------------- Distribution ------------- count    
          131072 |                                         0        
          262144 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ 1        
          524288 |                                         0
...
preemptプローブとremain-cpuプローブ

Oracle Linuxはプリエンプティブ・システムであるため、優先度の高いスレッドがそれより優先度の低いスレッドに割り込みます。優先使用により、優先度の低いスレッドで大きな待機時間のバブルが発生することがあります。そのため、他のスレッドよりも優先度が低いスレッドを知る必要がある場合があります。

次の例では、preemptプローブとremain-cpuプローブを使用してこの情報を表示します。次のソース・コードを入力し、whopreempt.dという名前のファイルに保存します。

#pragma D option quiet

sched:::preempt
{
  self->preempt = 1;
}

sched:::remain-cpu
/self->preempt/
{
  self->preempt = 0;
}

sched:::off-cpu
/self->preempt/
{
  /*
   * If we were told to preempt ourselves, see who we ended up giving
   * the CPU to.
   */
  @[stringof(args[1]->pr_fname), args[0]->pr_pri, execname,
    curlwpsinfo->pr_pri] = count();
  self->preempt = 0;
}

END
{
  printf("%30s %3s %30s %3s %5s\n", "PREEMPTOR", "PRI",
    "PREEMPTED", "PRI", "#");
  printa("%30s %3d %30s %3d %5@d\n", @);
}

デスクトップ・システムで前述のスクリプトを数秒間実行すると、次のような出力が得られます。

# dtrace -s whopreempt.d
^C
                     PREEMPTOR PRI                      PREEMPTED PRI     #
                       firefox 120                    kworker/0:0 120     1
                   gnome-panel 120                        swapper 120     1
                   gnome-panel 120                    wnck-applet 120     1
                   jbd2/dm-0-8 120                        swapper 120     1
                    khugepaged 139                    kworker/0:0 120     1
                   ksoftirqd/1 120                    kworker/0:0 120     1
                   kworker/0:0 120                 gnome-terminal 120     1
                   kworker/0:2 120                           Xorg 120     1
                   kworker/0:2 120                           java 120     1
                   kworker/1:0 120                           Xorg 120     1
                      nautilus 120                           Xorg 120     1
                  rtkit-daemon   0                   rtkit-daemon 120     1
                  rtkit-daemon 120                        swapper 120     1
                    watchdog/0   0                        swapper 120     1
                    watchdog/1   0                    kworker/0:0 120     1
                   wnck-applet 120                           Xorg 120     1
                   wnck-applet 120                        swapper 120     1
                     automount 120                    kworker/0:0 120     2
               gnome-power-man 120                    kworker/0:0 120     2
                   kworker/0:0 120                        swapper 120     2
                   kworker/1:0 120                         dtrace 120     2
                      metacity 120                    kworker/0:0 120     2
               notification-da 120                        swapper 120     2
                 udisks-daemon 120                    kworker/0:0 120     2
                     automount 120                        swapper 120     3
                   gnome-panel 120                           Xorg 120     3
               gnome-settings- 120                           Xorg 120     3
               gnome-settings- 120                        swapper 120     3
                gnome-terminal 120                        swapper 120     3
                          java 120                    kworker/0:0 120     3
                   ksoftirqd/0 120                        swapper 120     3
                   kworker/0:2 120                        swapper 120     3
                      metacity 120                           Xorg 120     3
                      nautilus 120                    kworker/0:0 120     3
                         qpidd 120                        swapper 120     3
                      metacity 120                        swapper 120     4
               gvfs-afc-volume 120                        swapper 120     5
                          java 120                           Xorg 120     5
               notification-da 120                           Xorg 120     5
               notification-da 120                    kworker/0:0 120     5
                          Xorg 120                    kworker/0:0 120     6
                   wnck-applet 120                    kworker/0:0 120    10
                   VBoxService 120                        swapper 120    13
                        dtrace 120                        swapper 120    14
                   kworker/1:0 120                    kworker/0:0 120    16
                        dtrace 120                    kworker/0:0 120    20
                          Xorg 120                        swapper 120    90
               hald-addon-inpu 120                        swapper 120   100
                          java 120                        swapper 120   108
                gnome-terminal 120                    kworker/0:0 120   110
tick

NOHZoffに設定すると、Oracle Linuxはクロック刻みベースのアカウンティングを使用します。この場合、システム・クロック割込みが固定間隔で起動し、CPU使用率はクロック刻みの時点で実行されていたプロセスによって決まります。次の例では、tickプローブを使用してこの属性を監視します。

# dtrace -n sched:::tick'{ @[stringof(args[1]->pr_fname)] = count() }'
dtrace: description 'sched:::tick' matched 1 probe
^C

  VBoxService                                                       1
  gpk-update-icon                                                   1
  hald-addon-inpu                                                   1
  jbd2/dm-0-8                                                       1
  automount                                                         2
  gnome-session                                                     2
  hald                                                              2
  gnome-power-man                                                   3
  ksoftirqd/0                                                       3
  kworker/0:2                                                       3
  notification-da                                                   4
  devkit-power-da                                                   6
  nautilus                                                          9
  dbus-daemon                                                      11
  gnome-panel                                                      11
  gnome-settings-                                                  11
  dtrace                                                           19
  khugepaged                                                       22
  metacity                                                         27
  kworker/0:0                                                      41
  swapper                                                          56
  firefox                                                          58
  wnck-applet                                                      61
  gnome-terminal                                                   67
  java                                                             84
  Xorg                                                            227

クロック刻みのアカウンティングには、アカウンティングを実行するシステム・クロックが原因で、時間に関係するスケジューリング・アクティビティがディスパッチされることがあるという短所があります。その結果、クロック刻みと同間隔で(つまり10ミリ秒ごとに)スレッドがなんらかの作業を実行する場合、時間に関係するスケジューリング・アクティビティをディスパッチした前後どちらでアカウンティングが行われるかに応じて、アカウンティングの結果が実際よりも上下します。アカウンティングが時間関係のディスパッチより前に実行される場合、定期的な間隔で実行されているスレッドは低くアカウンティングされます。このようなスレッドが、クロック間隔より短く実行された場合、クロック間隔の裏に隠すことができます。

次の例では、システムにこのようなスレッドがあるかどうかを確認します。次のソース・コードを入力し、tick.dという名前のファイルに保存します。

sched:::tick,
sched:::enqueue
{
  @[probename] = lquantize((timestamp / 1000000) % 10, 0, 10);
}

この例のスクリプトを実行した出力結果を見ると、10ミリ秒の間隔でミリ秒のオフセットが2つ存在することがわかります。1つはtickプローブのオフセット、もう1つはenqueueのオフセットです。

# dtrace -s tick.d
dtrace: script 'tick.d' matched 9 probes
^C

  tick                                              
           value  ------------- Distribution ------------- count    
             < 0 |                                         0        
               0 |@@@@@                                    29       
               1 |@@@@@@@@@@@@@@@@@@@                      106      
               2 |@@@@@                                    27       
               3 |@                                        7        
               4 |@@                                       10       
               5 |@@                                       12       
               6 |@                                        4        
               7 |@                                        8        
               8 |@@                                       9        
               9 |@@@                                      17       
           >= 10 |                                         0        

  enqueue                                           
           value  ------------- Distribution ------------- count    
             < 0 |                                         0        
               0 |@@@@                                     82       
               1 |@@@@                                     86       
               2 |@@@@                                     76       
               3 |@@@                                      65       
               4 |@@@@@                                    101      
               5 |@@@@                                     79       
               6 |@@@@                                     75       
               7 |@@@@                                     76       
               8 |@@@@                                     89       
               9 |@@@@                                     75       
           >= 10 |                                         0 

tickという出力ヒストグラムからは、クロック刻みが1ミリ秒のオフセットで起動していることがわかります。この例では、enqueueの出力が10ミリ秒間隔で均等に配置されており、1ミリ秒間隔のスパイクは見つからないため、スレッドは時間ベースでスケジュールされていないと判断されます。

schedの安定性

schedプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

ISA

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

発展中

発展中

ISA

引数

発展中

発展中

ISA

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。

ioプロバイダ

ioプロバイダは、ディスク入出力に関連したプローブを使用可能にします。ioプロバイダでは、iostatなどのI/O監視ツールを通じて監視された動作を迅速に調査できます。たとえばioプロバイダを使用して、デバイス、I/Oの種類、I/Oサイズ、プロセス、アプリケーション名を基準にI/Oを確認できます。

ioプローブ

次の表では、ioプロバイダのプローブについて説明します。

表11-9 ioプローブ

プローブ 説明

start

周辺機器またはNFSサーバーに対してI/Oリクエストが発行される直前に起動します。

done

I/Oリクエストが満たされた後に起動します。doneプローブは、I/Oが完了してから完了処理がバッファ上で実行されるまでの間に起動します。したがって、doneプローブが起動した時点では、b_flagsB_DONEは設定されません。

wait-start

特定のI/Oリクエストの完了を待機してスレッドが保留状態に入る直前に起動します。wait-startプローブの起動後しばらくすると、同じスレッドでwait-doneプローブが起動します。

wait-done

スレッドがI/Oリクエストの完了待機状態を終了したときに起動します。wait-doneプローブは常に、同じスレッドでwait-startプローブが起動した後に起動します。

ioプローブは、周辺機器に対するI/Oリクエストが発行されたときと、NFSサーバーに対するファイルの読取り/書込みリクエストが発行されたときに起動します。たとえば、NFSサーバーに対してメタデータのリクエストが発行されても、readdir()リクエストのためioプローブがトリガーされることはありません。

ioプローブの引数

次の表では、ioプローブの引数について説明します。

表11-10 ioプローブの引数

引数 タイプ 説明

args[0]

bufinfo_t *

対応するI/Oリクエストのbufinfo_t

args[1]

devinfo_t *

対応するI/Oリクエストのデバイスのdevinfo_t

args[2]

fileinfo_t *

対応するI/Oリクエストのファイルのfileinfo_t

ノート:

DTraceは、現在、ioプローブでfileinfo_tの使用をサポートしていません。Oracle Linuxでは、I/Oリクエストが発行されたファイルについて起動されたioプローブのレベルですぐにアクセスできる情報はありません。

bufinfo_t

bufinfo_t構造体は、I/Oリクエストについて説明する抽象化オブジェクトです。I/Oリクエストに対応するバッファは、startdonewait-startwait-doneの各プローブのargs[0]によってポイントされます。このデータ構造体の詳細は、/usr/lib64/dtrace/version/io.dを参照してください。bufinfo_tの定義は、次のとおりです。

typedef struct bufinfo {
  int b_flags;         /* flags */
  size_t b_bcount;     /* number of bytes */
  caddr_t b_addr;      /* buffer address */
  uint64_t b_blkno;    /* expanded block # on device */
  uint64_t b_lblkno;   /* logical block # on device */
  size_t b_resid;      /* not supported */
  size_t b_bufsize;    /* size of allocated buffer */
  caddr_t b_iodone;    /* I/O completion routine */
  int b_error;         /* not supported */
  dev_t b_edev;        /* extended device */
} bufinfo_t;

ノート:

DTraceは、カーネル・バージョンに応じて、bufinfo_tのメンバーを、Oracle Linux I/Oリクエスト構造体のbuffer_headまたはbioから変換します。

b_flagsはI/Oバッファの状態を表し、ビット単位または各種の状態値で構成されます。次の表では、サポートされる状態の値について説明します。

表11-11 b_flagsの値

b_flags 説明

B_ASYNC

0x000400

I/Oリクエストが非同期であり、待機していないことを示します。wait-startプローブとwait-doneプローブは、非同期I/Oリクエストに対して起動しません。

ノート:

非同期となるよう指示された一部のI/Oでは、B_ASYNCが設定されない場合があります。非同期I/Oサブシステムは、別のワーカー・スレッドで同期I/O操作を実行することで、非同期リクエストを実装できます。

B_PAGEIO

0x000010

ページ化されたI/Oリクエストでバッファが使用されていることを示します。

B_PHYS

0x000020

ユーザー・データ領域に対する物理(直接) I/Oでバッファが使用されていることを示します。

B_READ

0x000040

データが周辺機器からメイン・メモリーへ読み込まれることを示します。

B_WRITE

0x000100

データがメイン・メモリーから周辺機器へ転送されることを示します。

b_bcount: I/Oリクエストの一部として転送されるバイト数です。

b_addr: 既知の場合、I/Oリクエストの仮想アドレスです。

b_blkno: デバイス上のどのブロックにアクセスするかを示します。

b_lblkno: デバイス上のどの論理ブロックにアクセスするかを示します。論理ブロックから物理ブロック(シリンダ、トラックなど)へのマッピングは、デバイスごとに定義されています。

b_bufsize: 割当て済バッファのサイズが格納されます。

b_iodone: I/Oの完了時にコールされるカーネル内の特定のルーチンを表します。

b_edev: アクセスされるデバイスのメジャー・デバイス番号とマイナー・デバイス番号が入ります。Dのサブルーチンgetmajorgetminorを使用すると、b_edevフィールドからメジャーおよびマイナーのデバイス番号を抽出できます。

devinfo_t

devinfo_t構造体は、デバイスに関する情報を提供します。I/Oの対象であるデバイスに対応するdevinfo_t構造体は、startdonewait-startwait-doneの各プローブのargs[1]によってポイントされます。このデータ構造体の詳細は、/usr/lib64/dtrace/version/io.dを参照してください。devinfo_tの定義は、次のとおりです。

typedef struct devinfo {
  int dev_major;           /* major number */
  int dev_minor;           /* minor number */
  int dev_instance;        /* not supported */
  string dev_name;         /* name of device */
  string dev_statname;     /* name of device + instance/minor */
  string dev_pathname;     /* pathname of device */
} devinfo_t;

ノート:

DTraceは、devinfo_tのメンバーを、Oracle Linux I/Oリクエスト構造体のbuffer_headから変換します。

dev_major: メジャー・デバイス番号です。

dev_minor: マイナー・デバイス番号です。

dev_name: デバイスを管理するデバイス・ドライバの名前です。

dev_statname: iostatによってレポートされるデバイスの名前です。このフィールドは、異常なiostatの出力を実際のI/Oアクティビティに迅速に対応付けるために提供されています。

dev_pathname: デバイスのフルパスです。dev_pathnameで指定されるパスには、デバイス・ノード、インスタンス番号およびマイナー・ノードを表す構成要素が含まれます。ただし、統計名にこれら3つの要素がすべて含まれているとはかぎりません。デバイスによっては、統計名がデバイス名とインスタンス番号で構成されている場合もあります。あるいは、デバイス名とマイナー・ノード番号で構成されている場合もあります。そのため、同じdev_statnameを持つ2つのデバイスでdev_pathnameが異なることもあります。

fileinfo_t

ノート:

DTraceは、現在、ioプローブのargs[2]引数でfileinfo_tの使用をサポートしていません。fileinfo_t構造体は、プロセスのオープン・ファイルに関する情報をfds[]配列を使用して取得するために使用できます。「組込み変数」を参照してください。

fileinfo_t構造体は、ファイルに関する情報を提供します。startdonewait-startwait-doneの各プローブのargs[2]は、I/Oリクエストが対応しているファイルをポイントします。ファイル情報が存在するかどうかは、I/Oリクエストをディスパッチするときにこの情報を提供するファイル・システムによって決まります。一部のファイル・システム、特にサードパーティのファイル・システムには、この情報を提供しないものもあります。また、I/Oリクエストの発行元であるファイル・システムにファイル情報が存在しない場合もあります。たとえば、ファイル・システムのメタデータに対するI/Oにはファイルが関連付けられていません。さらに、高度に最適化されたファイル・システムは、複数の無関係なファイルからのI/Oを集積して、単一のI/Oリクエストを生成することがあります。この場合、I/Oの大部分を表すファイルまたはI/Oの一部を表すファイルについてファイル情報が提供されることがあります。あるいは、ファイル・システムからファイル情報がまったく提供されない可能性もあります。

このデータ構造体の詳細は、/usr/lib64/dtrace/version/io.dを参照してください。fileinfo_tの定義は、次のとおりです。

typedef struct fileinfo {
  string fi_name;           /* name (basename of fi_pathname) */
  string fi_dirname;        /* directory (dirname of fi_pathname) */
  string fi_pathname;       /* full pathname */
  loff_t fi_offset;         /* offset within file */
  string fi_fs;             /* file system */
  string fi_mount;          /* not supported */
  int fi_oflags;            /* open() flags for file descriptor */
} fileinfo_t;

fi_nameフィールドには、ファイルの名前が含まれていますがディレクトリ・コンポーネントは含まれていません。ファイル情報がI/Oに関連付けられていない場合、fi_nameフィールドは文字列<none>に設定されます。まれに、ファイルに関連付けられたパス名が不明な場合があります。この場合、fi_nameフィールドは文字列<unknown>に設定されます。

fi_dirnameフィールドには、ファイル名のディレクトリ・コンポーネントのみが含まれています。fi_nameの場合と同じように、この文字列は、ファイル情報が存在しない場合は<none>、またはファイルに関連付けられたパス名が不明な場合は<unknown>に設定できます。

fi_pathnameフィールドには、ファイルに対するフル・パス名が含まれています。fi_nameの場合と同じように、この文字列は、ファイル情報が存在しない場合は<none>、またはファイルに関連付けられたパス名が不明な場合は<unknown>に設定できます。

fi_offsetフィールドには、ファイル内のオフセット、またはファイル情報が存在しないかオフセットがファイル・システムによって未指定の場合には-1が含まれています。

fi_fsフィールドには、ファイル・システムのタイプ、または情報がない場合は<none>,が含まれています。

fi_oflagsフィールドには、ファイルを開くときに指定されたフラグが含まれています。

ioの例

'次の例のスクリプトは、I/Oが発行されるたびにその情報を表示します。次のソース・コードを入力し、iosnoop.dという名前のファイルに保存します。

#pragma D option quiet

BEGIN
{
  printf("%10s %2s\n", "DEVICE", "RW");
}

io:::start
{
  printf("%10s %2s\n", args[1]->dev_statname,
  args[0]->b_flags & B_READ ? "R" : "W");
}

このスクリプトの出力結果は、次のようになります。

# dtrace -s ./iosnoop.d
    DEVICE RW
     dm-00  R
     dm-00  R
     dm-00  R
     dm-00  R
     dm-00  R
     dm-00  R
...

連想配列を使用して各I/Oの所要時間(ミリ秒)を追跡すると、次の例に示すように、この例のスクリプトがさらに複雑になります。

#pragma D option quiet

BEGIN
{
  printf("%10s %2s %7s\n", "DEVICE", "RW", "MS");
}

io:::start
{
  start[args[0]->b_edev, args[0]->b_blkno] = timestamp;
}

io:::done
/start[args[0]->b_edev, args[0]->b_blkno]/
{
  this->elapsed = timestamp - start[args[0]->b_edev, args[0]->b_blkno];
  printf("%10s %2s %3d.%03d\n", args[1]->dev_statname,
  args[0]->b_flags & B_READ ? "R" : "W",
  this->elapsed / 10000000, (this->elapsed / 1000) % 1000);
  start[args[0]->b_edev, args[0]->b_blkno] = 0;
}

変更したスクリプトでは、出力に「MS (ミリ秒)」列が追加されています。

次の例のように、デバイス、アプリケーション、プロセスID、転送されたバイトについて集積し、whoio.dという名前のファイルに保存できます。

#pragma D option quiet

io:::start
{
  @[args[1]->dev_statname, execname, pid] = sum(args[0]->b_bcount);
}

END
{
  printf("%10s %20s %10s %15s\n", "DEVICE", "APP", "PID", "BYTES");
  printa("%10s %20s %10d %15@d\n", @);
}

このスクリプトを数秒間実行すると、次のような出力が得られます。

# dtrace -s whoio.d
                     ^C
    DEVICE                  APP        PID           BYTES
     dm-00               evince      14759           16384
     dm-00          flush-252:0       1367           45056
     dm-00                 bash      14758          131072
     dm-00       gvfsd-metadata       2787          135168
     dm-00               evince      14758          139264
     dm-00               evince      14338          151552
     dm-00          jbd2/dm-0-8        390          356352

デバイス間でデータをコピーする場合、いずれかのデバイスがコピーのリミッタとして機能するかどうかの確認が必要な場合があります。この問題に答えるには、各デバイスが転送する秒当たりのバイト数ではなく、各デバイスの有効なスループットを把握する必要があります。たとえば、次のスクリプトを使用して、copy.dという名前のファイルに保存して、スループットを確認できます。

#pragma D option quiet

io:::start
{
  start[args[0]->b_edev, args[0]->b_blkno] = timestamp;
}

io:::done
/start[args[0]->b_edev, args[0]->b_blkno]/
{
  /*
   * We want to get an idea of our throughput to this device in KB/sec.
   * What we have, however, is nanoseconds and bytes. That is we want
   * to calculate:
   *
   * bytes / 1024
   * ------------------------
   * nanoseconds / 1000000000
   *
   * But we cannot calculate this using integer arithmetic without losing
   * precision (the denominator, for one, is between 0 and 1 for nearly
   * all I/Os). So we restate the fraction, and cancel:
   *
   * bytes       1000000000      bytes       976562
   * --------- * ------------- = --------- * -------------
   * 1024        nanoseconds     1           nanoseconds
   *
   * This is easy to calculate using integer arithmetic.
   */
  this->elapsed = timestamp - start[args[0]->b_edev, args[0]->b_blkno];
  @[args[1]->dev_statname, args[1]->dev_pathname] =
    quantize((args[0]->b_bcount * 976562) / this->elapsed);
  start[args[0]->b_edev, args[0]->b_blkno] = 0;
}

END
{
  printa(" %s (%s)\n%@d\n", @);
}

ハード・ディスクからUSBドライブにデータをコピーするとき、前述のスクリプトを数秒間実行すると、次のような出力が得られます。

# dtrace -s copy.d
^C
sdc1 (/dev/sdc1)

           value  ------------- Distribution ------------- count    
              32 |                                         0
              64 |                                         3
             128 |                                         1
             256 |@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@  2257
             512 |                                         1
            1024 |                                         0       

 dm-00 (/dev/dm-00)

           value  ------------- Distribution ------------- count    
             128 |                                         0
             256 |                                         1
             512 |                                         0
            1024 |                                         2
            2048 |                                         0
            4096 |                                         2
            8192 |@@@@@@@@@@@@@@@@@@                       172
           16384 |@@@@@                                    52
           32768 |@@@@@@@@@@@                              108
           65536 |@@@                                      34
          131072 |                                         0     

前述の出力から、USBドライブ(sdc1)がデバイスのリミッタとして機能していることがわかります。sdc1のスループットは256K/秒から512K/秒の間で、dm-00は8MB/秒から64MB/秒超までのいずれかでI/Oを提供しています。

ioの安定性

ioプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

ISA

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

発展中

発展中

ISA

引数

発展中

発展中

ISA

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください

fasttrapプロバイダ

fasttrapプロバイダは、ユーザー空間スレッド内の任意の命令の動的なインストゥルメンテーションを実行します。他のほとんどのDTraceプロバイダとは異なり、fasttrapプロバイダはシステム・アクティビティのトレース用に設計されていません。かわりに、このプロバイダは、DTraceコンシューマがfasttrapプローブをアクティブ化して情報をDTraceフレームワークにインジェクトする方法として意図されています。

ユーザー空間プログラムでの静的定義プローブの有効化について、詳細は、「ユーザー・アプリケーションの静的定義トレース」を参照してください。

fasttrapプローブ

fasttrapプロバイダは、ユーザー・レベルのプロセスがカーネルへの特定のDTraceコールを行うたびに起動する単一のプローブを使用できるようにします。プローブをアクティブ化するためのDTraceコールは使用できません

fasttrapの安定性

fasttrapプロバイダは、DTraceの安定性メカニズムを使用してその安定性を記述します。これらの値を、次の表に示します。

要素 名前の安定性 データの安定性 依存クラス

プロバイダ

発展中

発展中

ISA

モジュール

非公開

非公開

不明

関数

非公開

非公開

不明

名前

発展中

発展中

ISA

引数

非公開

非公開

不明

安定性メカニズムの詳細は、「DTraceの安定性機能」を参照してください。