SystemTap はコマンドラインインターフェイスとスクリプト言語を提供し、稼働中の Linux システム、特にカーネルについて、詳しい動作を調査することができる ソフトウエアです。 SystemTap のスクリプトは SystemTap スクリプト言語で記述する 仕組みになっていて、それらは C 言語のコードとしてコンパイルされ、カーネル モジュールの形に構築されて組み込まれます。スクリプトはデータを取り出して フィルタを設定し、収集するようになっているため、複雑な性能問題や機能問題を 診断することができます。 SystemTap は netstat, ps, top, iostat などのツールが出力する のに似た形式で情報を提供しますが、収集された情報に対してフィルタや分析を 追加することができるという点が異なります。
SystemTap スクリプトを起動すると、 SystemTap セッションが開始されます。また、
スクリプトの実行が許可されるまでの間には、スクリプトをカーネルモジュールと
してコンパイルし、読み込まれる手順が行なわれます。以前に対象のスクリプトを
実行済みで、構成に変更がない場合 (構成にはたとえば、コンパイラのバージョンや
カーネルのバージョン、ライブラリパスやスクリプトの内容が含まれます) は、
SystemTap はコンパイルし直したりすることはせず、 SystemTap のキャッシュ
~/.systemtap
) 内に含まれる *.c
と
*.ko
を利用して実行します。読み込まれたモジュールは、
tap の実行が終わると読み込み解除されます。動作例として、
5.2項 「インストールと設定」 に書かれている
動作テストをお読みください。
SystemTap の使用は、主に SystemTap のスクリプト (*.stp
)
を使用することで行ないます。スクリプトでは SystemTap がどのような種類の情報を
収集し、その収集した情報に対して何かを行なうのかを指し示します。スクリプトは、
AWK や C 言語に似た SystemTap スクリプト言語で記述します。言語の定義について、
詳しくは http://sourceware.org/systemtap/langref/
をお読みください。
SystemTap スクリプトの主な考え方は、 イベント
に対して
命名を行ない、それらを ハンドラ
に渡す、という流れを
構築するというものです。 SystemTap がスクリプトを実行すると、特定のイベントに
対して監視を行ないます。イベントが発生すると、 Linux カーネルはサブルーチン
としてハンドラを動かし、元の実行に戻ります。そのため、イベントはハンドラを
動作させるためのトリガー (きっかけ) という位置づけになります。ハンドラでは、
指定したデータを指定した形式で記録または表示させることができます。
なお、 SystemTap 言語ではごく少数のデータ型 (整数、文字列、およびそれらの 連想配列) を使用し、完全な構造制御 (ブロック、条件分岐、ループ、関数) を 扱うことができます。また、簡易な文章構造 (セミコロンによる区切りは任意) に なっているほか、詳細な宣言を行なう必要はありません (データ型は自動的に 推測され、チェックされます) 。
SystemTap のスクリプトやそれらの文法について、詳しくは
5.3項 「スクリプトの文法」 のほか、
stapprobes や stapfuncs の
マニュアルページをお読みください。マニュアルページは、
systemtap-docs
パッケージに
含まれています。
tap セットとは事前に作成された探査用のライブラリで、 SystemTap スクリプト内
から使用することができる関数群を含んでいます。ユーザが SystemTap スクリプトを
実行すると、 SystemTap はスクリプトの探査イベントやハンドラが、 tap セット
ライブラリ内に存在するかどうかを確認します。その後 SystemTap はスクリプトが
C 言語のコードに変換される前に、それら必要な探査や関数を読み込みます。
SystemTap スクリプト自身と同様に、 tap セットのファイル名には
*.stp
という拡張子が付けられています。
ただし SystemTap スクリプトとは異なり、 tap セットは直接実行するためのもの ではありません。他のスクリプトから定義を引き出すための書庫として存在 しています。そのため tap セットライブラリは、イベントや関数を簡単に 作成できるようにするための抽象層と言えます。 tap セットはイベントを 指定したいユーザに対して、各種の便利な関数の別名を規定しています。 たとえばバージョンごとに異なるような特定のカーネル関数に対して、 わかりやすい別名が用意されていたりします。
SystemTap でもっとも使用されるコマンドは、 stap と
staprun の 2 つです。これらは root
から実行するか、
もしくは stapdev
または
stapusr
のメンバーのユーザから
実行する必要があります。
SystemTap のフロントエンドです。 SystemTap のスクリプトを、ファイルや標準入力 から受け付けて実行することができます。スクリプトは C 言語のソースコード に変換されてコンパイルされ、実行中の Linux カーネル内にモジュールとして 読み込まれます。その後、必要なシステムトレースや探査機能が実施されます。
SystemTap のバックエンドです。 SystemTap のフロントエンドが提供したカーネル モジュールを、読み込んだり解放したりします。
それぞれのコマンドについてオプションの一覧を表示するには、コマンドに
--help
オプションを付けて実行してください。また、詳しい説明
については、それぞれ stap と staprun
のマニュアルページをお読みください。
また、 SystemTap を実行するだけのユーザに対して、 root
の権限を付与せずに
済ませる方法として、それぞれ下記に示す SystemTap 用のグループに所属させる方法が
あります。
これらはいずれも openSUSE の既定の設定では用意されていませんが、下記の
グループを作成して適切な権限を与えることで、上記を実現することができます。
stapdev
このグループのメンバーの場合、 stap コマンドを利用して
SystemTap のスクリプトを実行することができるほか、 staprun
コマンドで SystemTap の仲介モジュールを実行することができます。
stap の実行には、カーネルモジュールのコンパイルや
カーネルへの読み込みも含まれることになりますので、実質的には root
の権限を持つことにもなります。
stapusr
このグループのメンバーの場合、 staprun を利用して
SystemTap の仲介モジュールを実行することだけが許可されます。これに加えて、
/lib/modules/
ディレクトリ以下にあるモジュールを実行することもできます。
このディレクトリは カーネルバージョン
/systemtap/root
が所有者に設定されていなければならない
もので、 root
だけが書き込むことができます。
下記の一覧では、 SystemTap における主なファイルとディレクトリについて、 概要を説明しています。
/lib/modules/カーネルバージョン
/systemtap/
SystemTap の仲介モジュールを保持しています。
/usr/share/systemtap/tapset/
tap セットの標準ライブラリを保持しています。
/usr/share/doc/packages/systemtap/examples
様々な目的で使用する SystemTap スクリプト例が多数収録されています。
このディレクトリは、 systemtap-docs
パッケージをインストールした場合のみ利用できます。
~/.systemtap/cache
SystemTap ファイルのキャッシュを保持するデータディレクトリです。
/tmp/stap*
SystemTap ファイルの一時ディレクトリです。 C 言語へ変換された後のファイルや、 カーネルオブジェクトなどが配置されます。
SystemTap はカーネルからの情報を
必要とするため、 SystemTap パッケージに加えていくつかのカーネル関連パッケージを
インストールしなければなりません。カーネル関連パッケージは SystemTap の探査対象の
カーネルバージョンや種類によって異なるものであるため、それらが正しく一致して
いるパッケージをインストールしてください (下記の表では *
としてバージョンや種類を示しています) 。
デバッグ情報付きパッケージのリポジトリ | |
---|---|
お使いのシステムでオンライン更新を利用している場合は、 openSUSE
13.1 用のオンラインインストールリポジトリ
|
従来型の利用形態の場合は、下記のパッケージをインストールしてください (YaST または zypper でインストールを行なうことが できます) 。
systemtap
systemtap-server
systemtap-docs
(任意)
kernel-*-base
kernel-*-debuginfo
kernel-*-devel
kernel-source-*
gcc
各種の目的でマニュアルページを読んだりサンプルの SystemTap スクリプト集を参照
したりしたい場合は、 systemtap-docs
パッケージをインストールしてください。
お使いのマシンに必要な全てのパッケージがインストールされていて、 SystemTap
を利用できる状態にあるかどうかを確認するには、 root
で下記のコマンドを
実行します。
stap -v -e 'probe vfs.read {printf("read performed\n"); exit()}'
上記のコマンドを実行するとスクリプトが起動し、現在使用しているカーネルについて チェックを行ないます。下記のように出力されれば、 SystemTap を利用する準備ができて いると判断できます:
Pass : parsed user script and 59 library script(s) in 80usr/0sys/214real ms. Pass : analyzed script: 1 probe(s), 11 function(s), 2 embed(s), 1 global(s) in 140usr/20sys/412real ms. Pass : translated to C into "/tmp/stapDwEk76/stap_1856e21ea1c246da85ad8c66b4338349_4970.c" in 160usr/0sys/408real ms. Pass : compiled C into "stap_1856e21ea1c246da85ad8c66b4338349_4970.ko" in 2030usr/360sys/10182real ms. Pass : starting run. read performed Pass : run completed in 10usr/20sys/257real ms.
| |
含まれているスクリプトに対して確認を行なっています。 | |
スクリプトを C 言語のソースコードに変換したあと、インストールされている
C コンパイラを実行して、ソースコードをコンパイルしています。それぞれ
生成されたソースコード ( | |
モジュールを読み込み、カーネルに接続することで全ての探査 (イベントと
ハンドラ) を有効にしています。調査するイベントは仮想ファイルシステム
(Virtual File System (VFS)) の読み込み処理です。このイベントはどのような
環境でも発生するものなので、正しいハンドラ ( | |
SystemTap セッションが完了した後は、探査が無効化されてカーネルモジュールの 読み込みが解除されます。 |
上記のテスト中に何らかのエラーメッセージが表示された場合は、表示されたメッセージを 手がかりにして必要なパッケージを確認し、それらが正しくインストールされているか どうかを確かめてください。場合によっては必要なカーネルを読み込むため、再起動を 行なう必要があるかもしれません。
SystemTap のスクリプトは下記の 2 つの要素から構成されます:
ハンドラを実行すべきカーネルイベントの名称。イベントとしては特定の 機能 (関数) の呼び出し部分や終了部分に設定することができるほか、タイマー による起動やセッションの起動/終了に設定することができます。
特定のイベントが発生したとき、何をすべきかを記述したスクリプト言語の 羅列。この部分は通常、イベントの状況に応じてデータを抽出し、それらを 内部の変数や結果表示などに保存する動作を行ないます。
イベントとそれに関連するハンドラは、まとめて 探査
と
呼ばれます。 SystemTap のイベントは 探査ポイント
とも呼ばれ、
そのイベントに対するハンドラは 探査体
とも呼ばれます。
SystemTap スクリプト内では、様々な方法で任意の場所にコメントを記述することが
できます: それぞれ #
, /* */
,
//
を使用して記述してください。
SystemTap スクリプトでは、複数の調査を記述することができます。調査は下記の 形式で記述されなければなりません:
probeイベント
{ステートメント
}
それぞれの調査には対応するステートメントブロックが存在します。この
ステートメントブロックは { }
で括られていなければ
ならず、イベントごとにステートメントが存在しなければなりません。
例5.1 シンプルな SystemTap スクリプト¶
下記はシンプルな SystemTap スクリプトの例です。
probe begin { printf ("hello world\n") exit () }
調査 (probe) の宣言です。 | |
イベント | |
ハンドラ定義の開始を | |
ハンドラ内で最初に使用されている関数 ( | |
| |
ハンドラ内で 2 番目に使用されている関数 ( | |
ハンドラ定義の終了を |
イベント begin
(SystemTap セッションの開始時) が発生すると、
{ }
内に書かれているハンドラが起動します。この場合は
printf
関数 が呼び出され、 hello world
という文字列と改行が出力 されます。出力が終わると、ハンドラは終了
します。
スクリプト内に複数のステートメントが記述されている場合、 SystemTap はこれらの ステートメントを書かれている順番に実行します。ステートメントの間には区切りと なる文字や終了を表わす文字を記述する必要はありません。また、ステートメントの ブロックは他のステートメントのブロック内にネストする (入れ子の形式にする) こともできます。一般的に SystemTap スクリプトで使用するステートメントブロックは、 C プログラミング言語でのそれと同じです。
SystemTap では数多くの内蔵イベントをしようすることができます。
一般的なイベントの書式は、ドットで区切って記述する形態です。このような仕組み
により、イベントを分類から絞り込むことができるようになっています。また、
それぞれのコンポーネント (イベントのパーツ) では、関数呼び出しのような形式で
文字列または数値のパラメータを指定することができるます。さらに、コンポーネント
には *
を指定することができます。これは該当する他の探査点に
展開される動作になります。それ以外にも、探査点に ?
を付加
して、展開に失敗した場合でもエラーとしない、任意指定のものとすることができます。
なお、探査点に対して !
を付加すると、任意指定かつ十分なもの
とすることができます。
SystemTap では探査点に対して複数のイベントを設定することができます。これらは
カンマ (,
) で区切って指定します。複数のイベントが
単一の探査に対して設定された場合、 SystemTap は指定したいずれかのイベントが
発生した際に実行を行ないます。
一般的には、イベントは下記のような分類に分けることができます:
同期イベント: 任意のプロセスが、カーネルコード内で特定の場所にある命令を 実行した場合に発生するイベント。追加の関連データとして、他のイベントに 対する参照ポイント (命令のアドレス) が設定される場合があります。
同期イベントには、たとえば
vfs.
などが
あります: 仮想ファイルシステム (Virtual File System (VFS)) に対して、
ファイル操作
ファイル操作
で指定するイベントを指定します。
たとえば 5.2項 「インストールと設定」 の場合、
read
が VFS で使用されるイベントです。
非同期イベント: 特定の命令やコード内での場所に結びつかないものを指します。 たとえばカウンタによるものやタイマーによるものなどがあげられます。
非同期イベントとしては、たとえば begin
などがあります。
これは SystemTap セッションの開始を意味するもので、 SystemTap スクリプトが
起動するとすぐに実行するものです。それ以外にも end
(SystemTap セッションの終了時) やタイマーのイベントなどがあります。
タイマーイベントは定期的に実行するためのハンドラで、たとえば
timer.s(
や
秒
)timer.ms(
などがあります。
ミリ秒
)
情報を収集するためのその他の探査とともに使用した場合、タイマーイベントを 利用することで、定期的にデータを表示させることができます。これにより、 一定時間ごとにどれだけの変化があったのかを確認することができます。
例5.2 タイマーイベントによる探査¶
たとえば、下記の探査では 「hello world」 というテキストを 4 秒おきに表示します:
probe timer.s(4) { printf("hello world\n") }
利用可能なイベントについて、詳しくは stapprobes の マニュアルページをお読みください。マニュアルページ内の See Also の項には、特定のサブシステムや コンポーネントに対応するイベントについて、他のマニュアルページへの リンクが書かれています。
Each SystemTap event is accompanied by a corresponding handler defined for that event, consisting of a statement block.
複数の探査で同じステートメント群を使用したい場合は、それらを簡単に
再利用することができます。 function
の後に名前を
記述することでそれらを関数として定義することができます。関数では
文字列や数値 (値渡し) のパラメータを必要なだけ作成することができ、
単一の文字列や数値を返却することができます。
function関数名
(パラメータ
) {ステートメント
} probeイベント
{関数名
(パラメータ
)}
上記の例において、 関数名
内にあるステートメントは、
イベント
で指定した探査が発生したタイミングで
実行されます。 パラメータ
は任意指定の値で、
関数に渡すパラメータを指定します。
関数はスクリプト内の任意の場所に定義することができます。
例5.1「シンプルな SystemTap スクリプト」 で既に紹介しているとおり、
とてもよく使用される関数のうちの 1 つが printf
関数です。
これは書式を指定してデータを出力する関数で、書式指定文字列を指定する
ことで、どのような形態で出力するのかを設定できます。書式指定文字列は
引用符で括って指定し、その中で %
文字で始まる詳細な
書式指定を行ないます。
書式指定文字列にどのように指定するのかは、表示対象のパラメータによって 異なります。書式指定文字列には複数の書式指定を設定することができ、それぞれ パラメータ 1 つに対応します。複数のパラメータはカンマで区切ります。
例5.3 書式指定付きの printf
関数¶
上記の例では、現在の実行名 (execname()
) を文字列で、
プロセス ID (pid()
) を整数で括弧内に表示したあと、
スペースで区切って open
という文字列を表示して
改行します:
[...] vmware-guestd(2206) open hald(2360) open [...]
例5.3「書式指定付きの printf
関数」 で使用している
execname()
と pid()
の 2 つの
関数以外にも、 printf
では様々な関数を使用する
ことができます。
SystemTap のスクリプトでもっともよく使用する関数は下記のとおりです:
現在のスレッドの ID
現在のスレッドのプロセス ID
現在のユーザ ID
現在の CPU 番号
現在のプロセスの名称
UNIX エポック (1970 年 1 月 1 日) からの経過秒数
時刻を文字列に変換したもの
現在処理している探査点の説明文字列
出力結果の体裁を整えるのに便利な関数です。この関数では各スレッド
(tid()
) に対して、インデントカウンタを保持する
動作を行ないます。この関数はインデント差分値と呼ばれる、スレッドの
インデントカウンタを増減させるための 1 つのパラメータを指定します。
関数の返り値は、インデントに必要な適切な数のスペースと、いくつかの
汎用的な追跡データを含む文字列です。汎用的な追跡データとしては、
タイムスタンプ (スレッドに対する最初のインデント処理からの経過
マイクロ秒数) のほか、プロセス名とスレッド ID 自身が含まれます。
これにより、呼び出された関数名や呼び出し元、およびスレッドの経過
時間を観測することができるようになっています。
関数の開始から終了までの間には通常、別の関数の呼び出しによる開始や
終了が含まれることから、何も行なわない状態ではそれらを識別するのが
難しくなってしまいます。このインデントカウンタでは、
別の 関数が呼び出された場合に値を増やし、そこ
から帰ってきた段階で値を減らすことができるので、関数の呼び出しが
起こるたびに右にずらす表示を行ない、関数の開始と終了を見分けやすく
する効果があります。 thread_indent()
を使用する
SystemTap スクリプトの例については、
http://sourceware.org/systemtap/tutorial/Tracing.html#fig:socket-trace
にある SystemTap Tutorial をお読みください。
利用可能な SystemTap 関数について、詳しくは stapfuncs のマニュアルページをお読みください。
関数とは別に、 SystemTap のハンドラではその他の一般的な構造を利用すること
ができます。たとえば変数や条件分岐 (if
/
else
) 、ループ (while
,
for
) 、配列やコマンドラインパラメータなどがあります。
変数はスクリプト内の任意の場所で定義することができます。変数を定義する には、単純に名前を指定して関数の値や数式を代入してください:
foo = gettimeofday( )
すると、変数を数式などで使用することができるようになります。変数に
代入した値の種類によって、 SystemTap は自動的にデータ型を推測 (文字列または
数値) します。何らかの矛盾が発生すると、それらはエラーとして報告されます。
上記の例では foo
は自動的に数値として解釈され、
printf()
で整数の書式を指定 (%d
)
することで表示できるようになります。
ただし、既定の設定では変数は、それが使用されている探査内ローカルの
存在です。つまり、変数はそれぞれのハンドラ内で初期化されて使用され、
そして廃棄されます。変数を探査の間で共有するには、スクリプト内で
その変数をグローバル変数として宣言する必要があります。これを行なう
には、探査の外側で global
キーワードを使用します:
例5.4 グローバル変数の使用
global count_jiffies, count_ms probe timer.jiffies(100) { count_jiffies ++ } probe timer.ms(100) { count_ms ++ } probe timer.ms(12345) { hz=(1000*count_jiffies) / count_ms printf ("jiffies:ms ratio %d:%d => CONFIG_HZ=%d\n", count_jiffies, count_ms, hz) exit () }
上記のサンプルスクリプトでは、カーネルの CONFIG_HZ の設定をタイマーで
計算するもので、それぞれカーネルの時分割値 (jiffies) とミリ秒を数えて
います (jiffy/jiffies はシステムのタイマー割り込みの 1 カウント分を
意味する値です。ハードウエアプラットフォーム側で決める割り込み周期に
依存するため、プラットフォームごとに異なる値になります) 。
global
ステートメントを使用することで count_jiffies
と count_ms
の変数をグローバル変数としていて、
timer.ms(12345)
の中でも使用できるようになっています。
なお、 ++
は指定した変数の値に 1
を加える、という意味です。
SystemTap スクリプト内で使用できる条件分岐には、いろいろなものがあります。 よく使用されるものを下記に示します:
下記のような書式で記述します:
if (条件
)ステートメント_1
elseステートメント_2
if
ステートメントは整数値を 0 と比較します。
条件演算子
が 0 以外の場合、最初のステートメント
が実行されます。 0 の場合は 2 番目のステートメント
が実行されます。 else の指定
(
および
)
は任意で、指定しなくてもかまいません。また、
と
は、いずれもステートメントブロックを記述することができます。
下記のような書式で記述します:
while (条件
)ステートメント
条件
で指定した値が 0 以外である限り、
で指定したステートメントを繰り返し実行します。なお、
はステートメントブロックを記述することもできます。
条件
の値は何回かの繰り返しの後に
0 にならなければなりません。
基本的には while
に対するショートカット機能で、
下記のような書式で記述します:
for (初期化
;条件
;増加表現
) statement
で指定した表現は繰り返し処理の初期化の際に使用するもので、処理が 実際に行なわれる前に実行される項目です。実行は で指定した条件が満たされなくなるまで繰り返し行なわれます (この表現は 各繰り返し処理の冒頭で行なわれます) 。また、 で指定した表現は、ループカウンタの加算に使用します。この表現は各 繰り返し処理の終わりで行なわれます。
条件演算子としては下記のものを使用することができます:
==: 左辺と右辺が等しい
!=: 左辺と右辺が等しくない
>=: 左辺のほうが右辺より大きいか、等しい
<=: 左辺のほうが右辺より小さいか、等しい
systemtap-docs
パッケージを
インストールしていれば、 /usr/share/doc/packages/systemtap/examples
内に各種の便利な SystemTap スクリプト例が配置されます。
この章では、
/usr/share/doc/packages/systemtap/examples/network/tcp_connections.stp
にあるサンプルのスクリプトについて、より詳しく説明します。
例5.5 tcp_connections.stp
を利用した着信 TCP の監視
#! /usr/bin/env stap probe begin { printf("%6s %16s %6s %6s %16s\n", "UID", "CMD", "PID", "PORT", "IP_SOURCE") } probe kernel.function("tcp_accept").return?, kernel.function("inet_csk_accept").return? { sock = $return if (sock != 0) printf("%6d %16s %6d %6d %16s\n", uid(), execname(), pid(), inet_get_local_port(sock), inet_get_ip_source(sock)) }
この SystemTap スクリプトは着信側の TCP 接続をリアルタイムに監視し、許可されて いない接続や望まない接続が発生しているかどうかを確認します。それぞれ TCP の接続がコンピュータ側に受け入れられると、下記のような情報が表示されます:
ユーザ ID (UID
)
接続を受け入れているコマンド (CMD
)
コマンドのプロセス ID (PID
)
接続が使用しているポート (PORT
)
TCP の接続元 IP アドレス (IP_SOUCE
)
スクリプトを実行するには、下記のように入力します:
stap /usr/share/doc/packages/systemtap/examples/network/tcp_connections.stp
あとはスクリプトの実行結果が出力されます。スクリプトを停止させるには、 Ctrl+C を押します。
この章では、 SystemTap についての概要を説明してきました。 SystemTap について より詳しい情報を得るには、下記のリンクを参照してください:
SystemTap プロジェクトの Web ページです。
SystemTap に関する便利な情報が数多く記載されています。レビューや他のツール との比較、よくある質問 (FAQ) やヒントなど、ユーザ向けおよび開発者向けの ドキュメンテーションがあります。また、 SystemTap のスクリプト集も掲載され ているほか、サンプルや使用例、最新の議論やストーリーなどもあります。
SystemTap Tutorial (SystemTap チュートリアル), SystemTap Beginner's Guide (SystemTap 初心者向けガイド), Tapset Developer's Guide (SystemTap 開発者向けガイド), SystemTap Language Reference (SystemTap 言語リファレンス) が、それぞれ PDF と HTML の形式で公開されています。また、これに関連した マニュアルページも公開されています。
SystemTap の言語リファレンスや SystemTap のチュートリアルについては、パッケージを
インストールしたシステムであれば
/usr/share/doc/packages/systemtap
内にも存在します。
また、 SystemTap のサンプルスクリプトは上記の example
サブディレクトリにあります。