Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

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

参考サイト

siman
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away