|
|
|
|
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}という名前の一貫性のなさが辛いので、この表はもう印刷して張っておきたい。
Ruby DTrace が提供する殆どのプローブは(DTraced) Ruby によって自動的に呼び出されるが、唯一「ruby-probe」だけはユーザから明示的に呼び出される。具体的には、Ruby コード上での Tracer.fire メソッド(※1)実行によって ruby-probe が発火される。同プローブには最大2個の引数(任意の文字列)を渡すことができ、fireメソッドにブロックが渡された場合は、around メソッドとしてプローブが実行される。
| コード | 実行されるプローブ | 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 をプローブするだけの凄く単純な例だが、
という点で、既に十分実用的である。特に、例外を中途半端に rescue してログに「エラー」とだけ表示している場合のデバッグは非常にやっかいだが、そういう場合に、そのコードを修正することなく、それどころかアプリの停止すら必要なく、その発生箇所情報を取得できるのは非常に心強い。さらに、「pid の絞込み」部分の述語をなくすだけで、そのサーバの Ruby 監視ツールに早変わりする。(※1)
例2: メソッド別実行時間
| プローブ名 | 実行タイミング | arg0 | arg1 | arg2 | arg3 |
|---|---|---|---|---|---|
| function-entry | メソッド呼出開始 | クラス名 | メソッド名 | ソースファイル | 行番号 |
| function-return | メソッド呼出終了 | クラス名 | メソッド名 | ソースファイル | 行番号 |
#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コード上での明示 | 任意の文字列 | 任意の文字列 | - | - |
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 全体のパフォーマンスを計測するツールを作りたい。
| JRuby | Rails | Berryz | ℃-ute | エッグ | jQuery |
| 前 | 2008年 3月 |
次 | ||||
| 日 | 月 | 火 | 水 | 木 | 金 | 土 |
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 | |||||
GJ