SlideShare a Scribd company logo
DEBUGGING RUBY
    Aman Gupta
      @tmm1


        http://tinyurl.com/eyfast
AS RUBYISTS, WE'VE SEEN...
nasty bugs
ejpphoto (flickr)
fatboyke (flickr)
                    code
memory bloat
37prime (flickr)
THIS TALK IS ABOUT...




TOOLS TO FIX THESE ISSUES.
TOOLS FOR LINUX.




                        lsof
                      strace
                      ltrace
pleeker (flickr)
TOOLS FOR C CODE.




                       perftools
                         gdb
booddin (flickr)
TOOLS FOR NETWORKS.




                         tcpdump
                           ngrep
pascal.charest (flickr)
TOOLS FOR CPU USAGE.




                      perftools
                     perftools.rb
marksze (flickr)
TOOLS FOR MEMORY USAGE.




                  bleak_house
                     gdb.rb
kgrocki (flickr)     memprof
IGNORE THE FINE PRINT



mayu (flickr)           delgrossodotcom (flickr)
LSOF
   list open files


lsof -nPp <pid>
lsof -nPp <pid>
-n
Inhibits the conversion of network numbers to host names.

-P
Inhibits the conversion of port numbers to names for network files


   FD    TYPE   NAME                                             json
  cwd     DIR   /var/www/myapp                               memcached
  txt     REG   /usr/bin/ruby                                   mysql
  mem     REG   /json-1.1.9/ext/json/ext/generator.so           http
  mem     REG   /json-1.1.9/ext/json/ext/parser.so
  mem     REG   /memcached-0.17.4/lib/rlibmemcached.so
  mem     REG   /mysql-2.8.1/lib/mysql_api.so
    0u    CHR   /dev/null
    1w    REG   /usr/local/nginx/logs/error.log
    2w    REG   /usr/local/nginx/logs/error.log
    3u   IPv4   10.8.85.66:33326->10.8.85.68:3306 (ESTABLISHED)
   10u   IPv4   10.8.85.66:33327->10.8.85.68:3306 (ESTABLISHED)
   11u   IPv4   127.0.0.1:58273->127.0.0.1:11211 (ESTABLISHED)
   12u    REG   /tmp/RackMultipart.28957.0
   33u   IPv4   174.36.83.42:37466->69.63.180.21:80 (ESTABLISHED)
STRACE
    trace system calls and signals


      strace -cp <pid>
strace -ttTp <pid> -o <file>
strace -cp <pid>
-c
Count time, calls, and errors for each system call and report a
summary on program exit.

-p pid
Attach to the process with the process ID pid and begin tracing.


% time     seconds usecs/call      calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 50.39    0.000064           0      1197       592 read
 34.65    0.000044           0       609           writev
 14.96    0.000019           0      1226           epoll_ctl
  0.00    0.000000           0         4           close
  0.00    0.000000           0         1           select
  0.00    0.000000           0         4           socket
  0.00    0.000000           0         4         4 connect
  0.00    0.000000           0      1057           epoll_wait
------ ----------- ----------- --------- --------- ----------------
100.00    0.000127                  4134       596 total
strace -ttTp <pid> -o <file>
-t
Prefix each line of the trace with the time of day.

-tt
If given twice, the time printed will include the microseconds.

-T
Show the time spent in system calls.

-o filename
Write the trace output to the file filename rather than to stderr.

