Created
December 31, 2012 04:49
-
-
Save anonymous/4417397 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
この記事は Ruby の Advent Calendar に参加しようとして用意しましたが、間に合わなかったものです。 | |
Ruby 2.0でdtrace対応が入りました。この機能はLinuxのsystemtapからもアクセス出来ます。でも、どこにもドキュメントがないのでいっちょ使い方を解説しようという、そういう趣旨の記事です。 | |
まず、基本の使い方ですが、以下の様に | |
process(プロセス名).provider("ruby").mark(Rubyで定義されてるprobe名) で引っ掛けるイベント名を | |
記述して、tapscriptを記述します | |
ruby.stp | |
<blockquote><pre>probe process("./ruby").provider("ruby").mark("find__require__entry") | |
{ | |
printf("%s %s %d\n", user_string($arg1), user_string($arg2), $arg3) | |
}</pre></blockquote> | |
test.rb | |
<blockquote><pre>require "thread"</pre></blockquote> | |
んで、 sudo stap TAPスクリプト -c 任意のコマンド として動かします。 | |
(コマンドは全体を""で括らないとおかしくなるので注意) | |
<blockquote><p>$ sudo stap ../ruby.stp -c "./ruby --disable-gems ../test.rb" | |
enc/encdb.so ./ruby 0 | |
enc/encdb.so <internal:enc/prelude> 3 | |
enc/trans/transdb.so <internal:enc/prelude> 3 | |
rubygems.rb <internal:gem_prelude> 1 | |
rbconfig /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 98 | |
rubygems/compatibility /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 105 | |
rubygems/defaults /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 107 | |
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 108 | |
rubygems/errors /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 109 | |
rubygems/specification /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1076 | |
rubygems/version /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 39 | |
rubygems/requirement /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 40 | |
rubygems/version /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/requirement.rb 11 | |
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/requirement.rb 12 | |
rubygems/platform /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 41 | |
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/platform.rb 1 | |
rubygems/deprecate /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/specification.rb 42 | |
rubygems/exceptions /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1079 | |
rubygems/defaults/operating_system /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1088 | |
rubygems/defaults/ruby /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1097 | |
rubygems/core_ext/kernel_gem /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1107 | |
rubygems/core_ext/kernel_require /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems.rb 1108 | |
thread /home/kosaki/local/ruby-trunk/lib/ruby/2.0.0/rubygems/core_ext/kernel_require.rb 45 | |
</p></blockquote> | |
おおー。見事に preludeコードから暗黙で呼ばれているencdb, transdbも含めてrequire関係がとれましたね。 | |
では、使用出来るprobeはどこを見ればいいかというと、見るべきところは2つあって、1つはrubyソースコードのprobe.d、なにせコンパイル時に実際に使ってる定義だから嘘がない。もう1つは RubyのBTSにある wiki情報で簡単な解説つき。<a href="http://bugs.ruby-lang.org/projects/ruby/wiki/DTraceProbes" target="_blank" title="http://bugs.ruby-lang.org/projects/ruby/wiki/DTraceProbes">http://bugs.ruby-lang.org/projects/ruby/wiki/DTraceProbes</a> | |
さて、ここでこの記事を終わらせてしまってもいいのですが、もうちょっと頑張りましょう。このままではちょっと使いにくいですよね。 | |
まず、すぐ気づくダメな点は probe.d に引数の情報がないので、見ても参考にならなさすぎなのです。こんな感じ | |
<a href="https://github.com/ruby/ruby/blob/afb02bbe92e55f877d50ed8705c95a41d541458d/probes.d" target="_blank" title="https://github.com/ruby/ruby/blob/afb02bbe92e55f877d50ed8705c95a41d541458d/probes.d">https://github.com/ruby/ruby/blob/afb02bbe92e55f877d50ed8705c95a41d541458d/probes.d</a> | |
<blockquote><pre>#include "vm_opts.h" | |
provider ruby { | |
probe method__entry(const char *, const char *, const char *, int); | |
probe method__return(const char *, const char *, const char *, int); | |
probe cmethod__entry(const char *, const char *, const char *, int); | |
probe cmethod__return(const char *, const char *, const char *, int); | |
probe require__entry(const char *, const char *, int); | |
probe require__return(const char *); | |
probe find__require__entry(const char *, const char *, int); | |
probe find__require__return(const char *, const char *, int); | |
probe load__entry(const char *, const char *, int); | |
probe load__return(const char *); | |
probe raise(const char *, const char *, int); | |
probe object__create(const char *, const char *, int); | |
probe array__create(long, const char *, int); | |
probe hash__create(long, const char *, int); | |
probe string__create(long, const char *, int); | |
probe parse__begin(const char *, int); | |
probe parse__end(const char *, int); | |
#if VM_COLLECT_USAGE_DETAILS | |
probe insn(const char *); | |
probe insn__operand(const char *, const char *); | |
#endif | |
probe gc__mark__begin(); | |
probe gc__mark__end(); | |
probe gc__sweep__begin(); | |
probe gc__sweep__end(); | |
}; | |
#pragma D attributes Stable/Evolving/Common provider ruby provider | |
#pragma D attributes Stable/Evolving/Common provider ruby module | |
#pragma D attributes Stable/Evolving/Common provider ruby function | |
#pragma D attributes Evolving/Evolving/Common provider ruby name | |
#pragma D attributes Evolving/Evolving/Common provider ruby args</pre></blockquote> | |
実はDTraceの仕様としては引数名は書けるけども無視されるという仕様なので、書いてしまいましょう。 | |
こんな感じ。 | |
<blockquote><pre>#include "vm_opts.h" | |
provider ruby { | |
probe method__entry(const char *classname, const char *methodname, const char *sourcefile, int sourceline); | |
probe method__return(const char *classname, const char *methodname, const char *sourcefile, int sourceline); | |
probe cmethod__entry(const char *classname, const char *methodname, const char *sourcefile, int sourceline); | |
probe cmethod__return(const char *classname, const char *methodname, const char *sourcefile, int sourceline); | |
probe require__entry(const char *filename, const char *sourcefile, int sourceline); | |
probe require__return(const char *filename); | |
probe find__require__entry(const char *filename, const char *sourcefile, int sourceline); | |
probe find__require__return(const char *filename, const char *sourcefile, int sourceline); | |
probe load__entry(const char *filename, const char *sourcefile, int sourceline); | |
probe load__return(const char *filename); | |
probe raise(const char *classname, const char *sourcefile, int sourceline); | |
probe object__create(const char *classname, const char *sourcefile, int sourceline); | |
probe array__create(long capacity, const char *sourcefile, int sourceline); | |
probe hash__create(long size, const char *sourcefile, int sourceline); | |
probe string__create(long length, const char *sourcefile, int sourceline); | |
probe parse__begin(const char *sourcefile, int sourceline); | |
probe parse__end(const char *sourcefile, int sourceline); | |
#if VM_COLLECT_USAGE_DETAILS | |
probe insn(const char *insns_name); | |
probe insn__operand(const char *val, const char *insns_name); | |
#endif | |
probe gc__mark__begin(); | |
probe gc__mark__end(); | |
probe gc__sweep__begin(); | |
probe gc__sweep__end(); | |
}; | |
#pragma D attributes Stable/Evolving/Common provider ruby provider | |
#pragma D attributes Stable/Evolving/Common provider ruby module | |
#pragma D attributes Stable/Evolving/Common provider ruby function | |
#pragma D attributes Evolving/Evolving/Common provider ruby name | |
#pragma D attributes Evolving/Evolving/Common provider ruby args</pre></blockquote> | |
github にもアップしといた。 | |
<a href="https://github.com/kosaki/ruby/blob/b93717702e3db5d9a6c900afbb61422ef4970f89/probes.d" target="_blank" title="https://github.com/kosaki/ruby/blob/b93717702e3db5d9a6c900afbb61422ef4970f89/probes.d">https://github.com/kosaki/ruby/blob/b93717702e3db5d9a6c900afbb61422ef4970f89/probes.d</a> | |
んで、ここまであれば、systemtapのライブラリ(tapsetといいます)が自動生成できます。こんな感じ | |
<blockquote><pre>#!/usr/bin/ruby | |
# -*- coding: us-ascii -*- | |
def set_argument (arg, nth) | |
# remove C style type info | |
arg.gsub!(/.+ (.+)/, '\1') | |
# use user_string if arg is a pointer | |
if (arg[0,1] == "*") | |
"#{arg[1, 9999]} = user_string($arg#{nth})" | |
else | |
"#{arg} = $arg#{nth}" | |
end | |
end | |
ruby_path = "./ruby" | |
text = ARGF.read | |
# remove preprocessor directives | |
text.gsub!(/^#.*$/, '') | |
# remove provider name | |
text.gsub!(/^provider ruby \{/, "") | |
text.gsub!(/^\};/, "") | |
# probename() | |
text.gsub!(/probe (.+)\( *\);/) { | |
probe_name = $1 | |
probe = <<-End | |
probe #{probe_name} = process("ruby").provider("ruby").mark("#{probe_name}") | |
{ | |
} | |
End | |
} | |
# probename(arg1) | |
text.gsub!(/ *probe (.+)\(([^,)]+)\);/) { | |
probe_name = $1 | |
arg1 = $2 | |
probe = <<-End | |
probe #{probe_name} = process("ruby").provider("ruby").mark("#{probe_name}") | |
{ | |
#{set_argument(arg1, 1)} | |
} | |
End | |
} | |
# probename(arg1, arg2) | |
text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+)\);/) { | |
probe_name = $1 | |
arg1 = $2 | |
arg2 = $3 | |
probe = <<-End | |
probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}") | |
{ | |
#{set_argument(arg1, 1)} | |
#{set_argument(arg2, 2)} | |
} | |
End | |
} | |
# probename(arg1, arg2, arg3) | |
text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+),([^,)]+)\);/) { | |
probe_name = $1 | |
arg1 = $2 | |
arg2 = $3 | |
arg3 = $4 | |
probe = <<-End | |
probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}") | |
{ | |
#{set_argument(arg1, 1)} | |
#{set_argument(arg2, 2)} | |
#{set_argument(arg3, 3)} | |
} | |
End | |
} | |
# probename(arg1, arg2, arg3, arg4) | |
text.gsub!(/ *probe (.+)\(([^,)]+),([^,)]+),([^,)]+),([^,)]+)\);/) { | |
probe_name = $1 | |
arg1 = $2 | |
arg2 = $3 | |
arg3 = $4 | |
arg4 = $5 | |
probe = <<-End | |
probe #{probe_name} = process("#{ruby_path}").provider("ruby").mark("#{probe_name}") | |
{ | |
#{set_argument(arg1, 1)} | |
#{set_argument(arg2, 2)} | |
#{set_argument(arg3, 3)} | |
#{set_argument(arg4, 4)} | |
} | |
End | |
} | |
print text | |
</pre></blockquote> | |
おなじくgithubにUpしといた | |
<a href="https://github.com/kosaki/ruby/commit/0d2653e849d5352d8287e97bce053c07f6e84384" target="_blank" title="https://github.com/kosaki/ruby/commit/0d2653e849d5352d8287e97bce053c07f6e84384">https://github.com/kosaki/ruby/commit/0d2653e849d5352d8287e97bce053c07f6e84384</a> | |
これで以下のようなライブラリが生成される | |
<blockquote><p> $ tool/gen_ruby_tapset.rb probes.d > ./ruby-tapset.stp </p></blockquote> | |
ruby-tapset.stp | |
<blockquote><pre> | |
probe method__entry = process("./ruby").provider("ruby").mark("method__entry") | |
{ | |
classname = user_string($arg1) | |
methodname = user_string($arg2) | |
sourcefile = user_string($arg3) | |
sourceline = $arg4 | |
} | |
probe method__return = process("./ruby").provider("ruby").mark("method__return") | |
{ | |
classname = user_string($arg1) | |
methodname = user_string($arg2) | |
sourcefile = user_string($arg3) | |
sourceline = $arg4 | |
} | |
probe cmethod__entry = process("./ruby").provider("ruby").mark("cmethod__entry") | |
{ | |
classname = user_string($arg1) | |
methodname = user_string($arg2) | |
sourcefile = user_string($arg3) | |
sourceline = $arg4 | |
} | |
probe cmethod__return = process("./ruby").provider("ruby").mark("cmethod__return") | |
{ | |
classname = user_string($arg1) | |
methodname = user_string($arg2) | |
sourcefile = user_string($arg3) | |
sourceline = $arg4 | |
} | |
probe require__entry = process("./ruby").provider("ruby").mark("require__entry") | |
{ | |
filename = user_string($arg1) | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe require__return = process("ruby").provider("ruby").mark("require__return") | |
{ | |
filename = user_string($arg1) | |
} | |
probe find__require__entry = process("./ruby").provider("ruby").mark("find__require__entry") | |
{ | |
filename = user_string($arg1) | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe find__require__return = process("./ruby").provider("ruby").mark("find__require__return") | |
{ | |
filename = user_string($arg1) | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe load__entry = process("./ruby").provider("ruby").mark("load__entry") | |
{ | |
filename = user_string($arg1) | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe load__return = process("ruby").provider("ruby").mark("load__return") | |
{ | |
filename = user_string($arg1) | |
} | |
probe raise = process("./ruby").provider("ruby").mark("raise") | |
{ | |
classname = user_string($arg1) | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe object__create = process("./ruby").provider("ruby").mark("object__create") | |
{ | |
classname = user_string($arg1) | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe array__create = process("./ruby").provider("ruby").mark("array__create") | |
{ | |
capacity = $arg1 | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe hash__create = process("./ruby").provider("ruby").mark("hash__create") | |
{ | |
size = $arg1 | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe string__create = process("./ruby").provider("ruby").mark("string__create") | |
{ | |
length = $arg1 | |
sourcefile = user_string($arg2) | |
sourceline = $arg3 | |
} | |
probe parse__begin = process("./ruby").provider("ruby").mark("parse__begin") | |
{ | |
sourcefile = user_string($arg1) | |
sourceline = $arg2 | |
} | |
probe parse__end = process("./ruby").provider("ruby").mark("parse__end") | |
{ | |
sourcefile = user_string($arg1) | |
sourceline = $arg2 | |
} | |
probe insn = process("ruby").provider("ruby").mark("insn") | |
{ | |
insns_name = user_string($arg1) | |
} | |
probe insn__operand = process("./ruby").provider("ruby").mark("insn__operand") | |
{ | |
val = user_string($arg1) | |
insns_name = user_string($arg2) | |
} | |
probe gc__mark__begin = process("ruby").provider("ruby").mark("gc__mark__begin") | |
{ | |
} | |
probe gc__mark__end = process("ruby").provider("ruby").mark("gc__mark__end") | |
{ | |
} | |
probe gc__sweep__begin = process("ruby").provider("ruby").mark("gc__sweep__begin") | |
{ | |
} | |
probe gc__sweep__end = process("ruby").provider("ruby").mark("gc__sweep__end") | |
{ | |
} | |
</pre></blockquote> | |
$arg1 みたいなくそ使いにくい引数を probes.d の記述にもとづいて sourcefile とか sourcelineとか | |
いった変数に変換してくれる。これによって最初のスクリプトは以下のようにも書けるようになる | |
<blockquote><p> sudo stap -I. ../ruby2.stp -c "./ruby ../test.rb" </p></blockquote> | |
-I はさきほど作った ruby-tapset.stp があるディレクトリを指定すること。SystemTapのデフォルトサーチパス | |
(普通は /usr/share/systemtap/tapset/とかそのへん)に入れてしまえば不要だけどおすすめしない。 | |
ruby2.stp | |
<blockquote><pre> | |
probe find__require__entry { | |
printf ("%s %s %d\n", filename, sourcefile, sourceline) | |
} | |
</pre></blockquote> | |
probe名からプロセス名とプロバイダ名が消え、引数アクセスもuser_string($arg1) といった記述が | |
消えているのが分かるだろうか。 | |
言い換えると、最初の例が2.0での書き方、最後の例が2.1での書き方(の候補として検討中のもの)というわけである。 | |
おしまい |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment