NAME dike SYNOPSIS a simple memory leak detector for ruby with preconfigured rails hooks. INSTALL gem install dike URIS http://www.codeforpeople.com/lib/ruby/ http://rubyforge.org/projects/codeforpeople/ DESCRIPTION the concept behind dike.rb is simple: class Object is extended in order that the location of each object's creation is tracked. a summarizer command is given to walk ObjectSpace using each object's class and the location if it's creation to detect memory leaks. not all leaks can be detected and some that are may not really be leaks, but dike provided a simple way to see the hotspots in your code that may potentially be leaking. HISTORY 0.0.4: - under rare circumstances dike itself interacted strangely with certain classes and caused them to leak, HTTPOK was one such example. this release fixes that bug. thanks to Jan Kubr for providing a great test case that helped me fix this. EXAMPLES ### PURE RUBY ## just dumping sequential snapshots to stderr, looking at a specific class # cfp:~ > cat sample/a.rb require 'dike' class Leak < ::String end Leaks = Array.new Dike.filter Leak loop do Leaks << Leak.new('leak' * 1024) Dike.finger sleep 1 end # cfp:~ > ruby sample/a.rb | less --- - class: Leak count: 2 trace: - sample/a.rb:11 - sample/a.rb:10:in `loop' - sample/a.rb:10 --- - class: Leak count: 3 trace: - sample/a.rb:11 - sample/a.rb:10:in `loop' - sample/a.rb:10 --- - class: Leak count: 4 trace: - sample/a.rb:11 - sample/a.rb:10:in `loop' - sample/a.rb:10 --- - class: Leak count: 5 trace: - sample/a.rb:11 - sample/a.rb:10:in `loop' - sample/a.rb:10 --- - class: Leak count: 6 trace: - sample/a.rb:11 - sample/a.rb:10:in `loop' - sample/a.rb:10 ## dumping sequential snapshots using Dike.logfactory and then using the 'dike' command line tool to do comparisons of the dumped snapshots # cfp:~ > cat sample/b.rb require 'dike' Leaks = Array.new class Leak def initialize @leak = 42.chr * (2 ** 20) end end Dike.logfactory './log/' Dike.finger 3.times{ Leaks << Leak.new } Dike.finger 2.times{ Leaks << Leak.new } Dike.finger # cfp:~ > ruby sample/b.rb # cfp:~ > ls log/ 0 1 2 # cfp:~ > dike log/ --- - class: Leak count: 3 trace: - sample/b.rb:15 - sample/b.rb:15:in `times' - sample/b.rb:15 - class: Leak count: 2 trace: - sample/b.rb:19 - sample/b.rb:19:in `times' - sample/b.rb:19 # cfp:~ > dike log/0 log/1 --- - class: Leak count: 3 trace: - sample/b.rb:15 - sample/b.rb:15:in `times' - sample/b.rb:15 # cfp:~ > dike log/1 log/2 --- - class: Leak count: 2 trace: - sample/b.rb:19 - sample/b.rb:19:in `times' - sample/b.rb:19 ### RAILS # cfp:~ > cat ./config/environment.rb ... require 'dike' Dike.on :rails # cfp:~ > ./script/server # cfp:~ > curl --silent http://localhost:3000 >/dev/null # cfp:~ > cat ./log/dike/0 --- - class: String count: 90769 trace: [] - class: Array count: 18931 trace: [] - class: Class count: 2 trace: - votelink.com/public/../config/../lib/widgets.rb:222:in `class_factory' - votelink.com/public/../config/../lib/widgets.rb:220:in `each' - votelink.com/public/../config/../lib/widgets.rb:220:in `class_factory' - votelink.com/public/../config/../lib/widgets.rb:248:in `Widget' - votelink.com/public/../config/../lib/widgets/page/base.rb:1 - votelink.com/public/../config/../lib/widgets.rb:31:in `require' - votelink.com/public/../config/../lib/widgets.rb:31:in `load' - votelink.com/public/../config/../lib/widgets.rb:16:in `for_controller' - votelink.com/public/../config/../lib/widgets.rb:243:in `widget' - votelink.com/public/../config/../app/controllers/application.rb:150 ... # cfp:~ > curl --silent http://localhost:3000 >/dev/null # cfp:~ > cat ./log/dike/1 --- - class: String count: 100769 trace: [] - class: Array count: 19931 trace: [] - class: Class count: 5 trace: - votelink.com/public/../config/../lib/widgets.rb:222:in `class_factory' - votelink.com/public/../config/../lib/widgets.rb:220:in `each' - votelink.com/public/../config/../lib/widgets.rb:220:in `class_factory' - votelink.com/public/../config/../lib/widgets.rb:248:in `Widget' - votelink.com/public/../config/../lib/widgets/page/base.rb:1 - votelink.com/public/../config/../lib/widgets.rb:31:in `require' - votelink.com/public/../config/../lib/widgets.rb:31:in `load' - votelink.com/public/../config/../lib/widgets.rb:16:in `for_controller' - votelink.com/public/../config/../lib/widgets.rb:243:in `widget' - votelink.com/public/../config/../app/controllers/application.rb:150 ... # cfp:~ > dike ./log/dike ... - class: Class count: 3 trace: - votelink.com/public/../config/../lib/widgets.rb:222:in `class_factory' - votelink.com/public/../config/../lib/widgets.rb:220:in `each' - votelink.com/public/../config/../lib/widgets.rb:220:in `class_factory' - votelink.com/public/../config/../lib/widgets.rb:248:in `Widget' - votelink.com/public/../config/../lib/widgets/page/base.rb:1 - votelink.com/public/../config/../lib/widgets.rb:31:in `require' - votelink.com/public/../config/../lib/widgets.rb:31:in `load' - votelink.com/public/../config/../lib/widgets.rb:16:in `for_controller' - votelink.com/public/../config/../lib/widgets.rb:243:in `widget' - votelink.com/public/../config/../app/controllers/application.rb:150 ... NOTES * the 'Dike.finger' method dumps it's log in a format showing class : the class of object being leaked/allocated count : the number instances leaked from the trace location trace : the trace location of object creation * loading into a rails environment causes snapshots of the above format to be dumped into RAILS_ROOT/log/dike/ after each request. each snapshot is incrementally numbered 0, 1, ... * the 'dike' command line tool can be used in two ways dike directory/with/logs/dike/ dike old_dump new_dump if given a directory 'old_dump' and 'new_dump' are auto-calculated by scanning the directory. in either case the tool dups a delta running old -->> new. the delta shows only changes from old to new, so a line like - class: Proc count: 3 ... means that 3 Proc objects were created between the two dumps. note that, when given a directory, the default old and new dumps are the oldest and newest dumps respectively, to get fine grained information sumarizing the changes between two requests give the files manually, for example dike ./log/dike/41 ./log/dike/42 * options that affect logging - Dike.filter pattern pattern must respond to '===' and each object in ObjectSpace will be compared against it. for example Dile.filter Array would cause logging to restrict itself to Array, or sublcasses of Array - Dike.log io set the dike logging object. the object should respond to 'puts'. - Dike.logfactory directory cause logging to occur into a new log for each call the 'Dike.finger'. the logs will be auto numbered 0, 1, ... LIMITATIONS not all object creation can be tracked. not all leaks are reported. some reported leaks are not. dike shows you where in the source objects are being created that cannot be reclaimed - these are not always leaks as this line class C; end shows. the class 'C' cannot be reclaimed and yet is not a leak. AUTHOR ara [dot] t [dot] howard [at] gmail [dot] com