Ruby の TracePoint について調べてみました。
Ruby 2.0 から導入された機能
TracePoint は Ruby 2.0 から使えるようになった機能です。様々なイベントをフックすることが出来ます。
トレースの開始方法
トレースの開始するには 2通りの方法があります。
1. trace
を利用して開始する
trace
を使用した場合はトレースが即座に有効状態になります。
TracePoint.trace do |tp|
p tp
end
返り値として TracePoint オブジェクトを返すので、トレースを無効にしたい場合は disable
を実行します。
trace = TracePoint.trace do |tp|
p tp
end
trace.enabled? #=> true
trace.disable
trace.enabled? #=> false
2. new
で TracePoint オブジェクトを作成して enable
で開始する
もう一つは new
で TracePoint オブジェクトを生成して enable
を実行する方法です。
trace = TracePoint.new do |tp|
p tp
end
trace.enable
disable
を使用するとトレースが終了します。(トレースが有効かどうかを調べるには disabled?
を使用する。
trace = TracePoint.new do |tp|
p tp
end
trace.enabled? #=> false
trace.enable
trace.enabled? #=> true
trace.disable
trace.enabled? #=> false
トレースするイベントを指定
trace
や new
では引数を渡すことでトレースするイベントを指出することが出来ます。
何も指定しない場合は全てのイベントがトレースされるのですが、引数でトレースするイベントの種類を指定することで、そのイベントだけがトレースされるようになります。
TracePoint.trace(:call) do |tp|
p [tp.self, tp.event, tp.method_id]
end
def say
'hello'
end
say
実行結果
[main, :call, :say]
トレース出来るイベントの種類
TracePoint では Ruby の様々なイベントを指定することが可能です。
イベント | 説明 |
---|---|
:line | 式の評価 |
:class | クラス定義、特異クラス定義、モジュール定義への突入 |
:end | クラス定義、特異クラス定義、モジュール定義の終了 |
:call | Ruby で記述されたメソッドの呼び出し |
:return | Ruby で記述されたメソッド呼び出しからのリターン |
:c_call | Cで記述されたメソッドの呼び出し |
:c_return | Cで記述されたメソッド呼び出しからのリターン |
:raise | 例外の発生 |
:b_call | ブロックの開始 |
:b_return | ブロックの終了 |
:thread_begin | スレッドの開始 |
:thread_end | スレッドの終了 |
:fiber_switch | コンテキストの切替時 |
:script_compiled | スクリプトが ISeq に変換された後 |
line
式の評価毎に発生するイベント。
TracePoint.trace(:line) do |tp|
puts "#{tp.lineno}: exec"
end
a = 1
b = 2
c = 3
実行結果
5: exec
6: exec
7: exec
class, end
class
はクラスやモジュールの定義の開始、 end
はクラスやモジュールの定義の終了の際に発生するイベントです。
TracePoint.trace(:class, :end) do |tp|
p [tp.lineno, tp.self, tp.event]
end
class A
def say
end
end
実行結果
[5, A, :class]
[8, A, :end]
self
を利用すると、どのクラス・モジュールが定義されたのかがわかります。
call, return
call
は Ruby で記述されたメソッドの呼び出し、 return
は Ruby で記述されたメソッドからのリターン時に呼び出されるイベントです。 method_id
を使用することで、どのメソッドから呼び出されたのかがわかります。
TracePoint.trace(:call, :return) do |tp|
p [tp.lineno, tp.event, tp.method_id]
end
def say
'hello'
end
say
実行結果
[5, :call, :say]
[7, :return, :say]
c_call, c_return
c_call
はC で記述されたメソッドの呼び出し、 c_return
はC で記述されたメソッド呼び出しからのリターン時に呼び出されるイベントです。
TracePoint.trace(:c_call, :c_return) do |tp|
p [tp.lineno, tp.event, tp.method_id]
end
'100'.to_i
実行結果
[1, :c_return, :trace]
[5, :c_call, :to_i]
[5, :c_return, :to_i]
raise
raise
は例外発生時に呼び出されるイベントです。発生した例外は raised_exception
で取得することが出来ます。
TracePoint.trace(:raise) do |tp|
p [tp.lineno, tp.event, tp.raised_exception]
end
raise 'error'
実行結果
[5, :raise, #<RuntimeError: error>]
Traceback (most recent call last):
raise.rb:5:in `<main>': error (RuntimeError)
b_call, b_return
b_call
はブロックの開始、 b_return
はブロックの終了時に呼び出されるイベントです。
TracePoint.trace(:b_call, :b_return) do |tp|
p [tp.lineno, tp.event]
end
[1, 2, 3].each do |n|
p n
end
実行結果
[5, :b_call]
1
[7, :b_return]
[5, :b_call]
2
[7, :b_return]
[5, :b_call]
3
[7, :b_return]
thread_begin, thread_end
thread_begin
はスレッドの開始、 thread_end
はスレッドの終了時に呼び出されるイベントです。
TracePoint.trace(:thread_begin, :thread_end) do |tp|
p tp.event
end
thr = Thread.new { puts 'Hello World!' }
thr.join
実行結果
:thread_begin
Hello World!
:thread_end
fiber_switch
fiber_switch
はコンテキストの切り替えが発生したタイミングに発生するイベントです。
TracePoint.trace(:fiber_switch) do |tp|
p [tp.lineno, tp.event]
end
f = Fiber.new do
n = 0
loop do
Fiber.yield(n)
n += 1
end
end
3.times do
p f.resume
end
実行結果
[0, :fiber_switch]
[15, :fiber_switch]
0
[9, :fiber_switch]
[15, :fiber_switch]
1
[9, :fiber_switch]
[15, :fiber_switch]
2
TracePoint で使用できるメソッド
TracePoint では様々なメソッドが使用できます
メソッド名 | 説明 | 備考 |
---|---|---|
binding | binding オブジェクトを返す | |
defined_class | メソッドを定義したクラスかモジュールを返す | |
disable | トレースを無効にする | |
enable | トレースを有効にする | |
enabled? | トレースが有効かどうかを調べる | |
event | 発生したイベントの種類を返す | |
inspect | self の状態を人間が読みやすいものにしてくれる | |
lineno | イベントが発生した行番号を返す | |
method_id | イベントが発生したメソッド名を返す | |
parameters | 引数の情報を返す | 2.6 から使用可能 |
path | イベントが発生したファイルのパスを返す | |
raised_exception | 発生した例外を返す | |
return_value | メソッドやブロックの戻り値を返す | |
self | イベントを発生させたオブジェクトを返す |
binding
binding オブジェクトを取得することが出来ます。
binding でさらに便利になる TracePoint
Binding オブジェクトで利用できるメソッドを使用することで、TracePoint がさらに強力になります。
メソッド名 | 説明 |
---|---|
eval | self をコンテキストとして渡された文字列を評価しその結果を返す |
local_variable_defined? | 引数で渡された名前の変数が定義されているかどうかを判定する |
local_variable_get | 引数で渡された名前の変数の値を取得する |
local_variable_set | 第一引数で指定した名前の変数に第二引数で渡された値を設定する |
local_variables | ローカル変数の一覧を取得します |
receiver | 保持するコンテキスト内での self を返します |
例) メソッドの引数の値をチェックする
TracePoint.trace(:call) do |tp|
puts "#{tp.method_id} : arguments"
tp.binding.local_variables.each do |name|
puts " #{name} = #{tp.binding.local_variable_get(name).inspect}"
end
end
def say(message)
puts message
end
say('hello')
実行結果
say : arguments
message = "hello"
hello
defined_class
メソッドを定義したクラスかモジュールを返します。
TracePoint.trace(:call) do |tp|
p tp.defined_class
end
class A
def say
'hi'
end
end
A.new.say
実行結果
A
disable
トレースを無効にします。
trace = TracePoint.trace { |tp| }
trace.enabled? #=> true
trace.disable
trace.enabled? #=> false
disable
にブロックを渡すことで、そのブロックの範囲だけトレースを無効にすることが出来ます。
trace = TracePoint.trace { |tp| }
trace.enabled? #=> true
trace.disable { }
trace.enabled? #=> true
enable
トレースを有効にします。
trace = TracePoint.new { |tp| }
trace.enabled? #=> false
trace.enable
trace.enabled? #=> true
enable
にブロックを渡すことで、そのブロックの範囲だけトレースを有効にすることが出来ます。
trace = TracePoint.new { |tp| }
trace.enabled? #=> false
trace.enable { }
trace.enabled? #=> false
enabled?
トレースが有効かどうかを返します。
トレースが有効のままだと遅くなる?
トレースが有効な状態はトレースが無効な状態に比べて実行速度が遅くなってしまうので、使う時はなるべく必要な範囲だけを有効にしたほうが良さそうです。
require 'benchmark'
trace = TracePoint.new { |tp| }
result1 = Benchmark.measure do
1000000.times { 1 + 1 }
end
result2 = Benchmark.measure do
trace.enable { 1000000.times { 1 + 1 } }
end
puts Benchmark::CAPTION
puts result1
puts result2
結果
user system total real
0.052800 0.000084 0.052884 ( 0.053058)
0.339369 0.000967 0.340336 ( 0.341383)
event
発生したイベントの種類を返します。
inspect
self の状態を人間が読みやすいものにしてくれます。
TracePoint.trace do |tp|
p tp
end
実行結果
#<TracePoint:c_return `trace'@inspect.rb:1>
lineno
イベントが発生した行番号を返します。 path
と一緒に使用することでどこで発生したイベントなのかを判別することが出来ます。
TracePoint.trace(:line) do |tp|
p tp.lineno
end
a = 1
b = 2
c = 3
実行結果
5
6
7
method_id
イベントが発生したメソッド名を返します。
TracePoint.trace(:call) do |tp|
p tp.method_id
end
class A
def say
'hi'
end
end
A.new.say
実行結果
:say
parameters
メソッドの引数の情報を取得します。帰ってくる値の形式は [引数の種類, 引数名]
の配列になっているので、引数の値を取得したい場合は別途 local_variable_get
などを使用する必要があります。
TracePoint.trace(:call) do |tp|
pp tp.parameters
tp.parameters.each do |type, name|
puts "parameter #{name}: #{tp.binding.local_variable_get(name).inspect}"
end
end
def foo(msg, name = 'bob'); end
foo('hello')
[[:req, :msg], [:opt, :name]]
parameter msg: "hello"
parameter name: "bob"
path
イベントが発生したファイルのパスを返します。
raised_exception
発生した例外を返します。 raise
イベント以外で使用した場合は RuntimeError
が発生します。
return_value
メソッドやブロックの返り値を取得することが出来ます。このイベントが利用出来るのは return
, c_return
, b_return
の 3つで、これ以外のイベントで使用すると RuntimeError
が発生します。
TracePoint.trace(:return) do |tp|
p tp.return_value
end
def message
'hi'
end
message
実行結果
"hi"
return_value を書き変える
この return_value
の値を破壊的に変更することで、メソッドの返り値を変更することが出来ます。
TracePoint.trace(:c_return) do |tp|
tp.return_value.downcase! if tp.method_id == :upcase
end
p 'Hello'.upcase
実行結果
"hello"
self
イベントを発生させたオブジェクトを返します。
TracePoint.trace(:call) do |tp|
p tp.self
p tp.binding.eval('self')
p tp.binding.receiver
end
class A
def say
end
end
A.new.say
実行結果
#<A:0x00007fa099020198>
#<A:0x00007fa099020198>
#<A:0x00007fa099020198>
サンプルコード
TracePoint を使って色々コードを書いてみました。