Skip to content

Instantly share code, notes, and snippets.

@NickLaMuro
Created October 25, 2017 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save NickLaMuro/293105bc7e005323fb00ae005fd8284a to your computer and use it in GitHub Desktop.
Save NickLaMuro/293105bc7e005323fb00ae005fd8284a to your computer and use it in GitHub Desktop.
Memory Dump Analyzer

This is a adaptation of some of the ObjectSpace.dump_all analyisis scripts found in Sam Saffron's article on "Debugging memory leaks in Ruby":

https://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby

The input files are also generated in a similar fashion that is described in the blog post:

io=File.open("/tmp/my_dump", "w")
ObjectSpace.dump_all(output: io); 
io.close

And assumes that you are currently running with ObjectSpace.trace_object_allocations_start (see the post for more details).

That said, this script supports working with gzipped files, which is highly recommend that you zip up the output files that you do dump, as they are quite large (500MB a piece in my case).

Usage:

There are two modes for the script:

Summary mode

Without any flag, the script will keep track of all object allocations and print out the counts, grouped by GC generation.

Specific generation analysis

The -g/--generation flag allows for displaying the more versbose output described in the post, which displays the counts of the currently allocated objects for a specific generation, grouped by line and line number.

require 'optparse'
options = { :generation => nil }
OptionParser.new do |opt|
opt.banner = "Usage: #{File.basename $0} [options] DUMPFILE"
opt.separator ""
opt.separator "Parses the given DUMPFILE (output from ObjectSpace.dump_all)"
opt.separator "and either gives the generation statisics or the specific"
opt.separator "memory locations of the objects in a given generation."
opt.separator ""
opt.separator "Files can either be the raw json data, or their gzipped"
opt.separator "equivalents, and the parser will figure out how to handle"
opt.separator "them accordingly."
opt.separator ""
opt.separator "Options"
opt.on("-gNUM", "--generation=NUM", Integer, "Method to analyze") do |val|
options[:generation] = val.to_i
end
opt.on("-h", "--help", "Show this message") do
puts opt
exit
end
end.parse!
require 'json'
require 'zlib'
class Analyzer
def initialize filename, options = {}
@filename = filename
@generation = options[:generation]
@data = []
end
def analyze
io_klass.open(@filename) do |f|
f.each_line do |line|
parsed = JSON.parse(line)
@data << parsed if @generation.nil? || parsed["generation"] == @generation
end
end
@generation ? print_for_speicific_generation : print_generation_count
end
private
def print_generation_count
@data.group_by { |row| row["generation"] }
.sort { |a,b| a[0].to_i <=> b[0].to_i }
.each { |k,v| puts "generation #{k} objects #{v.count}" }
end
def print_for_speicific_generation
puts "Generation #{@generation}:\n"
@data.group_by { |row| "#{row["file"]}:#{row["line"]}" }
.sort { |a,b| b[1].count <=> a[1].count }
.each { |k,v| puts "#{k} * #{v.count}" }
end
def io_klass
File.extname(@filename) == ".gz" ? Zlib::GzipReader : File
end
end
Analyzer.new(ARGV[0], options).analyze
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment