gc_monitorを作ってみた

最近、Railsメモリリーク絡みで困ることがあったので、何か簡単に(拡張ライブラリを書かずに)GCの状況を把握できないものかと思い、それらしいのを作ってgemにしてみました。

http://github.com/komamitsu/gc_monitor

まぁ、簡単に言うとCGされずに残っているインスタンスの一覧を取得するモジュールです。

使い方は簡単(にしたつもり)。まず

sudo gem source -a http://gems.github.com
sudo gem install komamitsu-gc_monitor

で、gemとしてインストール。ところでgithubのgemの名前って、auther-projectなのに今頃気がついた。何だかなぁ…

で、こんな風に使うのです。

require 'rubygems'
require 'gc_monitor'
require 'date'          # 何となく例としてDateを使ってみたので

class Date
  attr_accessor :dummy  # GCさせるためダミーのアクセッサを
end

# Objectを指定すればほぼ全てのインスタンスをモニター
# ただし、Stringとかの組み込みクラスや実装上の問題でTimeクラスは対象外
GcMonitor.include_in_subclasses(Date)

# 必須ではないけれど、GCされたタイミングで何か処理させたければ(文字列で…)
Date.release_hook('puts "i am released."')

# リアルタイムで情報取得ができるTCPのインターフェース(これも任意)
# $ telnet localhost 4321
# list   <= GCされていないインスタンスを列挙
# list 6 <= 生成後、6秒経過したもののみ列挙
# quit   <= 切断
GcMonitor.tcp_server('0.0.0.0', 4321)
15.times do
  o = Date.new
  o.dummy = 'x' * 100 * 1024 * 1024  # GCしろ、CGしろ
  sleep 0.5
end

# この時点でGCされていないやつを列挙(これも任意、tcp_serverかこれか)
# :time => 8 で生成後8秒経過しているもののみ, 引数無しの場合は全て
GcMonitor.list(:time => 8).each{|rec| p rec}

とすると、

i am released.
i am released.
i am released.
i am released.
i am released.
i am released.
i am released.
i am released.
i am released.
i am released.
i am released.
["Date__fdbd2f89c", {:time=>Mon Aug 10 01:24:13 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd465ec", {:time=>Mon Aug 10 01:24:15 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd465a6", {:time=>Mon Aug 10 01:24:17 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd45d40", {:time=>Mon Aug 10 01:24:18 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd465e2", {:time=>Mon Aug 10 01:24:20 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd46510", {:time=>Mon Aug 10 01:24:22 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd45642", {:time=>Mon Aug 10 01:24:23 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd45eb2", {:time=>Mon Aug 10 01:24:29 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
["Date__fdbd45dcc", {:time=>Mon Aug 10 01:24:30 +0900 2009, :caller=>["/usr/lib/ruby/1.8/date.rb:754:in `new!'", "/usr/lib/ruby/1.8/date.rb:754:in `new'", "hoge.rb:23", "hoge.rb:22:in `times'", "hoge.rb:22"]}]
i am released.
i am released.
i am released.
i am released.

で、これを見て「最後に残っているインスタンスは九個だけだねぇ」、「じゃあ六個はGCされたってことかい?」、「まぁそういうこったねぇ」、「なるほどねぇ、どうですもう一杯?」という、まったりした会話につながるのではないでしょうか。

# あと、詳細は上記のgithubリポジトリにおいてある拙すぎるやっつけREADMEを…

2009-08-10 追記
Railsに使ってみたら色々問題が発覚… 明らかにミスったところもあるのでそれは直そうと思うのだけど、Railsメタプログラミングとの相性が悪いところもあるので、それはどうしようかなぁ…
いっそ、Cの拡張ライブラリで頑張った方が楽なのかも。