epoll_wait(9, {{EPOLLIN, {u32=68841296, u64=68841296}}}, 4096, 50) = 1 <0.033109>
accept(10, {sin_port=38313, sin_addr="127.0.0.1"}, [1226]) = 22 <0.000014>
fcntl(22, F_GETFL) = 0x2 (flags O_RDWR) <0.000007>
fcntl(22, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000008>
setsockopt(22, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000008>
accept(10, 0x7fff5d9c07d0, [1226]) = -1 EAGAIN <0.000014>
epoll_ctl(9, EPOLL_CTL_ADD, 22, {EPOLLIN, {u32=108750368, u64=108750368}}) = 0 <0.000009>
epoll_wait(9, {{EPOLLIN, {u32=108750368, u64=108750368}}}, 4096, 50) = 1 <0.000007>
read(22, "GET / HTTP/1.1r"..., 16384) = 772 <0.000012>
rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000007>
poll([{fd=5, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout) <0.000008>
write(5, "1000000-0003SELECT * FROM `table`"..., 56) = 56 <0.000023>
read(5, "25101,20x234m"..., 16384) = 284 <1.300897>
http client connection
                                   read 772 bytes


read(22, "GET / HTTP/1.1r"..., 16384) = 772 <0.0012>




       incoming http request
                                        took 0.0012s
mysql connection
                     write sql query to db


write(5, "SELECT * FROM `table`"..., 56) = 56 <0.0023>
read(5, "25101,20x234m"..., 16384) = 284 <1.30>



           read query
            response
                                             slow query
stracing ruby: SIGVTALRM
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 2207807 <0.000009>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 0 <0.000009>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 140734552062624 <0.000009>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 140734552066688 <0.000009>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 11333952 <0.000008>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 0 <0.000009>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---
    rt_sigreturn(0x1a)        = 1 <0.000010>
    --- SIGVTALRM (Virtual   timer expired) @ 0 (0) ---


• ruby 1.8 uses signals to schedule its green threads
• process receives a SIGVTALRM signal every 10ms
stracing ruby: sigprocmask
% time     seconds usecs/call      calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100.00    0.326334           0   3568567           rt_sigprocmask
  0.00    0.000000           0         9           read
  0.00    0.000000           0        10           open
  0.00    0.000000           0        10           close
  0.00    0.000000           0         9           fstat
  0.00    0.000000           0        25           mmap
------ ----------- ----------- --------- --------- ----------------
100.00    0.326334               3568685         0 total



  • debian/redhat compile ruby with --enable-pthread
  • uses a native thread timer for SIGVTALRM
  • causes excessive calls to sigprocmask: 30% slowdown!
TCPDUMP
   dump traffic on a network


tcpdump -i eth0 -s 0 -nqA
    tcp dst port 3306
tcpdump -i <eth> -s <len> -nqA <expr>
  tcpdump -i <eth> -w <file> <expr>
-i <eth>
Network interface.

-s <len>
Snarf len bytes of data from each packet.

-n
Don't convert addresses (host addresses, port numbers) to names.

-q
Quiet output.   Print less protocol information.

-A
Print each packet (minus its link level header) in ASCII.

-w <file>
Write the raw packets to file rather than printing them out.

<expr>
libpcap expression, for example:
  tcp src port 80
  tcp dst port 3306
tcp dst port 80
19:52:20.216294 IP 24.203.197.27.40105 >
174.37.48.236.80: tcp 438
E...*.@.l.%&.....%0....POx..%s.oP.......
GET /poll_images/cld99erh0/logo.png HTTP/1.1
Accept: */*
Referer: http://apps.facebook.com/realpolls/?
_fb_q=1
tcp dst port 3306
19:51:06.501632 IP 10.8.85.66.50443 >
10.8.85.68.3306: tcp 98
E..."K@.@.Yy
.UB
.UD.....z....L............
GZ.y3b..[......W....
SELECT * FROM `votes` WHERE (`poll_id` =
72621) LIMIT 1
tcpdump -w <file>
PERFTOOLS
     google's performance tools


CPUPROFILE=/tmp/myprof ./myapp
  pprof ./myapp /tmp/myprof
wget http://google-perftools.googlecode.com/files/google-
perftools-1.6.tar.gz                                 download
tar zxvf google-perftools-1.6.tar.gz
cd google-perftools-1.6

./configure --prefix=/opt
make                                                 compile
sudo make install

# for linux
export LD_PRELOAD=/opt/lib/libprofiler.so            setup

# for osx
export DYLD_INSERT_LIBRARIES=/opt/lib/libprofiler.dylib

CPUPROFILE=/tmp/ruby.prof ruby -e'                   profile
  5_000_000.times{ "hello world" }
'

pprof `which ruby` --text /tmp/ruby.prof             report
pprof ruby                pprof ruby
 ruby.prof --text           ruby.prof --gif
Total: 103 samples
    95 92.2% rb_yield_0
   103 100.0% rb_eval
    12 11.7% gc_sweep
    52 50.5% rb_str_new3
     3   2.9% obj_free
   103 100.0% int_dotimes
    12 11.7% gc_mark
Profiling MRI
                  • 10% of production
                     VM time spent in
                     rb_str_sub_bang
                  • String#sub!
                  • called from
                     Time.parse


return   unless   str.sub!(/A(d{1,2})/, '')
return   unless   str.sub!(/A( d|d{1,2})/, '')
return   unless   str.sub!(/A( d|d{1,2})/, '')
return   unless   str.sub!(/A(d{1,3})/, '')
return   unless   str.sub!(/A(d{1,2})/, '')
return   unless   str.sub!(/A(d{1,2})/, '')
Profiling EM + threads
           Total: 3763 samples
            2764 73.5% catch_timer
             989 26.3% memcpy
               3   0.1% st_lookup
               2   0.1% rb_thread_schedule
               1   0.0% rb_eval
               1   0.0% rb_newobj
               1   0.0% rb_gc_force_recycle


           • known issue: EM+threads =
            slow
           • memcpy??
           • thread context switches copy
            the stack w/ memcpy
           • EM allocates huge buffer on
            the stack
           • solution: move buffer to the
            heap
PERFTOOLS.RB
   perftools for ruby code


pprof.rb /tmp/myrbprof

                 github.com/tmm1/perftools.rb
gem install perftools.rb

RUBYOPT="-r`gem which perftools | tail -1`"
CPUPROFILE=/tmp/myrbprof
ruby myapp.rb

pprof.rb /tmp/myrbprof --text
pprof.rb /tmp/myrbprof --gif > /tmp/myrbprof.gif
require 'sinatra'
                       $ ab -c 1 -n 50 http://127.0.0.1:4567/compute
                       $ ab -c 1 -n 50 http://127.0.0.1:4567/sleep
get '/sleep' do
  sleep 0.25
  'done'                          • Sampling profiler:
end
                                    • 232 samples total
get '/compute' do                   • 83 samples were in /compute
  proc{ |n|
    a,b=0,1                         • 118 samples had /compute on
                                       the stack but were in
    n.times{ a,b = b,a+b }             another function
    b
  }.call(10_000)                    • /compute accounts for 50%
  'done'                               of process, but only 35% of
                                       time was in /compute itself
end

== Sinatra has ended his set (crowd applauds)
PROFILE: interrupts/evictions/bytes = 232/0/2152

Total: 232 samples
      83 35.8% 35.8%        118   50.9% Sinatra::Application#GET /compute
      56 24.1% 59.9%         56   24.1% garbage_collector
      35 15.1% 75.0%        113   48.7% Integer#times
CPUPROFILE_REALTIME=1
                         CPUPROFILE=app.prof
CPUPROFILE=app-rt.prof
redis-rb bottleneck
why is rubygems slow?
faster
      bundle
      install
• 23% spent in
  Gem::Version#<=>
• simple patch to rubygems
  improved overall install
  performance by 15%
• http://gist.github.com/
  458185
CPUPROFILE_OBJECTS=1
CPUPROFILE=app-objs.prof

• object allocation profiler
  mode built-in
• 1 sample = 1 object
  created
• Time parsing is both
  CPU and object
  allocation intensive
• using mysql2 moves
  this to C
LTRACE
        trace library calls


      ltrace -cp <pid>
ltrace -ttTp <pid> -o <file>
ltrace -c ruby threaded_em.rb
         % time     seconds usecs/call      calls       function
         ------ ----------- ----------- --------- --------------------
          48.65   11.741295         617     19009 memcpy
          30.16    7.279634         831      8751 longjmp
           9.78    2.359889         135     17357 _setjmp
           8.91    2.150565         285      7540 malloc
           1.10    0.265946          20     13021 memset
           0.81    0.195272          19     10105 __ctype_b_loc
           0.35    0.084575          19      4361 strcmp
           0.19    0.046163          19      2377 strlen
           0.03    0.006272          23       265 realloc
         ------ ----------- ----------- --------- --------------------
         100.00   24.134999                 82999 total


  ltrace -ttT -e memcpy ruby threaded_em.rb
01:24:48.769408 --- SIGVTALRM (Virtual timer expired) ---
01:24:48.769616 memcpy(0x1216000, "", 1086328)   = 0x1216000 <0.000578>
01:24:48.770555 memcpy(0x6e32670, "240&343v", 1086328) = 0x6e32670 <0.000418>

01:24:49.899414 --- SIGVTALRM (Virtual timer expired) ---
01:24:49.899490 memcpy(0x1320000, "", 1082584)   = 0x1320000 <0.000628>
01:24:49.900474 memcpy(0x6e32670, "", 1086328) = 0x6e32670 <0.000479>
LTRACE/LIBDL
  trace dlopen’d library calls

ltrace -F <conf> -bg -x
   <symbol> -p <pid>

             github.com/ice799/ltrace/tree/libdl
ltrace -F <conf> -b -g -x <sym>
-b
Ignore signals.

-g
Ignore libraries linked at compile time.

-F <conf>
Read prototypes from config file.

-x <sym>
Trace calls to the function sym.

-s <num>
Show first num bytes of string args.


-F ltrace.conf
int mysql_real_query(addr,string,ulong);
void garbage_collect(void);
int memcached_set(addr,string,ulong,string,ulong);
ltrace -x garbage_collect
19:08:06.436926   garbage_collect()   =   <void>   <0.221679>
19:08:15.329311   garbage_collect()   =   <void>   <0.187546>
19:08:17.662149   garbage_collect()   =   <void>   <0.199200>
19:08:20.486655   garbage_collect()   =   <void>   <0.205864>
19:08:25.102302   garbage_collect()   =   <void>   <0.214295>
19:08:35.552337   garbage_collect()   =   <void>   <0.189172>
ltrace -x mysql_real_query
mysql_real_query(0x1c9e0500,   "SET NAMES 'UTF8'", 16)         =   0   <0.000324>
mysql_real_query(0x1c9e0500,   "SET SQL_AUTO_IS_NULL=0", 22)   =   0   <0.000322>
mysql_real_query(0x19c7a500,   "SELECT * FROM `users`", 21)    =   0   <1.206506>
mysql_real_query(0x1c9e0500,   "COMMIT", 6)                    =   0   <0.000181>
ltrace -x memcached_set
memcached_set(0x15d46b80,   "Status:33",   21,   "004b",   366)   =   0   <0.01116>
memcached_set(0x15d46b80,   "Status:96",   21,   "004b",   333)   =   0   <0.00224>
memcached_set(0x15d46b80,   "Status:57",   21,   "004b",   298)   =   0   <0.01850>
memcached_set(0x15d46b80,   "Status:10",   21,   "004b",   302)   =   0   <0.00530>
memcached_set(0x15d46b80,   "Status:67",   21,   "004b",   318)   =   0   <0.00291>
memcached_set(0x15d46b80,   "Status:02",   21,   "004b",   299)   =   0   <0.00658>
memcached_set(0x15d46b80,   "Status:34",   21,   "004b",   264)   =   0   <0.00243>
GDB
 the GNU debugger


gdb <executable>
gdb attach <pid>
Debugging Ruby Segfaults
 test_segv.rb:4: [BUG] Segmentation fault
 ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.7.0]

                             def test
  #include "ruby.h"            require 'segv'
                               4.times do
  VALUE                          Dir.chdir '/tmp' do
  segv()                           Hash.new{ segv }[0]
  {                              end
    VALUE array[1];            end
    array[1000000] = NULL;   end
    return Qnil;
  }                          sleep 10
                             test()
  void
  Init_segv()
  {
    rb_define_method(rb_cObject, "segv", segv, 0);
  }
1. Attach to running process
 $ ps aux | grep ruby
 joe 23611 0.0 0.1      25424   7540 S Dec01 0:00 ruby test_segv.rb

 $ sudo gdb ruby 23611
 Attaching to program: ruby, process 23611
 0x00007fa5113c0c93 in nanosleep () from /lib/libc.so.6
 (gdb) c
 Continuing.

 Program received signal SIGBUS, Bus error.
 segv () at segv.c:7
 7	   array[1000000] = NULL;


2. Use a coredump
 Process.setrlimit Process::RLIMIT_CORE, 300*1024*1024
 $ sudo mkdir /cores
 $ sudo chmod 777 /cores
 $ sudo sysctl kernel.core_pattern=/cores/%e.core.%s.%p.%t

 $ sudo gdb ruby /cores/ruby.core.6.23611.1259781224
def test
  require 'segv'
  4.times do
    Dir.chdir '/tmp' do
       Hash.new{ segv }[0]
    end
  end     (gdb) where
end       #0 segv () at segv.c:7
          #1 0x000000000041f2be in    call_cfunc () at eval.c:5727
test()    ...
          #13 0x000000000043ba8c in   rb_hash_default () at hash.c:521
          ...
          #19 0x000000000043b92a in   rb_hash_aref () at hash.c:429
          ...
          #26 0x00000000004bb7bc in   chdir_yield () at dir.c:728
          #27 0x000000000041d8d7 in   rb_ensure () at eval.c:5528
          #28 0x00000000004bb93a in   dir_s_chdir () at dir.c:816
          ...
          #35 0x000000000041c444 in   rb_yield () at eval.c:5142
          #36 0x0000000000450690 in   int_dotimes () at numeric.c:2834
          ...
          #48 0x0000000000412a90 in   ruby_run () at eval.c:1678
          #49 0x000000000041014e in   main () at main.c:48
GDB.RB
  gdb with MRI hooks


gem install gdb.rb
   gdb.rb <pid>
                  github.com/tmm1/gdb.rb
(gdb) ruby eval 1+2
3

(gdb) ruby eval Thread.current
#<Thread:0x1d630 run>

(gdb) ruby eval Thread.list.size
8
(gdb) ruby threads list
0x15890 main thread THREAD_STOPPED    WAIT_JOIN(0x19ef4)
0x19ef4      thread THREAD_STOPPED    WAIT_TIME(57.10s)
0x19e34      thread THREAD_STOPPED    WAIT_FD(5)
0x19dc4      thread THREAD_STOPPED    WAIT_NONE
0x19dc8      thread THREAD_STOPPED    WAIT_NONE
0x19dcc      thread THREAD_STOPPED    WAIT_NONE
0x22668      thread THREAD_STOPPED    WAIT_NONE
0x1d630 curr thread THREAD_RUNNABLE   WAIT_NONE
(gdb) ruby objects
  HEAPS            8
  SLOTS      1686252
  LIVE        893327 (52.98%)
  FREE        792925 (47.02%)

  scope         1641   (0.18%)
  regexp        2255   (0.25%)
  data          3539   (0.40%)
  class         3680   (0.41%)
  hash          6196   (0.69%)
  object        8785   (0.98%)
  array        13850   (1.55%)
  string      105350   (11.79%)
  node        742346   (83.10%)
(gdb) ruby objects strings
      140 u'lib'
      158 u'0'
      294 u'n'
      619 u''

    30503 unique strings
  3187435 bytes
def test
  require 'segv'
  4.times do
    Dir.chdir '/tmp' do
       Hash.new{ segv }[0]
    end
  end
end
            (gdb) ruby threads
test()
             0xa3e000 main curr thread THREAD_RUNNABLE WAIT_NONE
                   node_vcall     segv in test_segv.rb:5
                   node_call      test in test_segv.rb:5
                   node_call      call in test_segv.rb:5
                   node_call      default in test_segv.rb:5
                   node_call      [] in test_segv.rb:5
                   node_call      test in test_segv.rb:4
                   node_call      chdir in test_segv.rb:4
                   node_call      test in test_segv.rb:3
                   node_call      times in test_segv.rb:3
                   node_vcall     test in test_segv.rb:9
rails_warden leak
(gdb) ruby objects classes
    1197 MIME::Type
    2657 NewRelic::MetricSpec
    2719 TZInfo::TimezoneTransitionInfo
    4124 Warden::Manager
    4124 MethodOverrideForAll
    4124 AccountMiddleware
    4124 Rack::Cookies
    4125 ActiveRecord::ConnectionAdapters::ConnectionManagement
    4125 ActionController::Session::CookieStore
    4125 ActionController::Failsafe
    4125 ActionController::ParamsParser
    4125 Rack::Lock
    4125 ActionController::Dispatcher
    4125 ActiveRecord::QueryCache
    4125 ActiveSupport::MessageVerifier
    4125 Rack::Head


middleware chain leaking per request
mongrel sleeper thread
0x16814c00          thread THREAD_STOPPED    WAIT_TIME(0.47) 1522 bytes
      node_fcall    sleep in lib/mongrel/configurator.rb:285
      node_fcall    run in lib/mongrel/configurator.rb:285
      node_fcall    loop in lib/mongrel/configurator.rb:285
      node_call     run in lib/mongrel/configurator.rb:285
      node_call     initialize in lib/mongrel/configurator.rb:285
      node_call     new in lib/mongrel/configurator.rb:285
      node_call     run in bin/mongrel_rails:128
      node_call     run in lib/mongrel/command.rb:212
      node_call     run in bin/mongrel_rails:281
      node_fcall    (unknown) in bin/mongrel_rails:19


def run
  @listeners.each {|name,s|
    s.run
  }


  $mongrel_sleeper_thread = Thread.new { loop { sleep 1 } }
end
god memory leaks
(gdb) ruby objects arrays    43   God::Process
 elements instances          43   God::Watch
    94310 3                  43   God::Driver
    94311 3                  43   God::DriverEventQueue
    94314 2                  43   God::Conditions::MemoryUsage
    94316 1                  43   God::Conditions::ProcessRunning
                             43   God::Behaviors::CleanPidFile
     5369 arrays             45   Process::Status
  2863364 member elements    86   God::Metric
                            327   God::System::SlashProcPoller
many arrays with            327   God::System::Process
 90k+ elements!             406   God::DriverEvent



  5 separate god leaks fixed by Eric
   Lindvall with the help of gdb.rb!
MEMPROF
   a heap visualizer for ruby


  gem install memprof
open http://memprof.com
                     github.com/ice799/memprof
memprof features
• memprof.track

• memprof.dump

• memprof.dump_all

• memprof.com
Memprof.track{
                100.times{ "abc" }
                100.times{ 1.23 + 1 }
                100.times{ Module.new }
              }

              100       file.rb:2:String
              100       file.rb:3:Float
              100       file.rb:4:Module

• like   bleak_house, but for a given block of code
• use Memprof::Middleware       in your webapps to run track
 per request
memprof features
• memprof.track

• memprof.dump

• memprof.dump_all

• memprof.com
Memprof.dump{
        strings                  }
                                   "hello" + "world"




{
    "_id": "0x19c610",        memory address of object
    "file": "file.rb",        file and line where string
    "line": 2,
                              was created
    "type": "string",
    "class": "0x1ba7f0",      address of the class
    "class_name": "String",   “String”

    "length": 10,             length and contents
    "data": "helloworld"      of this string instance
}
arrays
                                        Memprof.dump{
                                          [
                                            1,
                                            :b,
{
    "_id": "0x19c5c0",
                                            2.2,
                                            "d"
    "class": "0x1b0d18",                  ]
    "class_name": "Array",              }

    "length": 4,
    "data": [
      1,                     integers and symbols are
      ":b",                  stored in the array itself
        "0x19c750",          floats and strings are
        "0x19c598"           separate ruby objects
    ]
}
hashes
                                        Memprof.dump{
                                          {
                                            :a => 1,
                                            "b" => 2.2
{                                         }
    "_id": "0x19c598",                  }
    "type": "hash",
    "class": "0x1af170",
    "class_name": "Hash",

    "default": null,           no default proc
    "length": 2,
    "data": [
      [ ":a", 1 ],
                               hash entries as key/value
      [ "0xc728", "0xc750" ]   pairs
    ]
}
classes
                                   Memprof.dump{
                                     class Hello
                                       @@var=1
                                       Const=2
{                                      def world() end
    "_id": "0x19c408",
                                     end
    "type": "class",               }
    "name": "Hello",
    "super": "0x1bfa48",       superclass object reference
    "super_name": "Object",

    "ivars": {                 class variables and constants
       "@@var":   1,           are stored in the instance
       "Const":   2
    },                         variable table
    "methods":    {
       "world":   "0x19c318"   references to method objects
    }
}
memprof features
• memprof.track

• memprof.dump

• memprof.dump_all

• memprof.com
Memprof.dump_all("myapp_heap.json")
• dump    out every single live object as json

• one   per line to specified file


• analyze     via

  • jsawk/grep

  • mongodb/couchdb

  • custom      ruby scripts

  • libyajl   + Boost Graph Library
memprof features
• memprof.track

• memprof.dump

• memprof.dump_all

• memprof.com
memprof.com
a web-based heap visualizer and leak analyzer
plugging a leak in rails3
• in dev mode, rails3 is leaking 10mb per request

let’s use memprof to find it!

  # in environment.rb
  require `gem which memprof/signal`.strip
plugging a leak
   in rails3
 send the app some
 requests so it leaks
 $ ab -c 1 -n 30
 http://localhost:3000/


 tell memprof to dump
 out the entire heap to
 json
 $ memprof
   --pid <pid>
   --name <dump name>
   --key <api key>
2519 classes
  30 copies of
TestController

                 mongo query for all
                 TestController classes



                 details for one copy of
                 TestController
find references to object




 “leak” is on line 178



holding references
 to all controllers
• In development mode, Rails reloads all your
  application code on every request
• ActionView::Partials::PartialRenderer is caching
  partials used by each controller as an optimization
• But.. it ends up holding a reference to every single
  reloaded version of those controllers
MORE* MEMPROF
          FEATURES
• memprof.trace

• memprof::tracer




                    * currently under development
config.middleware.use(Memprof::Tracer)

{
    "time": 4.3442,              total time for request

    "rails": {                   rails controller/action
       "controller": "test",
       "action": "index"
    },

    "request": {               request env info
       "REQUEST_PATH": "/test",
       "REQUEST_METHOD": "GET"
    },
config.middleware.use(Memprof::Tracer)



 "mysql": {
    "queries": 3,        3 mysql queries
    "time": 0.00109302
 },

 "gc": {
    "calls": 8,          8 calls to GC
    "time": 2.04925      2 secs spent in GC
 },
config.middleware.use(Memprof::Tracer)
    "objects": {
      "created": 3911103,    3 million objs created
      "types": {
        "none": 1168831,     1 million method calls
        "object": 1127,      object instances
        "float": 627,
        "string": 1334637,   lots of strings
        "array": 609313,     lots of arrays
        "hash": 3676,
        "match": 70211       regexp matches
      }
    }
}
RACK-PERFTOOLS
 rack middleware for perftools.rb


    gem install rack-
   perftools_profiler
            github.com/bhb/rack-perftools_profiler
require 'rack/perftools_profiler'
                config.middleware.insert(
                  0,
                  Rack::PerftoolsProfiler,
                  :default_printer => 'gif'
                )

$   curl   http://localhost:3000/__start__
$   curl   http://localhost:3000/home
$   curl   http://localhost:3000/about
$   curl   http://localhost:3000/__stop__

$ curl http://localhost:3000/__data__ -o profile.gif
$ curl http://localhost:3000/__data__?printer=text -o profile.txt

$ curl "http://localhost:3000/home?profile=true&times=10"
    -o 10_requests_to_homepage.gif
TOOLS MAKE
DEBUGGING EASIER.
LEARN THESE TOOLS.
                  USE THESE TOOLS.




yujie (flickr)
EY CASE STUDY
• Example   of Professional Services engagement

• Interested?   http://tinyurl.com/eyfast




                          http://tinyurl.com/eyfast
LSOF
15u    IPv4    TCP ->10.243.63.80:11211 (ESTABLISHED)
20u    IPv4    TCP ->10.243.63.80:11211 (ESTABLISHED)
23u    IPv4    TCP ->10.243.63.80:11211 (ESTABLISHED)
                       multiple connections to memcached
                                         via different drivers

 18r     DIR    /shared/public/javascripts/packaged
 19r     DIR    /shared/public/javascripts/packaged
 22r     DIR    /shared/public/javascripts/packaged
                        multiple open handles to javascript
                                        assets directories
STRACE
% time     seconds     calls   syscall
------ ----------- ---------   ---------
 26.28    0.054178      8731   read
 25.81    0.053216    316519   stat
 20.37    0.041993         1   clone
 15.83    0.032648     11034   getdents
  3.54    0.007309     10326   write

                40% of kernel time querying
                                  filesystem
LTRACE
mysql_query("SELECT   * FROM tags WHERE id = 9129")
mysql_query("SELECT   * FROM tags WHERE id = 9129")
mysql_query("SELECT   * FROM tag_info WHERE tag_id
= 9129")
mysql_query("SELECT   * FROM tags WHERE id = 9129")
mysql_query("SELECT   * FROM tags WHERE id = 9129")
mysql_query("SELECT   * FROM tags WHERE id = 9129")

                       common queries repeated multiple
                                times during a request
GDB.RB
(gdb) ruby objects

  HEAPS           10
  SLOTS      4450607
  LIVE       2006769 (45.09%)
  FREE       2443838 (54.91%) large ruby heap

  hash         12939   (0.64%)    numerous 'node'
  array        38007   (1.89%)    objects- required
  object       47633   (2.37%)    libraries and plugins
  string      234205   (11.67%)   that are no longer in
  node       1637926   (81.62%)   use
PERFTOOLS.RB



majority of ruby process' time on
CPU spent in garbage collection
MEMPROF



wide distribution of response times




       expensive controller actions creating
       many millions of objects per request
MEMPROF




          37% of
          response time
          attributed to
          garbage
          collection
BEFORE
Time taken for tests:     20.156 seconds
Complete requests:        100
Requests per second:      4.96 [#/sec] (mean)
Time per request:         201.555 [ms] (mean)


                  AFTER
Time taken for tests:      8.100 seconds
Complete requests:         100
Requests per second:       12.35 [#/sec] (mean)
Time per request:          81.000 [ms] (mean)

     2.5x improvement in throughput after addressing
                 some of these issues and tuning GC
QUESTIONS?
     Aman Gupta
       @tmm1

 http://scr.bi/debuggingruby

More Related Content

PDF
Performance tweaks and tools for Linux (Joe Damato)
PDF
A deep dive about VIP,HAIP, and SCAN
PDF
Kernel Recipes 2017 - Modern Key Management with GPG - Werner Koch
PDF
BPF Internals (eBPF)
ODP
Linux Capabilities - eng - v2.1.5, compact
PPTX
Creating "Secure" PHP applications, Part 2, Server Hardening
PPT
Troubleshooting Linux Kernel Modules And Device Drivers
PDF
Profiling your Applications using the Linux Perf Tools
Performance tweaks and tools for Linux (Joe Damato)
A deep dive about VIP,HAIP, and SCAN
Kernel Recipes 2017 - Modern Key Management with GPG - Werner Koch
BPF Internals (eBPF)
Linux Capabilities - eng - v2.1.5, compact
Creating "Secure" PHP applications, Part 2, Server Hardening
Troubleshooting Linux Kernel Modules And Device Drivers
Profiling your Applications using the Linux Perf Tools

What's hot (20)

PDF
Deploying Prometheus stacks with Juju
PDF
Solaris Kernel Debugging V1.0
PDF
Osol Pgsql
PDF
Tracing MariaDB server with bpftrace - MariaDB Server Fest 2021
PDF
Pledge in OpenBSD
PDF
A little systemtap
PDF
David container security-with_falco
PDF
Performance Analysis Tools for Linux Kernel
PDF
Rac introduction
PDF
計算機性能の限界点とその考え方
PDF
Kernel Recipes 2017: Performance Analysis with BPF
PDF
Linux Tracing Superpowers by Eugene Pirogov
PDF
True stories on the analysis of network activity using Python
PPTX
SSL Failing, Sharing, and Scheduling
PDF
Kernel Recipes 2019 - Hunting and fixing bugs all over the Linux kernel
PDF
BPF / XDP 8월 세미나 KossLab
PDF
Практический опыт профайлинга и оптимизации производительности Ruby-приложений
PDF
ch6-pv2-device-drivers
KEY
Fosscon 2012 firewall workshop
PPT
370410176 moshell-commands
Deploying Prometheus stacks with Juju
Solaris Kernel Debugging V1.0
Osol Pgsql
Tracing MariaDB server with bpftrace - MariaDB Server Fest 2021
Pledge in OpenBSD
A little systemtap
David container security-with_falco
Performance Analysis Tools for Linux Kernel
Rac introduction
計算機性能の限界点とその考え方
Kernel Recipes 2017: Performance Analysis with BPF
Linux Tracing Superpowers by Eugene Pirogov
True stories on the analysis of network activity using Python
SSL Failing, Sharing, and Scheduling
Kernel Recipes 2019 - Hunting and fixing bugs all over the Linux kernel
BPF / XDP 8월 세미나 KossLab
Практический опыт профайлинга и оптимизации производительности Ruby-приложений
ch6-pv2-device-drivers
Fosscon 2012 firewall workshop
370410176 moshell-commands
Ad

Viewers also liked (20)

PDF
Debugging Ruby
PDF
Rubinius @ RubyAndRails2010
PDF
Debugging Ruby (Aman Gupta)
PDF
OpenSource Hardware -Debian Way
PPTX
Operating OPNFV
PDF
OpenStack@Mini-Deb Conf'16 Mumbai
PDF
Your first patch to OpenStack
PDF
Copr HD OpenStack Day India
PDF
Deploying openstack using ansible
PPTX
The OpenStack Contribution Workflow
PDF
Your first patch to open stack
PDF
Open stack qa and tempest
PDF
Guts & OpenStack migration
PDF
OpenStack Storage Buddy Ceph
PDF
Introduction to tempest
PPT
Pgcon2012 ori-20120224
PDF
Ryu with OpenFlow 1.3, Traffic Monitor
PPTX
Who carries your container? Zun or Magnum?
PDF
OPNFV & OpenStack
PDF
OpenStack Tempest and REST API testing
Debugging Ruby
Rubinius @ RubyAndRails2010
Debugging Ruby (Aman Gupta)
OpenSource Hardware -Debian Way
Operating OPNFV
OpenStack@Mini-Deb Conf'16 Mumbai
Your first patch to OpenStack
Copr HD OpenStack Day India
Deploying openstack using ansible
The OpenStack Contribution Workflow
Your first patch to open stack
Open stack qa and tempest
Guts & OpenStack migration
OpenStack Storage Buddy Ceph
Introduction to tempest
Pgcon2012 ori-20120224
Ryu with OpenFlow 1.3, Traffic Monitor
Who carries your container? Zun or Magnum?
OPNFV & OpenStack
OpenStack Tempest and REST API testing
Ad

Similar to Debugging Ruby Systems (20)

PDF
How to Make Ruby CGI Script Faster - CGIを高速化する小手先テクニック -
PPTX
Staring into the eBPF Abyss
PPTX
The Next Linux Superpower: eBPF Primer
PDF
Scaling Rails with Ruby-prof -- Ruby Conf Kenya 2017 by Ben Hughes
PDF
Rubinius - What Have You Done For Me Lately?
PDF
A22 Introduction to DTrace by Kyle Hailey
PDF
Designing Tracing Tools
PDF
Профилирование и оптимизация производительности Ruby-кода
PPTX
Debugging linux issues with eBPF
PDF
Interruption Timer Périodique
PDF
Perl at SkyCon'12
PDF
Михаил Епихин — Бутылочное горлышко. как найти узкие места сервиса и увеличит...
PPTX
Designing Tracing Tools
PDF
YOW2020 Linux Systems Performance
PDF
Linux Performance Tools 2014
PDF
Analyzing OS X Systems Performance with the USE Method
PDF
Tips on High Performance Server Programming
PDF
PDF
LISA2019 Linux Systems Performance
PDF
re:Invent 2019 BPF Performance Analysis at Netflix
How to Make Ruby CGI Script Faster - CGIを高速化する小手先テクニック -
Staring into the eBPF Abyss
The Next Linux Superpower: eBPF Primer
Scaling Rails with Ruby-prof -- Ruby Conf Kenya 2017 by Ben Hughes
Rubinius - What Have You Done For Me Lately?
A22 Introduction to DTrace by Kyle Hailey
Designing Tracing Tools
Профилирование и оптимизация производительности Ruby-кода
Debugging linux issues with eBPF
Interruption Timer Périodique
Perl at SkyCon'12
Михаил Епихин — Бутылочное горлышко. как найти узкие места сервиса и увеличит...
Designing Tracing Tools
YOW2020 Linux Systems Performance
Linux Performance Tools 2014
Analyzing OS X Systems Performance with the USE Method
Tips on High Performance Server Programming
LISA2019 Linux Systems Performance
re:Invent 2019 BPF Performance Analysis at Netflix

More from Engine Yard (19)

POTX
Engine Yard Partner Program 2014
PDF
Getting Started with PHP on Engine Yard Cloud
PDF
Engine Yard Cloud Architecture Enhancements
PDF
6 tips for improving ruby performance
PDF
Simplifying PCI on a PaaS Environment
PDF
The Tao of Documentation
PDF
Innovate Faster in the Cloud with a Platform as a Service
PDF
Introduction to Ruby
PDF
JRuby: Enhancing Java Developers Lives
PDF
High Performance Ruby: Evented vs. Threaded
PDF
Release Early & Release Often: Reducing Deployment Friction
PDF
JRuby Jam Session
PDF
Rubinius and Ruby | A Love Story
KEY
Rails Antipatterns | Open Session with Chad Pytel
PDF
JRuby: Apples and Oranges
PDF
Developing a Language
KEY
Geemus
PDF
Everything Rubinius
PDF
Rails Hosting and the Woes
Engine Yard Partner Program 2014
Getting Started with PHP on Engine Yard Cloud
Engine Yard Cloud Architecture Enhancements
6 tips for improving ruby performance
Simplifying PCI on a PaaS Environment
The Tao of Documentation
Innovate Faster in the Cloud with a Platform as a Service
Introduction to Ruby
JRuby: Enhancing Java Developers Lives
High Performance Ruby: Evented vs. Threaded
Release Early & Release Often: Reducing Deployment Friction
JRuby Jam Session
Rubinius and Ruby | A Love Story
Rails Antipatterns | Open Session with Chad Pytel
JRuby: Apples and Oranges
Developing a Language
Geemus
Everything Rubinius
Rails Hosting and the Woes

Recently uploaded (20)

PDF
Unlocking AI with Model Context Protocol (MCP)
PDF
NewMind AI Weekly Chronicles - August'25 Week I
PDF
Chapter 3 Spatial Domain Image Processing.pdf
PDF
Spectral efficient network and resource selection model in 5G networks
PDF
The Rise and Fall of 3GPP – Time for a Sabbatical?
PDF
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PPTX
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
PDF
Dropbox Q2 2025 Financial Results & Investor Presentation
PDF
Modernizing your data center with Dell and AMD
PPTX
Understanding_Digital_Forensics_Presentation.pptx
PDF
NewMind AI Monthly Chronicles - July 2025
PDF
Electronic commerce courselecture one. Pdf
PDF
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
DOCX
The AUB Centre for AI in Media Proposal.docx
PDF
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
PDF
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
PPTX
MYSQL Presentation for SQL database connectivity
PDF
Encapsulation_ Review paper, used for researhc scholars
PPTX
Big Data Technologies - Introduction.pptx
PDF
Agricultural_Statistics_at_a_Glance_2022_0.pdf
Unlocking AI with Model Context Protocol (MCP)
NewMind AI Weekly Chronicles - August'25 Week I
Chapter 3 Spatial Domain Image Processing.pdf
Spectral efficient network and resource selection model in 5G networks
The Rise and Fall of 3GPP – Time for a Sabbatical?
Architecting across the Boundaries of two Complex Domains - Healthcare & Tech...
PA Analog/Digital System: The Backbone of Modern Surveillance and Communication
Dropbox Q2 2025 Financial Results & Investor Presentation
Modernizing your data center with Dell and AMD
Understanding_Digital_Forensics_Presentation.pptx
NewMind AI Monthly Chronicles - July 2025
Electronic commerce courselecture one. Pdf
7 ChatGPT Prompts to Help You Define Your Ideal Customer Profile.pdf
The AUB Centre for AI in Media Proposal.docx
Bridging biosciences and deep learning for revolutionary discoveries: a compr...
Build a system with the filesystem maintained by OSTree @ COSCUP 2025
MYSQL Presentation for SQL database connectivity
Encapsulation_ Review paper, used for researhc scholars
Big Data Technologies - Introduction.pptx
Agricultural_Statistics_at_a_Glance_2022_0.pdf

Debugging Ruby Systems

  • 1. DEBUGGING RUBY Aman Gupta @tmm1 http://tinyurl.com/eyfast
  • 6. THIS TALK IS ABOUT... TOOLS TO FIX THESE ISSUES.
  • 7. TOOLS FOR LINUX. lsof strace ltrace pleeker (flickr)
  • 8. TOOLS FOR C CODE. perftools gdb booddin (flickr)
  • 9. TOOLS FOR NETWORKS. tcpdump ngrep pascal.charest (flickr)
  • 10. TOOLS FOR CPU USAGE. perftools perftools.rb marksze (flickr)
  • 11. TOOLS FOR MEMORY USAGE. bleak_house gdb.rb kgrocki (flickr) memprof
  • 12. IGNORE THE FINE PRINT mayu (flickr) delgrossodotcom (flickr)
  • 13. LSOF list open files lsof -nPp <pid>
  • 14. lsof -nPp <pid> -n Inhibits the conversion of network numbers to host names. -P Inhibits the conversion of port numbers to names for network files FD TYPE NAME json cwd DIR /var/www/myapp memcached txt REG /usr/bin/ruby mysql mem REG /json-1.1.9/ext/json/ext/generator.so http mem REG /json-1.1.9/ext/json/ext/parser.so mem REG /memcached-0.17.4/lib/rlibmemcached.so mem REG /mysql-2.8.1/lib/mysql_api.so 0u CHR /dev/null 1w REG /usr/local/nginx/logs/error.log 2w REG /usr/local/nginx/logs/error.log 3u IPv4 10.8.85.66:33326->10.8.85.68:3306 (ESTABLISHED) 10u IPv4 10.8.85.66:33327->10.8.85.68:3306 (ESTABLISHED) 11u IPv4 127.0.0.1:58273->127.0.0.1:11211 (ESTABLISHED) 12u REG /tmp/RackMultipart.28957.0 33u IPv4 174.36.83.42:37466->69.63.180.21:80 (ESTABLISHED)
  • 15. STRACE trace system calls and signals strace -cp <pid> strace -ttTp <pid> -o <file>
  • 16. strace -cp <pid> -c Count time, calls, and errors for each system call and report a summary on program exit. -p pid Attach to the process with the process ID pid and begin tracing. % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 50.39 0.000064 0 1197 592 read 34.65 0.000044 0 609 writev 14.96 0.000019 0 1226 epoll_ctl 0.00 0.000000 0 4 close 0.00 0.000000 0 1 select 0.00 0.000000 0 4 socket 0.00 0.000000 0 4 4 connect 0.00 0.000000 0 1057 epoll_wait ------ ----------- ----------- --------- --------- ---------------- 100.00 0.000127 4134 596 total
  • 17. strace -ttTp <pid> -o <file> -t Prefix each line of the trace with the time of day. -tt If given twice, the time printed will include the microseconds. -T Show the time spent in system calls. -o filename Write the trace output to the file filename rather than to stderr. epoll_wait(9, {{EPOLLIN, {u32=68841296, u64=68841296}}}, 4096, 50) = 1 <0.033109> accept(10, {sin_port=38313, sin_addr="127.0.0.1"}, [1226]) = 22 <0.000014> fcntl(22, F_GETFL) = 0x2 (flags O_RDWR) <0.000007> fcntl(22, F_SETFL, O_RDWR|O_NONBLOCK) = 0 <0.000008> setsockopt(22, SOL_TCP, TCP_NODELAY, [1], 4) = 0 <0.000008> accept(10, 0x7fff5d9c07d0, [1226]) = -1 EAGAIN <0.000014> epoll_ctl(9, EPOLL_CTL_ADD, 22, {EPOLLIN, {u32=108750368, u64=108750368}}) = 0 <0.000009> epoll_wait(9, {{EPOLLIN, {u32=108750368, u64=108750368}}}, 4096, 50) = 1 <0.000007> read(22, "GET / HTTP/1.1r"..., 16384) = 772 <0.000012> rt_sigprocmask(SIG_SETMASK, [], NULL, 8) = 0 <0.000007> poll([{fd=5, events=POLLIN|POLLPRI}], 1, 0) = 0 (Timeout) <0.000008> write(5, "1000000-0003SELECT * FROM `table`"..., 56) = 56 <0.000023> read(5, "25101,20x234m"..., 16384) = 284 <1.300897>
  • 18. http client connection read 772 bytes read(22, "GET / HTTP/1.1r"..., 16384) = 772 <0.0012> incoming http request took 0.0012s
  • 19. mysql connection write sql query to db write(5, "SELECT * FROM `table`"..., 56) = 56 <0.0023> read(5, "25101,20x234m"..., 16384) = 284 <1.30> read query response slow query
  • 20. stracing ruby: SIGVTALRM --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 2207807 <0.000009> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 0 <0.000009> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 140734552062624 <0.000009> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 140734552066688 <0.000009> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 11333952 <0.000008> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 0 <0.000009> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- rt_sigreturn(0x1a) = 1 <0.000010> --- SIGVTALRM (Virtual timer expired) @ 0 (0) --- • ruby 1.8 uses signals to schedule its green threads • process receives a SIGVTALRM signal every 10ms
  • 21. stracing ruby: sigprocmask % time seconds usecs/call calls errors syscall ------ ----------- ----------- --------- --------- ---------------- 100.00 0.326334 0 3568567 rt_sigprocmask 0.00 0.000000 0 9 read 0.00 0.000000 0 10 open 0.00 0.000000 0 10 close 0.00 0.000000 0 9 fstat 0.00 0.000000 0 25 mmap ------ ----------- ----------- --------- --------- ---------------- 100.00 0.326334 3568685 0 total • debian/redhat compile ruby with --enable-pthread • uses a native thread timer for SIGVTALRM • causes excessive calls to sigprocmask: 30% slowdown!
  • 22. TCPDUMP dump traffic on a network tcpdump -i eth0 -s 0 -nqA tcp dst port 3306
  • 23. tcpdump -i <eth> -s <len> -nqA <expr> tcpdump -i <eth> -w <file> <expr> -i <eth> Network interface. -s <len> Snarf len bytes of data from each packet. -n Don't convert addresses (host addresses, port numbers) to names. -q Quiet output. Print less protocol information. -A Print each packet (minus its link level header) in ASCII. -w <file> Write the raw packets to file rather than printing them out. <expr> libpcap expression, for example: tcp src port 80 tcp dst port 3306
  • 24. tcp dst port 80 19:52:20.216294 IP 24.203.197.27.40105 > 174.37.48.236.80: tcp 438 E...*.@.l.%&.....%0....POx..%s.oP....... GET /poll_images/cld99erh0/logo.png HTTP/1.1 Accept: */* Referer: http://apps.facebook.com/realpolls/? _fb_q=1
  • 25. tcp dst port 3306 19:51:06.501632 IP 10.8.85.66.50443 > 10.8.85.68.3306: tcp 98 E..."K@.@.Yy .UB .UD.....z....L............ GZ.y3b..[......W.... SELECT * FROM `votes` WHERE (`poll_id` = 72621) LIMIT 1
  • 27. PERFTOOLS google's performance tools CPUPROFILE=/tmp/myprof ./myapp pprof ./myapp /tmp/myprof
  • 28. wget http://google-perftools.googlecode.com/files/google- perftools-1.6.tar.gz download tar zxvf google-perftools-1.6.tar.gz cd google-perftools-1.6 ./configure --prefix=/opt make compile sudo make install # for linux export LD_PRELOAD=/opt/lib/libprofiler.so setup # for osx export DYLD_INSERT_LIBRARIES=/opt/lib/libprofiler.dylib CPUPROFILE=/tmp/ruby.prof ruby -e' profile 5_000_000.times{ "hello world" } ' pprof `which ruby` --text /tmp/ruby.prof report
  • 29. pprof ruby pprof ruby ruby.prof --text ruby.prof --gif Total: 103 samples 95 92.2% rb_yield_0 103 100.0% rb_eval 12 11.7% gc_sweep 52 50.5% rb_str_new3 3 2.9% obj_free 103 100.0% int_dotimes 12 11.7% gc_mark
  • 30. Profiling MRI • 10% of production VM time spent in rb_str_sub_bang • String#sub! • called from Time.parse return unless str.sub!(/A(d{1,2})/, '') return unless str.sub!(/A( d|d{1,2})/, '') return unless str.sub!(/A( d|d{1,2})/, '') return unless str.sub!(/A(d{1,3})/, '') return unless str.sub!(/A(d{1,2})/, '') return unless str.sub!(/A(d{1,2})/, '')
  • 31. Profiling EM + threads Total: 3763 samples 2764 73.5% catch_timer 989 26.3% memcpy 3 0.1% st_lookup 2 0.1% rb_thread_schedule 1 0.0% rb_eval 1 0.0% rb_newobj 1 0.0% rb_gc_force_recycle • known issue: EM+threads = slow • memcpy?? • thread context switches copy the stack w/ memcpy • EM allocates huge buffer on the stack • solution: move buffer to the heap
  • 32. PERFTOOLS.RB perftools for ruby code pprof.rb /tmp/myrbprof github.com/tmm1/perftools.rb
  • 33. gem install perftools.rb RUBYOPT="-r`gem which perftools | tail -1`" CPUPROFILE=/tmp/myrbprof ruby myapp.rb pprof.rb /tmp/myrbprof --text pprof.rb /tmp/myrbprof --gif > /tmp/myrbprof.gif
  • 34. require 'sinatra' $ ab -c 1 -n 50 http://127.0.0.1:4567/compute $ ab -c 1 -n 50 http://127.0.0.1:4567/sleep get '/sleep' do sleep 0.25 'done' • Sampling profiler: end • 232 samples total get '/compute' do • 83 samples were in /compute proc{ |n| a,b=0,1 • 118 samples had /compute on the stack but were in n.times{ a,b = b,a+b } another function b }.call(10_000) • /compute accounts for 50% 'done' of process, but only 35% of time was in /compute itself end == Sinatra has ended his set (crowd applauds) PROFILE: interrupts/evictions/bytes = 232/0/2152 Total: 232 samples 83 35.8% 35.8% 118 50.9% Sinatra::Application#GET /compute 56 24.1% 59.9% 56 24.1% garbage_collector 35 15.1% 75.0% 113 48.7% Integer#times
  • 35. CPUPROFILE_REALTIME=1 CPUPROFILE=app.prof CPUPROFILE=app-rt.prof
  • 38. faster bundle install • 23% spent in Gem::Version#<=> • simple patch to rubygems improved overall install performance by 15% • http://gist.github.com/ 458185
  • 39. CPUPROFILE_OBJECTS=1 CPUPROFILE=app-objs.prof • object allocation profiler mode built-in • 1 sample = 1 object created • Time parsing is both CPU and object allocation intensive • using mysql2 moves this to C
  • 40. LTRACE trace library calls ltrace -cp <pid> ltrace -ttTp <pid> -o <file>
  • 41. ltrace -c ruby threaded_em.rb % time seconds usecs/call calls function ------ ----------- ----------- --------- -------------------- 48.65 11.741295 617 19009 memcpy 30.16 7.279634 831 8751 longjmp 9.78 2.359889 135 17357 _setjmp 8.91 2.150565 285 7540 malloc 1.10 0.265946 20 13021 memset 0.81 0.195272 19 10105 __ctype_b_loc 0.35 0.084575 19 4361 strcmp 0.19 0.046163 19 2377 strlen 0.03 0.006272 23 265 realloc ------ ----------- ----------- --------- -------------------- 100.00 24.134999 82999 total ltrace -ttT -e memcpy ruby threaded_em.rb 01:24:48.769408 --- SIGVTALRM (Virtual timer expired) --- 01:24:48.769616 memcpy(0x1216000, "", 1086328) = 0x1216000 <0.000578> 01:24:48.770555 memcpy(0x6e32670, "240&343v", 1086328) = 0x6e32670 <0.000418> 01:24:49.899414 --- SIGVTALRM (Virtual timer expired) --- 01:24:49.899490 memcpy(0x1320000, "", 1082584) = 0x1320000 <0.000628> 01:24:49.900474 memcpy(0x6e32670, "", 1086328) = 0x6e32670 <0.000479>
  • 42. LTRACE/LIBDL trace dlopen’d library calls ltrace -F <conf> -bg -x <symbol> -p <pid> github.com/ice799/ltrace/tree/libdl
  • 43. ltrace -F <conf> -b -g -x <sym> -b Ignore signals. -g Ignore libraries linked at compile time. -F <conf> Read prototypes from config file. -x <sym> Trace calls to the function sym. -s <num> Show first num bytes of string args. -F ltrace.conf int mysql_real_query(addr,string,ulong); void garbage_collect(void); int memcached_set(addr,string,ulong,string,ulong);
  • 44. ltrace -x garbage_collect 19:08:06.436926 garbage_collect() = <void> <0.221679> 19:08:15.329311 garbage_collect() = <void> <0.187546> 19:08:17.662149 garbage_collect() = <void> <0.199200> 19:08:20.486655 garbage_collect() = <void> <0.205864> 19:08:25.102302 garbage_collect() = <void> <0.214295> 19:08:35.552337 garbage_collect() = <void> <0.189172>
  • 45. ltrace -x mysql_real_query mysql_real_query(0x1c9e0500, "SET NAMES 'UTF8'", 16) = 0 <0.000324> mysql_real_query(0x1c9e0500, "SET SQL_AUTO_IS_NULL=0", 22) = 0 <0.000322> mysql_real_query(0x19c7a500, "SELECT * FROM `users`", 21) = 0 <1.206506> mysql_real_query(0x1c9e0500, "COMMIT", 6) = 0 <0.000181>
  • 46. ltrace -x memcached_set memcached_set(0x15d46b80, "Status:33", 21, "004b", 366) = 0 <0.01116> memcached_set(0x15d46b80, "Status:96", 21, "004b", 333) = 0 <0.00224> memcached_set(0x15d46b80, "Status:57", 21, "004b", 298) = 0 <0.01850> memcached_set(0x15d46b80, "Status:10", 21, "004b", 302) = 0 <0.00530> memcached_set(0x15d46b80, "Status:67", 21, "004b", 318) = 0 <0.00291> memcached_set(0x15d46b80, "Status:02", 21, "004b", 299) = 0 <0.00658> memcached_set(0x15d46b80, "Status:34", 21, "004b", 264) = 0 <0.00243>
  • 47. GDB the GNU debugger gdb <executable> gdb attach <pid>
  • 48. Debugging Ruby Segfaults test_segv.rb:4: [BUG] Segmentation fault ruby 1.8.7 (2008-08-11 patchlevel 72) [i686-darwin9.7.0] def test #include "ruby.h" require 'segv' 4.times do VALUE Dir.chdir '/tmp' do segv() Hash.new{ segv }[0] { end VALUE array[1]; end array[1000000] = NULL; end return Qnil; } sleep 10 test() void Init_segv() { rb_define_method(rb_cObject, "segv", segv, 0); }
  • 49. 1. Attach to running process $ ps aux | grep ruby joe 23611 0.0 0.1 25424 7540 S Dec01 0:00 ruby test_segv.rb $ sudo gdb ruby 23611 Attaching to program: ruby, process 23611 0x00007fa5113c0c93 in nanosleep () from /lib/libc.so.6 (gdb) c Continuing. Program received signal SIGBUS, Bus error. segv () at segv.c:7 7 array[1000000] = NULL; 2. Use a coredump Process.setrlimit Process::RLIMIT_CORE, 300*1024*1024 $ sudo mkdir /cores $ sudo chmod 777 /cores $ sudo sysctl kernel.core_pattern=/cores/%e.core.%s.%p.%t $ sudo gdb ruby /cores/ruby.core.6.23611.1259781224
  • 50. def test require 'segv' 4.times do Dir.chdir '/tmp' do Hash.new{ segv }[0] end end (gdb) where end #0 segv () at segv.c:7 #1 0x000000000041f2be in call_cfunc () at eval.c:5727 test() ... #13 0x000000000043ba8c in rb_hash_default () at hash.c:521 ... #19 0x000000000043b92a in rb_hash_aref () at hash.c:429 ... #26 0x00000000004bb7bc in chdir_yield () at dir.c:728 #27 0x000000000041d8d7 in rb_ensure () at eval.c:5528 #28 0x00000000004bb93a in dir_s_chdir () at dir.c:816 ... #35 0x000000000041c444 in rb_yield () at eval.c:5142 #36 0x0000000000450690 in int_dotimes () at numeric.c:2834 ... #48 0x0000000000412a90 in ruby_run () at eval.c:1678 #49 0x000000000041014e in main () at main.c:48
  • 51. GDB.RB gdb with MRI hooks gem install gdb.rb gdb.rb <pid> github.com/tmm1/gdb.rb
  • 52. (gdb) ruby eval 1+2 3 (gdb) ruby eval Thread.current #<Thread:0x1d630 run> (gdb) ruby eval Thread.list.size 8
  • 53. (gdb) ruby threads list 0x15890 main thread THREAD_STOPPED WAIT_JOIN(0x19ef4) 0x19ef4 thread THREAD_STOPPED WAIT_TIME(57.10s) 0x19e34 thread THREAD_STOPPED WAIT_FD(5) 0x19dc4 thread THREAD_STOPPED WAIT_NONE 0x19dc8 thread THREAD_STOPPED WAIT_NONE 0x19dcc thread THREAD_STOPPED WAIT_NONE 0x22668 thread THREAD_STOPPED WAIT_NONE 0x1d630 curr thread THREAD_RUNNABLE WAIT_NONE
  • 54. (gdb) ruby objects HEAPS 8 SLOTS 1686252 LIVE 893327 (52.98%) FREE 792925 (47.02%) scope 1641 (0.18%) regexp 2255 (0.25%) data 3539 (0.40%) class 3680 (0.41%) hash 6196 (0.69%) object 8785 (0.98%) array 13850 (1.55%) string 105350 (11.79%) node 742346 (83.10%)
  • 55. (gdb) ruby objects strings 140 u'lib' 158 u'0' 294 u'n' 619 u'' 30503 unique strings 3187435 bytes
  • 56. def test require 'segv' 4.times do Dir.chdir '/tmp' do Hash.new{ segv }[0] end end end (gdb) ruby threads test() 0xa3e000 main curr thread THREAD_RUNNABLE WAIT_NONE node_vcall segv in test_segv.rb:5 node_call test in test_segv.rb:5 node_call call in test_segv.rb:5 node_call default in test_segv.rb:5 node_call [] in test_segv.rb:5 node_call test in test_segv.rb:4 node_call chdir in test_segv.rb:4 node_call test in test_segv.rb:3 node_call times in test_segv.rb:3 node_vcall test in test_segv.rb:9
  • 57. rails_warden leak (gdb) ruby objects classes 1197 MIME::Type 2657 NewRelic::MetricSpec 2719 TZInfo::TimezoneTransitionInfo 4124 Warden::Manager 4124 MethodOverrideForAll 4124 AccountMiddleware 4124 Rack::Cookies 4125 ActiveRecord::ConnectionAdapters::ConnectionManagement 4125 ActionController::Session::CookieStore 4125 ActionController::Failsafe 4125 ActionController::ParamsParser 4125 Rack::Lock 4125 ActionController::Dispatcher 4125 ActiveRecord::QueryCache 4125 ActiveSupport::MessageVerifier 4125 Rack::Head middleware chain leaking per request
  • 58. mongrel sleeper thread 0x16814c00 thread THREAD_STOPPED WAIT_TIME(0.47) 1522 bytes node_fcall sleep in lib/mongrel/configurator.rb:285 node_fcall run in lib/mongrel/configurator.rb:285 node_fcall loop in lib/mongrel/configurator.rb:285 node_call run in lib/mongrel/configurator.rb:285 node_call initialize in lib/mongrel/configurator.rb:285 node_call new in lib/mongrel/configurator.rb:285 node_call run in bin/mongrel_rails:128 node_call run in lib/mongrel/command.rb:212 node_call run in bin/mongrel_rails:281 node_fcall (unknown) in bin/mongrel_rails:19 def run @listeners.each {|name,s| s.run } $mongrel_sleeper_thread = Thread.new { loop { sleep 1 } } end
  • 59. god memory leaks (gdb) ruby objects arrays 43 God::Process elements instances 43 God::Watch 94310 3 43 God::Driver 94311 3 43 God::DriverEventQueue 94314 2 43 God::Conditions::MemoryUsage 94316 1 43 God::Conditions::ProcessRunning 43 God::Behaviors::CleanPidFile 5369 arrays 45 Process::Status 2863364 member elements 86 God::Metric 327 God::System::SlashProcPoller many arrays with 327 God::System::Process 90k+ elements! 406 God::DriverEvent 5 separate god leaks fixed by Eric Lindvall with the help of gdb.rb!
  • 60. MEMPROF a heap visualizer for ruby gem install memprof open http://memprof.com github.com/ice799/memprof
  • 61. memprof features • memprof.track • memprof.dump • memprof.dump_all • memprof.com
  • 62. Memprof.track{ 100.times{ "abc" } 100.times{ 1.23 + 1 } 100.times{ Module.new } } 100 file.rb:2:String 100 file.rb:3:Float 100 file.rb:4:Module • like bleak_house, but for a given block of code • use Memprof::Middleware in your webapps to run track per request
  • 63. memprof features • memprof.track • memprof.dump • memprof.dump_all • memprof.com
  • 64. Memprof.dump{ strings } "hello" + "world" { "_id": "0x19c610", memory address of object "file": "file.rb", file and line where string "line": 2, was created "type": "string", "class": "0x1ba7f0", address of the class "class_name": "String", “String” "length": 10, length and contents "data": "helloworld" of this string instance }
  • 65. arrays Memprof.dump{ [ 1, :b, { "_id": "0x19c5c0", 2.2, "d" "class": "0x1b0d18", ] "class_name": "Array", } "length": 4, "data": [ 1, integers and symbols are ":b", stored in the array itself "0x19c750", floats and strings are "0x19c598" separate ruby objects ] }
  • 66. hashes Memprof.dump{ { :a => 1, "b" => 2.2 { } "_id": "0x19c598", } "type": "hash", "class": "0x1af170", "class_name": "Hash", "default": null, no default proc "length": 2, "data": [ [ ":a", 1 ], hash entries as key/value [ "0xc728", "0xc750" ] pairs ] }
  • 67. classes Memprof.dump{ class Hello @@var=1 Const=2 { def world() end "_id": "0x19c408", end "type": "class", } "name": "Hello", "super": "0x1bfa48", superclass object reference "super_name": "Object", "ivars": { class variables and constants "@@var": 1, are stored in the instance "Const": 2 }, variable table "methods": { "world": "0x19c318" references to method objects } }
  • 68. memprof features • memprof.track • memprof.dump • memprof.dump_all • memprof.com
  • 69. Memprof.dump_all("myapp_heap.json") • dump out every single live object as json • one per line to specified file • analyze via • jsawk/grep • mongodb/couchdb • custom ruby scripts • libyajl + Boost Graph Library
  • 70. memprof features • memprof.track • memprof.dump • memprof.dump_all • memprof.com
  • 71. memprof.com a web-based heap visualizer and leak analyzer
  • 72. plugging a leak in rails3 • in dev mode, rails3 is leaking 10mb per request let’s use memprof to find it! # in environment.rb require `gem which memprof/signal`.strip
  • 73. plugging a leak in rails3 send the app some requests so it leaks $ ab -c 1 -n 30 http://localhost:3000/ tell memprof to dump out the entire heap to json $ memprof --pid <pid> --name <dump name> --key <api key>
  • 74. 2519 classes 30 copies of TestController mongo query for all TestController classes details for one copy of TestController
  • 75. find references to object “leak” is on line 178 holding references to all controllers
  • 76. • In development mode, Rails reloads all your application code on every request • ActionView::Partials::PartialRenderer is caching partials used by each controller as an optimization • But.. it ends up holding a reference to every single reloaded version of those controllers
  • 77. MORE* MEMPROF FEATURES • memprof.trace • memprof::tracer * currently under development
  • 78. config.middleware.use(Memprof::Tracer) { "time": 4.3442, total time for request "rails": { rails controller/action "controller": "test", "action": "index" }, "request": { request env info "REQUEST_PATH": "/test", "REQUEST_METHOD": "GET" },
  • 79. config.middleware.use(Memprof::Tracer) "mysql": { "queries": 3, 3 mysql queries "time": 0.00109302 }, "gc": { "calls": 8, 8 calls to GC "time": 2.04925 2 secs spent in GC },
  • 80. config.middleware.use(Memprof::Tracer) "objects": { "created": 3911103, 3 million objs created "types": { "none": 1168831, 1 million method calls "object": 1127, object instances "float": 627, "string": 1334637, lots of strings "array": 609313, lots of arrays "hash": 3676, "match": 70211 regexp matches } } }
  • 81. RACK-PERFTOOLS rack middleware for perftools.rb gem install rack- perftools_profiler github.com/bhb/rack-perftools_profiler
  • 82. require 'rack/perftools_profiler' config.middleware.insert( 0, Rack::PerftoolsProfiler, :default_printer => 'gif' ) $ curl http://localhost:3000/__start__ $ curl http://localhost:3000/home $ curl http://localhost:3000/about $ curl http://localhost:3000/__stop__ $ curl http://localhost:3000/__data__ -o profile.gif $ curl http://localhost:3000/__data__?printer=text -o profile.txt $ curl "http://localhost:3000/home?profile=true&times=10" -o 10_requests_to_homepage.gif
  • 84. LEARN THESE TOOLS. USE THESE TOOLS. yujie (flickr)
  • 85. EY CASE STUDY • Example of Professional Services engagement • Interested? http://tinyurl.com/eyfast http://tinyurl.com/eyfast
  • 86. LSOF 15u IPv4 TCP ->10.243.63.80:11211 (ESTABLISHED) 20u IPv4 TCP ->10.243.63.80:11211 (ESTABLISHED) 23u IPv4 TCP ->10.243.63.80:11211 (ESTABLISHED) multiple connections to memcached via different drivers 18r DIR /shared/public/javascripts/packaged 19r DIR /shared/public/javascripts/packaged 22r DIR /shared/public/javascripts/packaged multiple open handles to javascript assets directories
  • 87. STRACE % time seconds calls syscall ------ ----------- --------- --------- 26.28 0.054178 8731 read 25.81 0.053216 316519 stat 20.37 0.041993 1 clone 15.83 0.032648 11034 getdents 3.54 0.007309 10326 write 40% of kernel time querying filesystem
  • 88. LTRACE mysql_query("SELECT * FROM tags WHERE id = 9129") mysql_query("SELECT * FROM tags WHERE id = 9129") mysql_query("SELECT * FROM tag_info WHERE tag_id = 9129") mysql_query("SELECT * FROM tags WHERE id = 9129") mysql_query("SELECT * FROM tags WHERE id = 9129") mysql_query("SELECT * FROM tags WHERE id = 9129") common queries repeated multiple times during a request
  • 89. GDB.RB (gdb) ruby objects HEAPS 10 SLOTS 4450607 LIVE 2006769 (45.09%) FREE 2443838 (54.91%) large ruby heap hash 12939 (0.64%) numerous 'node' array 38007 (1.89%) objects- required object 47633 (2.37%) libraries and plugins string 234205 (11.67%) that are no longer in node 1637926 (81.62%) use
  • 90. PERFTOOLS.RB majority of ruby process' time on CPU spent in garbage collection
  • 91. MEMPROF wide distribution of response times expensive controller actions creating many millions of objects per request
  • 92. MEMPROF 37% of response time attributed to garbage collection
  • 93. BEFORE Time taken for tests: 20.156 seconds Complete requests: 100 Requests per second: 4.96 [#/sec] (mean) Time per request: 201.555 [ms] (mean) AFTER Time taken for tests: 8.100 seconds Complete requests: 100 Requests per second: 12.35 [#/sec] (mean) Time per request: 81.000 [ms] (mean) 2.5x improvement in throughput after addressing some of these issues and tuning GC
  • 94. QUESTIONS? Aman Gupta @tmm1 http://scr.bi/debuggingruby