Systems Performance: Enterprise and Cloudを読んでいく。
前回の続き。
※ Solarisに関する記述は省略しています。
4.3 DTrace
DTraceはプログラミング言語やツールを含む観測用のフレームワーク。
この節ではDTraceの基本である、動的・静的なトレーシング、probe, provider, D, action, variable, ワンライナーとスクリプティングをまとめている。
これらはこの後の章で、LinuxでもSolarisでも利用する。
DTraceはprobesと呼ばれる機構によって、ユーザ・カーネルレベルの両方のコードを観測することが出来る。 probesがヒットすると、任意のD言語によるactionsが実行される。 actionsはカウントイベントやタイムスタンプの記録計算、値の出力、データの集計などを行うことが出来る。 これらの処理はトレーシングを有効にした状態でリアルタイムに実行することができる。
DTraceがspa_sync()を実行する時間をトレーシングする例
他のトレーシングツールと異なる重要な点はDTraceはオーバーヘッドを極力減らしていることで、プロダクションセーフになっていることである。 これを実現している1つの方法は、CPUごとのカーネルバッファを利用していることである。 これにより、メモリの空間的局所性を高め、キャッシュミスを減らし、同期のためのロックを不要にしている。
DTraceは静的・動的なトレーシングの両方をサポートしている。
4.3.1 Static and Dynamic Tracing
静的・動的なトレーシングを理解する一つの方法は、ソースとCPU命令を調べることだ。
bio.cの例
DTRACW_IO1マクロはソースがコンパイルされる前に加えられた、static probeの例である。 ここに動的なトレーシングのコードは見つからないが、それは実行された後に加えられるからである。
bio.cの命令トレーシング結果の例
動的トレーシングを行うと、検出された命令にint命令が含まれていることがわかる。 これはsoft割り込みを発生させ、動的トレーシングを実行している様子である。 これは動的トレーシングを有効にした場合のみ現れるもので、有効にしていない時はオーバーヘッドがないのが特徴である。
4.3.2 Probes
DTraceのProbeは4つのタプルからなる。
provider: module: function: name
- provider : probeに関連するコレクション、ソフトウェア・ライブラリと似たようなもの
- module and function : 動的に生成され、probeするコードの場所を特定する
- name : このprobe自体の名前
これらを特定するために、ワイルドカード(*)を使うことができ、空欄にすることもワイルドカードとみなされる。
io:::start
上記のようにした場合、ioプロバイダからの全てを調査するstartプルーブとなる。
4.3.3 Providers
DTraceのプロバイダは、使用しているDTraceとOSのバージョンに依存するが大抵は以下が備わっている。
- syscall
- vminfo (vertual memory statistics)
- sysinfo
- profile
- sched (kernel scheduling events)
- proc (process-level events)
- io
- pid (user-level dynamic tracing)
- tcp
- ip
- fbt (kernel-level dynamic tracing)
これらに加えて、高級言語(Java, JavaScript, Perl, Python, ...)のprobeもある。 これらの大半は静的なトレーシングを利用して実装されているので、可能ならばこれらを動的トレーシングから利用するのが良い。
4.3.4 Arguments
Probeはargumentと呼ばれる変数を介してデータを提供することが出来る。 argumentの使い方はproviderに依存する。 例えば、syscallプロバイダはそれぞれのシステムコールへのエントリーを提供し、probeを返す。 これらは以下のargument変数に値をセットする。
- Entry :arg0...argN (システムコールへの引き数)
- Return : arg0 or arg1 (戻り値、errnoもセットされる)
それぞれのプロバイダがどういったargumentやreturnを必要とするかはドキュメントをみるように。
4.3.5 D Language
D言語はawk-likeでワンライナーやスクリプトとしても使える。
DTraceの宣言は以下の様に使える
probe_description /predicate/ {action}
action部分はprobeが発火した時に、セミコロンで区切った文を実行できる。 predicateはオプショナルなフィルタリング表現である。 例えば以下のように書ける。
proc:::exec-success /execname == "httpd"/ {trace(pid);}
4.3.6 Built-in Variables
Built-in変数は計算とpredicate(フィルタ)として使え、それ自身をactionによって出力させることも出来る。
表4.2にBuilt-in変数の代表例
4.3.7 Actions
代表的なactionを表4.3に示す。
表4.3
4.3.8 Variable Types
変数のタイプを表4.4に示す。
表4.4
thread-local変数はスレッドごとのスコープで、タイムスタンプなどのデータを受けられる。
clause-local変数は中間時点での計算に利用でき、actionが同じprobeに対して処理をしている時だけ有効である。
aggregationは特別な変数型で、CPUごとの計算を行い後から集計することが出来る。
aggretgationのためのactionを表4.5に示す。
表4.5
aggregationとhistogramアクションの例として、次の例ではread()システムコールの返すサイズを表示している。
4.3.9 One-Liners
DTraceは簡潔で強力なワンライナーを書けるようにしてくれる。以下はその例。
DTraceの例
この本の中で以降もさらなるDTraceのワンライナーを使う。
4.3.10 Scripting
DTraceのワンライナーはファイルに保存することも出来、更に長いプログラムを書くことも可能である。 例えばbitesize.dスクリプトはプロセス名毎のdiskI/Oを表示する
bitesize.dスクリプト
プログラムが#!から始まっているように、コマンドラインから実行することも出来る。
また、#pragmaから始まる行で標準の出力を抑制して、必要な出力のみを表示させている。
この結果の例を以下に示す。
bitesize.dの結果の例
4.3.11 Overheads
これまでにも述べてきたようにDTraceは命令のオーバーヘッドを、CPUごとのカーネルバッファで行うことで最小化している。 この他にも、カーネルスペースからユーザスペースに1秒に1回の頻度でまとめてデータを送ったり、システムの応答が遅くなった場合はトレースを中断するなどして、オーバーヘッドを落とし、安全性を高めている。 DTraceによるオーバーヘッドは、トレースする頻度や、集計のためのスクリプトの複雑度に依存する。 また、それらを保存するときにも発生するので、このことを考慮する必要がある。
4.3.12 Documentation and Resources
より詳細なドキュメントがDynamic Tracing Guideとしてオンライン上にある。 また、AppendixDに便利なDTraceワンライナー集がある。 スクリプトや戦略の参考として、DTrace+ Dynamic Tracing in Oracle Solaris, Mac OS X and FreeBSD[Gregg 11]などもある。
4.4 SystemTap
SystemTapもまた静的・動的なトレーシングが可能である。 これはLinuxでDTraceができなかった時にRed Hat, IBM, Intelといったチームで考案された。 DTraceと同じように、probeと呼ばれる命令を起点とした任意のactionの実行が可能になっている。 これらはリアルタイムで実行でき、コマンドラインからワンライナーやスクリプトとしても実行できる。
SystemTapはトレーシングのための他のカーネルのフレームワークに由来している。 static probesやkprobes, uprobesといったものは他のツール(perf, LTTng)でも使われている。
何年かして、SystemTapはDTraceと同じように良くなり、時には驚くこともあるが、安定性に問題があり、未熟な部分も多い。
しかし、DTraceも安定性に問題があることもあり、もしSystemTapを使いたいのであれば、本書のDTraceのサンプルの殆どはSystemTapに変換することが出来る。
Appendix Eに変換のガイドがある。
4.4.1 Probes
Probeの定義は括弧の中で引き数としてピリオドで区切って定義される。
幾つかのサンプルを示す。
- begin
- end
- syscall.read
- syscall.read.return
- kernel.function("sys_read")
- kernel.function("sys_read").return
- socket.send
- timer.ms(100)
- timer.profileprocess("a.out").statement("*@main.c:100")
多くのprobeは関連するbuilt-in変数を提供している。
4.4.2 Tapsets
probeと関連するものにtapsetがある。 probeの多くはprobe nameにtapset nameを含んでいる。
その例として以下がある。
tapsetはprobeの追加的なアクションとして使われることもある。
4.4.3 Actions and Built-ins
SystemTapは多くのactionやbuilt-in、プロセス名のためのexecname()や現在のプロセスIDを示すためのpid()などを提供する。
さらなる例はAppendix Eにリストにされている。
4.4.4 Examples
以下のサンプルではread()システムコールが返すサイズをワンライナーで取得する例である。 これはDTraceで以前に示したサンプルとの比較にもなる。
SystemTapでの例
4.4.5 Overheads
DTraceと同じようなオーバーヘッドがかかる。 (トレースする頻度やその時の処理内容、保存処理の量や複雑度に依存する。)
4.4.6 Documentation and Resources
オンラインドキュメントとmanページがある。
4.5 perf
Linux Performance Events(LPE)、または略してperf(!?)は幅広い領域のパフォーマンス監視のために進化してきた。 DTraceやSystemTapのような現在はリアルタイムでのプログラミングを可能にする機能はないものの、perfは静的・動的なトレーシング、プロファイリングを可能にしている。 また、stack trace, local variables, data typesを使える。 さらに、kernelのメインラインであるため、元々入っていることが多く使いやすい上、ユーザが必要とする質問の多くに応えることが出来る。
幾つかのトレーシングによるオーバーヘッドは、DTraceやSystemTapと同じである一方で、ユーザレベルへのデータの受け渡しはpost-processingによって行われるため、頻度によっては甚大なオーバーヘッドになることがある。
4.6 Observing Observability
全てのシステムにはバグが存在し、ドキュメントも同様である。 監視ツールに限ったことではないが、以下のことに気をつけるべきである。
- ツールは常に正しいわけではない
- manページは常に正しいわけではない
- メトリクスは常に完全というわけではない
- メトリクスは低機能なデザインかもしれない
もし複数の観測ツールで検知できる項目に重複があるならば、クロスチェックすることが出来る。 また、すでに負荷のわかっているものを使って、ツールが正しく動作しているかを試すことも効果がある。 逆にドキュメントにはバグが有るとされていても、ドキュメントが更新されておらず修復されている場合もある。 実際にはすべての項目についてダブルチェックするような余裕はないかもしれない、しかし、こうした状況があることを考慮に入れておくことも重要である。