Skip to content

Instantly share code, notes, and snippets.

@tuanmai
Last active April 21, 2016 07:21
Show Gist options
  • Save tuanmai/465d6128ea1644aafac130c03e807357 to your computer and use it in GitHub Desktop.
Save tuanmai/465d6128ea1644aafac130c03e807357 to your computer and use it in GitHub Desktop.
# rbtrace -p PID -e 'Thread.new{require "objspace"; ObjectSpace.trace_object_allocations_start; GC.start(); ObjectSpace.dump_all(output: File.open("heap_#{Time.now.to_i}.json", "w"))}.join'
# Gather three snapshots
# Remove common objects in snapshot 1 from snapshot 2
# Remove missing objects in snapshot 3 from snapshot 2
#!/usr/bin/env ruby
require 'set'
require 'json'
if ARGV.length != 3
puts "Usage: detect_leaks [FIRST.json] [SECOND.json] [THIRD.json]"
exit 1
end
first_addrs = Set.new
third_addrs = Set.new
# Get a list of memory addresses from the first dump
File.open(ARGV[0], "r").each_line do |line|
parsed = JSON.parse(line)
first_addrs << parsed["address"] if parsed && parsed["address"]
end
# Get a list of memory addresses from the last dump
File.open(ARGV[2], "r").each_line do |line|
parsed = JSON.parse(line)
third_addrs << parsed["address"] if parsed && parsed["address"]
end
diff = []
# Get a list of all items present in both the second and
# third dumps but not in the first.
File.open(ARGV[1], "r").each_line do |line|
parsed = JSON.parse(line)
if parsed && parsed["address"]
if !first_addrs.include?(parsed["address"]) && third_addrs.include?(parsed["address"])
diff << parsed
end
end
end
# Group items
diff.group_by do |x|
[x["type"], x["file"], x["line"]]
end.map do |x,y|
# Collect memory size
[x, y.count, y.inject(0){|sum,i| sum + (i['bytesize'] || 0) }, y.inject(0){|sum,i| sum + (i['memsize'] || 0) }]
end.sort do |a,b|
b[1] <=> a[1]
end.each do |x,y,bytesize,memsize|
# Output information about each potential leak
puts "Leaked #{y} #{x[0]} objects of size #{bytesize}/#{memsize} at: #{x[1]}:#{x[2]}"
end
# Also output total memory usage, because why not?
memsize = diff.inject(0){|sum,i| sum + (i['memsize'] || 0) }
bytesize = diff.inject(0){|sum,i| sum + (i['bytesize'] || 0) }
puts "\n\nTotal Size: #{bytesize}/#{memsize}"
# OUTPUT
# Leaked 77 STRING objects of size 10947/10961 at: /Users/peterwagenet/.rvm/gems/ruby-2.1.3/gems/actionview-4.1.6/lib/action_view/template.rb:297
# Leaked 15 DATA objects of size 0/51808 at: /Users/peterwagenet/.rvm/gems/ruby-2.1.3/gems/actionview-4.1.6/lib/action_view/template.rb:297
# Leaked 1 FILE objects of size 0/216 at: /Users/peterwagenet/.rvm/gems/ruby-2.1.3/gems/puma-2.7.1/lib/puma/server.rb:290
# Leaked 1 DATA objects of size 0/0 at: /Users/peterwagenet/.rvm/gems/ruby-2.1.3/gems/puma-2.7.1/lib/puma/client.rb:35
# Total Size: 11767/68771
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment