まとめておく。コレも便利だぜ、とかあったら教えてください

1. [C] gdb

まずはなにはともあれ gdb。例えば指定スレッドのCレベルでのバックトレースを表示させるにはこんなかんじ。

$ sudo gdb -p <pid>
(gdb) info thread
(gdb) thread 14
(gdb) bt

core を吐かせておいてから追う場合はこう

$ sudo gcore <pid>
$ gdb /usr/lib64/fluent/ruby/bin/ruby core.22111
(gdb) info thread
(gdb) thread 14
(gdb) bt

出力端折ったけど、こんなかんじ https://gist.github.com/sonots/10803036

2. [C] gstack

gdb で全スレッドのバックトレースをダンプさせようと思うとめんどくさいが、gstack を使うとできる。

$ sudo gstack <pid>

ただし、core ファイルに対しては適用できない。

pstack というツールもあるらしいが、私の環境だと全く同じものだった(gstack へのシンボリックリンクだった)。

出力はこんなかんじ https://gist.github.com/sonots/f58f632c9e7a5786aa05

3. [C] strace

実行中プロセスのシステムコールを追い続けるには strace を使おう。

$ strace -s 1024 -f -p <pid>

スレッドを追いたい場合は -f をつける。瞬間のダンプではないので処理がすすめば出力は更新される。

出力はこんなかんじ https://gist.github.com/sonots/8020037

strace については以前書いたやつもあるのでそちらも見てもらえると良い => http://blog.livedoor.jp/sonots/archives/18193659.html 

4. [Ruby] gdbruby

https://github.com/gunyarakun/gdbruby

生きているプロセスならば次に記載している sigdump など、他にも ruby レベルのバックトレースを表示するツールがあるが、core ファイルから ruby レベルのバックトレースを表示できるのは gdbruby だけ。

gdbruby.rb core.24113 `rbenv which ruby`

出力サンプルは gunyarakun さんのブログをみると書いてある
=> http://blog.wktk.co.jp/ja/entry/2013/10/30/gdbruby

5. [Ruby] sigdump

https://github.com/frsyuki/sigdump

ruby レベルでの全スレッドのバックトレースをダンプすることができる。gstack の ruby レベル版と言えるか。ついでに、ObjectSpace の状態(どのクラスのインスタンスが何個存在しているかなど)もダンプできる。

使い方はダンプしたいアプリのコードで 

require 'sigdump/setup'

と require しておき、kill -CONT <pid> すると、/tmp/sigdump-<pid>.log のようなファイルに出力される。

出力はこんなかんじ https://gist.github.com/sonots/e05d8fbcd7ab39e4a5f8

なお、コードに仕込んで好きなところでダンプしたい場合は

Sigdump.dump

を呼び出せばできる。=> sigdump.rb#L13

特にコードを遅くするようなこともないので、とりあえず require しておいてよさそう。

6. [Ruby] rbtrace

https://github.com/tmm1/rbtrace

rbtrace は ruby レベルでの strace のようなもの、とのこと。strace 同様、処理が進めば更新される。

trace 対象のアプリで require しておき、

require 'rbtrace'
$ rbtrace -p <pid> --firehose

とかやると追える。※ require すると外から rbtrace で attach できるようになるようだ。内部的には POSIX message queue を使っているっぽい。

firehose は全出力のオプションで以下のようになる。

Kernel#proc <0.000006>

Proc#call
  Dir.chdir
    Dir.pwd <0.000004>
    Process.pid <0.000003>
    String#multiply_vowels
      String#gsub
        String#* <0.000192>
        String#* <0.000003>
      String#gsub <0.000585>
    String#multiply_vowels <0.000593>
    Kernel#rand <0.000003>
    Kernel#sleep <0.239254>
  Dir.chdir <0.239933>
Proc#call <0.239947> 

-m で特定メソッドにしぼったり、--slow で遅いやつだけ出力したりもできる。
=> 詳細は README を読もう https://github.com/tmm1/rbtrace

7. [Ruby] profile

どのメソッドを何回呼び出したのか、平均で何秒かかったのかプロファイリングするには、標準ライブラリの profile を利用できる。

普通に require すると、その瞬間にスタートして、終了時に print する動きをするので、任意の期間に仕込みたい場合は、 profile ではなく profiler を require して以下のようにする。

require 'profiler'
RubyVM::InstructionSequence.compile_option = {
  :trace_instruction => true,
  :specialized_instruction => false
}

# 好きなタイミングで start する
Profiler__::start_profile

# print して終了する
Profiler__::print_profile(STDERR)
Profiler__::stop_profile

出力はこんなかんじ

  %   cumulative   self              self     total
 time   seconds   seconds    calls  ms/call  ms/call  name
