Edited at

Ruby の TracePoint について調べてみた

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


トレースするイベントを指定

tracenew では引数を渡すことでトレースするイベントを指出することが出来ます。

何も指定しない場合は全てのイベントがトレースされるのですが、引数でトレースするイベントの種類を指定することで、そのイベントだけがトレースされるようになります。

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 を使って色々コードを書いてみました。


参考サイト