Last active
April 21, 2016 07:21
-
-
Save tuanmai/465d6128ea1644aafac130c03e807357 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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