2008-03-19 [長年日記]
● [DTrace] Ruby DTrace
DTrace パッチ済みの Ruby に対しては、以下のプローブが DTrace から利用可能になる。
Ruby DTrace が提供するプローブ
| プローブ名 | 実行タイミング | arg0 | arg1 | arg2 | arg3 |
|---|---|---|---|---|---|
| function-entry | メソッド呼出開始 | クラス名 | メソッド名 | ソースファイル | 行番号 |
| function-return | メソッド呼出終了 | クラス名 | メソッド名 | ソースファイル | 行番号 |
| raise | 例外発生時 | クラス名 | ソースファイル名 | 行番号 | - |
| rescue | rescue実行時 | ソースファイル名 | 行番号 | - | - |
| line | コードの1行毎 | ソースファイル名 | 行番号 | - | - |
| gc-begin | GCの開始 | - | - | - | - |
| gc-end | GCの終了 | - | - | - | - |
| object-create-start | オブジェクト生成の開始 | Ruby型名 | ソースファイル名 | 行番号 | - |
| object-create-done | オブジェクト生成の終了 | Ruby型名 | ソースファイル名 | 行番号 | - |
| object-free | メモリ解放時 | Ruby型名 | - | - | - |
| ruby-probe | Rubyコード上での明示 | 任意の文字列 | 任意の文字列 | - | - |
function-{entry, return}, gc-{begin, end}, object-create-{start, done}, (fire)-{start, end}という名前の一貫性のなさが辛いので、この表はもう印刷して張っておきたい。
DTraceに関する情報
- [SUN] 概要 http://jp.sun.com/products/software/solaris/10/dtracehowto.html
- [SUN] 仕様 http://docs.sun.com/app/docs/doc/819-0395
- [SUN] 使用例(※1) http://sdc.sun.co.jp/solaris/solaris10/dtrace/guide.html
- Ruby DTrace 公式 https://dtrace.joyent.com/projects/ruby-dtrace/wiki/Ruby+DTrace
- Ruby DTrace 使用例 http://theexciter.com/articles/a-quick-stroll-through-dtrace
- [PDF] Ruby DTrace かんたん入門 (高井乙!) http://recompile.net/uploaded_files/ruby-dtrace.pdf
特別なプローブ(ruby-probe)
Ruby DTrace が提供する殆どのプローブは(DTraced) Ruby によって自動的に呼び出されるが、唯一「ruby-probe」だけはユーザから明示的に呼び出される。具体的には、Ruby コード上での Tracer.fire メソッド(※1)実行によって ruby-probe が発火される。同プローブには最大2個の引数(任意の文字列)を渡すことができ、fireメソッドにブロックが渡された場合は、around メソッドとしてプローブが実行される。
fireメソッドの挙動
| コード | 実行されるプローブ | arg0 | arg1 |
|---|---|---|---|
| Tracer.fire('name') | ruby-probe | 'name' | - |
| Tracer.fire('age', 14) | (TypeErrorが発生) | ||
| Tracer.fire('age', '14') | ruby-probe | 'age' | '14' |
| Tracer.fire('age', '14', 'ok') | (ArgumentErrorが発生) | ||
| Tracer.fire('query'){...} | ruby-probe | 'query-start' | - |
| (ブロック内がyield) | |||
| ruby-probe | 'query-end' | - |
例1: 例外発生の確認
| プローブ名 | 実行タイミング | arg0 | arg1 | arg2 | arg3 |
|---|---|---|---|---|---|
| raise | 例外発生時 | クラス名 | ソースファイル名 | 行番号 | - |
raise.d
|
|
# dtrace -s raise.d 1985 dtrace: script 'raise.d' matched 2 probes CPU ID FUNCTION:NAME 1 41395 rb_longjmp:raise NameError ((irb):2) 1 41395 rb_longjmp:raise NameError (/usr/local/lib/ruby/1.8/irb/workspace.rb:81) |
これは raise をプローブするだけの凄く単純な例だが、
- 事前に何も準備する必要がなく
- システムの不調を感じたときに
- その pid を指定するだけで
- エラーの発生箇所を外部から特定できる
という点で、既に十分実用的である。特に、例外を中途半端に rescue してログに「エラー」とだけ表示している場合のデバッグは非常にやっかいだが、そういう場合に、そのコードを修正することなく、それどころかアプリの停止すら必要なく、その発生箇所情報を取得できるのは非常に心強い。さらに、「pid の絞込み」部分の述語をなくすだけで、そのサーバの Ruby 監視ツールに早変わりする。(※1)
例2: メソッド別実行時間
| プローブ名 | 実行タイミング | arg0 | arg1 | arg2 | arg3 |
|---|---|---|---|---|---|
| function-entry | メソッド呼出開始 | クラス名 | メソッド名 | ソースファイル | 行番号 |
| function-return | メソッド呼出終了 | クラス名 | メソッド名 | ソースファイル | 行番号 |
method.d
#pragma D option quiet
ruby$1:::function-entry
{
self->ts = timestamp;
}
ruby$1:::function-return
/self->ts/
{
@[copyinstr(arg0), copyinstr(arg1)] = sum(timestamp - self->ts);
self->ts = 0;
}
END
{
printf("%-15s %-20s %s\n", "Class", "Method", "Time");
printa("%-15s %-20s %10@d\n", @);
} |
# dtrace -s method.d 2035 ... Array shift 1079362 String gsub 1198380 String to_s 1457360 Hash []= 1481531 Module constants 2108975 Hash key? 5629032 TCPServer accept 352271736 Object sleep 2019952588 |
実行結果は、Scaffold(Rails) の list アクションを実行したときの、各メソッド毎の実行時間(nsec)である。これにより、高速化を目指すならば Hash#key? 付近に手を入れることになるが、TCP接続の前では誤差になることもわかる。
例3: ActiveRecordのクエリ実行時間の計測
| プローブ名 | 実行タイミング | arg0 | arg1 | arg2 | arg3 |
|---|---|---|---|---|---|
| ruby-probe | Rubyコード上での明示 | 任意の文字列 | 任意の文字列 | - | - |
config/initializers/dtrace.rb
module DTracer
module QueryProbe
def self.included(base)
base.class_eval <<-EOS
alias_method :untraced_execute, :execute
def traced_execute(sql, name=nil)
Tracer.fire('query', sql) { untraced_execute(sql, name) }
end
alias_method :execute, :traced_execute
EOS
end
end
end
ActiveRecord::Base.connection.class.class_eval do
include DTracer::QueryProbe
end |
(参考: http://dev.joyent.com/projects/ruby-dtrace/browse/ruby/examples/rails/dtrace_plugin )
ar.d
|
|
# dtrace -s ar.d SELECT * FROM kids 413311 SELECT count(*) AS count_all FROM kids 462813 |
Rails 全体のパフォーマンスを計測するツールを作りたい。


GJ