Ruby
 Computer >> コンピューター >  >> プログラミング >> Ruby

Rubyメソッドをスパイする方法!TracePointで実行をトレースしよう

Rubyには、TracePointクラスを使ってアクセスできる組み込みのトレーシングシステムが備わっています。このシステムを利用すると、メソッド呼び出し、新しいスレッドの開始、例外の発生などをトレースできます。

なぜこれを使いたくなるのでしょうか?

たとえば、特定のメソッドの実行過程を追跡したい場合に非常に便利です。その処理の中で他にどのメソッドが呼ばれているのか、どんな戻り値が返されているのかを、目で確認できるようになります。

それでは、いくつか具体例を見ていきましょう!

メソッド呼び出しをトレースする

多くの場合、TracePointでトレースしたいのはアプリケーション側のコードであり、組み込みメソッド(putsやsizeなど)ではないはずです。

そんなときはcallイベントを使います。

:

def the_method; other_method; end
def other_method; end

def start_trace
  trace =
  TracePoint.new(:call) { |tp| p [tp.path, tp.lineno, tp.event, tp.method_id] }

  trace.enable
  yield
  trace.disable
end

start_trace { the_method }

このコードは、ファイルパス・行番号・イベント名・メソッド名を出力します。

["test.rb", 1, :call, :the_method]
["test.rb", 2, :call, :other_method]

イベントを何も指定しなかった場合、Rubyはすべてのイベントに対してブロックを呼び出すため、出力が膨大になってしまいます。目的の情報に素早くたどり着くためにも、特定のイベントに絞ってトレースすることをおすすめします🙂

以下はTracePointの主なイベント一覧です。

イベント名 説明
call アプリケーションのメソッド呼び出し
c_call Cレベルのメソッド呼び出し(putsなど)
return メソッドからの復帰(戻り値や呼び出し深度のトレースに使用)
b_call ブロックの呼び出し
b_return ブロックからの復帰
raise 例外の発生
thread_begin 新しいスレッドの開始
thread_end スレッドの終了

TracePoint + Graphvizでコールグラフを可視化

特にフレームワークのコードでは、1つのメソッドが3つ以上のメソッドを呼び出すのは珍しくありません。そのため、TracePointのテキスト出力だけでは全体像を把握しづらいことがあります。

そこで私は、次のような視覚的なコールグラフを作成できるgemを作りました。

require 'visual_call_graph'

VisualCallGraph.trace { "Your method call here..." }

これを実行すると、結果がcall_graph.pngファイルとして生成されます。

Rubyメソッドをスパイする方法!TracePointで実行をトレースしよう

なお、これは静的解析ではなく、実際にメソッドを呼び出して計測している点に注意してください。

ファイルパスを表示する

「これらのメソッドがどこで定義されているのか知りたい」と思いませんか?

ご安心ください!各メソッド呼び出しのファイルパスを表示できるオプションも用意しています。

VisualCallGraph.trace(show_path: true) { Foo.aaa }

実行結果:

Rubyメソッドをスパイする方法!TracePointで実行をトレースしよう

巨大なコールグラフを見てみたい場合は、Railsのメソッドをトレースしてみてください😉

戻り値を取得する

冒頭で触れたとおり、戻り値も取得できます。

その場合はreturnイベントをトレースし、return_valueメソッドを使用します。

:

def the_method; "A" * 10; end

trace = TracePoint.new(:return) { |tp| puts "Return value for #{tp.method_id} is #{tp.return_value}." }

trace.enable
the_method
trace.disable

このコードは次のように出力します。

Return value for the_method is AAAAAAAAAA.

イベントは「先」に発生する

Redditにこんな質問が投稿されました。「次のコードでfooメソッドを呼び出したとき、"bar"という文字列が出力されないようにするにはどうすればよいか?」というものです。

class Thing
  def foo
    puts "foo"
    bar
  end

  def bar
    puts "bar"
  end
end

# your code here

t = Thing.new
t.foo

これを実現する方法はいくつもあります。モジュールをprependする、$stdoutをリダイレクトする、barメソッドを再定義するなどです。

もし自分ならこうするというアイデアが浮かんだら、ぜひコメントで教えてください!

しかし、その中でも特に興味深い回答がありました。TracePointクラスを使ったものです。

そのコードがこちら:

TracePoint.trace(:call) { |tp| exit if tp.method_id == :bar }

このコードは、barメソッドが呼ばれた時点でexitを実行し、プログラムを終了させることで文字列の出力を防ぎます。

実務のコードで使いたいテクニックではありませんが、TracePointについて重要な事実を1つ証明しています。それは、「イベントは実際に起きる前に発火する」ということです。

この仕組みを使って何らかのツールを作る予定があるなら、ぜひ覚えておきたい挙動ですね🙂

まとめ

この記事では、メソッド呼び出しや新規スレッドなどのイベントをトレースできるTracePointクラスについて学びました。デバッグツールとしてだけでなく、コードの探索や理解にも役立つ強力な機能です。

この記事が役に立ったら、ぜひシェアして、更多人に届けてください🙂


  1. iPhoneで削除した通話履歴を復元する3つの方法|復元なしで確認する裏ワザも解説

    iPhoneの通話履歴は、いざ必要になったときにはもう手遅れ——そんな経験はありませんか?残念ながら、削除してしまった通話履歴を復元するのは簡単ではありません。iPhoneが通話履歴を管理する仕組みを変えることはほぼ不可能で、「最近削除した項目」のようなフォルダも用意されていません。しかし、いくつかの対処法は存在します。 この記事では、iPhoneで削除された通話履歴を復元する3つの方法に加えて、データを復元せずに削除済みの通話を確認する3つのテクニックもご紹介します。 なぜiPhoneは通話履歴を自動的に削除するのか? 実は、iPhoneの通話履歴機能には細かい設定項目がありません。デフ

  2. Excelで依存関係をトレースする2つの簡単な方法

    Excelでデータを扱う際、依存関係のトレース(Trace Dependents)の使い方を知っておくことは非常に重要です。あるセルの値が他のどのセルに依存しているのかを把握できれば、ワークブック全体の構造理解やトラブルシューティングが格段にしやすくなります。この記事では、Excelで依存関係をトレースする2つの簡単で実用的な方法を解説します。 練習用ワークブックをダウンロードして、実際に手を動かしながら試してみてください。 Excelで依存関係をトレースする2つの方法 ここでは、「ABC商事の上半期売上」というサンプルデータセットを使用します。データは3列構成で、列B・C・Dにはそれぞれ月・