100.00     0.01      0.01        2     5.00     5.00  Hoge.fuga
  0.00     0.01      0.00        2     0.00     0.00  IO#puts
  0.00     0.01      0.00        1     0.00     0.00  TracePoint#enable
  0.00     0.01      0.00        4     0.00     0.00  IO#write
  0.00     0.01      0.00        2     0.00     0.00  Fixnum#to_s
  0.00     0.01      0.00        2     0.00     0.00  Kernel#puts
  0.00     0.01      0.00        4     0.00     2.50  nil#
  0.00     0.01      0.00        1     0.00    10.00  Range#each
  0.00     0.01      0.00        1     0.00     0.00  TracePoint#disable
  0.00     0.01      0.00        1     0.00    10.00  #toplevel

8. [Ruby] tracer

on してから off するまでの ruby レベルの実行をすべてトレース出力するには、 標準ライブラリ tracerを利用できる。

require 'tracer'

# 好きなタイミングでスタートする
Tracer.on

# 好きなタイミングで終了する
Tracer.off

処理が進むたびに逐次出力される。

試しに require 'uri' をトレース出力したのはこんなかんじ
=> https://gist.github.com/sonots/551689f166e9b3cf4aa4

9. [Ruby] stackprof

https://github.com/tmm1/stackprof

これも Ruby レベルのプロファイラ。Ruby 2.1 時代のプロファイラだそうで、ruby 2.1 から入った rb_profile_frames() API を使っているらしい。ruby 標準の profile は正直大分時間と負荷がかかるけれど、こちらだと本番でもイケるらしい。ホントか。

たとえば rack middleware としてこんなかんじで仕込んでおいて、1s ごとにプロファイリングしたりできるようだ。 もちろん普通に ruby コードとしてプロファイリングする箇所を指定して出力を得ることもできる。

# config.ru
use StackProf::Middleware, enabled: true,
                           mode: :cpu,
                           interval: 1000,
                           save_every: 5

コード区間を指定する場合

require 'stackprof'
StackProf.start

// code

StackProf.stop
StackProf.results(
'/tmp/stackprof.dump')
stackprof コマンドに dump ファイルを喰わせると集計してくれる。
$ stackprof tmp/stackprof-cpu-*.dump
==================================
  Mode: cpu(1000)
  Samples: 711 (1.52% miss rate)
  GC: 119 (16.74%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
       465  (65.4%)          95  (13.4%)     Coolio::IO#on_readable
       332  (46.7%)          70   (9.8%)     Fluent::MeasureTime#measure_time
       513  (72.2%)          48   (6.8%)     Coolio::Loop#run
        30   (4.2%)          30   (4.2%)     Fluent::EngineClass#now
     以下略
仕組みとして、どのメソッドに何秒かかったか計測しているというよりは、一定周期毎に現在実行されているメソッドをカウント(サンプリング)し、比率を出す仕組みになっている。
SAMPLING が自身自身が呼ばれた回数、TOTAL が自分自身とそこから呼び出しているメソッド(child) を含めた回数を示しているようだ。pct はそれぞれ percentage。

さらにメソッドを指定して、そのメソッドのみのプロファイリングができる。そのメソッドを呼んでいるやつを特定したりできる。呼んでいるやつがわかるというのは便利。
$ stackprof tmp/stackprof-cpu-*.dump --method 'String#blank?'
String#blank? (gems/activesupport-2.3.14.github30/lib/active_support/core_ext/object/blank.rb:80) samples: 1595 self (2.6%) / 1660 total (2.7%) callers: 373 ( 41.0%) ApplicationHelper#current_user 192 ( 21.1%) ApplicationHelper#current_repository callers: 803 ( 48.4%) Object#present? code: | 80 | def blank? 1225 (2.0%) / 1225 (2.0%) | 81 | self !~ /[^[:space:]]/ | 82 | end

作者のこちらのブログで作者の職場である GitHub での事例を元に使い方を教えてくれていて、非常にわかりやすいので一読をすすめる => http://tmm1.net/ruby21-profiling/
rbtrace も同じ作者だけど、tmm1 氏はいろんなの持ってる。 

(追加) 10. [Ruby] memprof2

https://github.com/sonots/memprof2

Ruby レベルのヒープ(メモリ)プロファイラ。Ruby 2.1 から入った trace_object_allocations API を使っている。

使い方はこんな感じで、Memprof2.start と Memprof2.stop で囲った範囲内で、どの行がどのぐらいメモリを消費しているか調べることができる。

# example.rb
require 'memprof2'
Memprof2.start
foo = ["foo"] * 255 # 調べたいやつ
Memprof2.report
Memprof2.stop
$ ruby example.rb
2080 example.rb:3:Array
40 example.rb:3:Array
40 example.rb:3:String

詳細な説明については Ruby でラインメモリプロファイラ の記事に書いている。


メモ追記:

10. [Ruby] GCにかかった時間を追う http://docs.ruby-lang.org/ja/2.1.0/class/GC=3a=3aProfiler.html
11. [Ruby] メモリ allocation を追うやつ https://github.com/csfrancis/alloc_track
12. [C] pmap: report memory map of process https://gist.github.com/sonots/ffe105fdcb6d48c1142c