class Heapy::Diff

Diff 2 dumps example:

Heapy::Diff.new(before: 'my_dump_1.json', after: 'my_dump_2.json').call

This will find objects that are present in my_dump_2 that are not present in my_dump_1 this means they were allocated sometime between the two heap dumps.

Diff 3 dumps example:

Heapy::Diff.new(before: 'my_dump_1.json', after: 'my_dump_2.json', retained: 'my_dump_3.json').call

This will find objects that are present in my_dump_2 that are not present in my_dump_1 but only if the objects are still present at the time that my_dump_3 was taken. This does not guarantee that they're retained forever, but were still present at the time the last dump was taken.

You can output the diff of heap dumps by passing in a filename as `output_diff` for example

Heapy::Diff.new(before: 'my_dump_1.json', after: 'my_dump_2.json', outpu_diff: 'out.json').call

Attributes

diff[R]

Public Class Methods

new(before:, after:, retained: nil, io: STDOUT, output_diff: nil) click to toggle source
# File lib/heapy/diff.rb, line 27
def initialize(before:, after:, retained: nil, io: STDOUT, output_diff: nil)
  @before_file = before
  @after_file = after
  @retained_file = retained
  @output_diff_file = output_diff ? File.open(output_diff, "w+") : nil
  @io = io
  @diff = Hash.new { |hash, k|
    hash[k] = {}
    hash[k]["count"] = 0
    hash[k]["memsize"] = 0
    hash[k]
  }

  @before_address_hash = {}
  @retained_address_hash = {}
end

Public Instance Methods

call() click to toggle source
# File lib/heapy/diff.rb, line 45
def call
  read(@before_file) { |parsed| @before_address_hash[parsed['address']] = true }
  read(@retained_file) { |parsed| @retained_address_hash[parsed['address']] = true } if @retained_file

  read(@after_file) do |parsed, original_line|
    address = parsed['address']
    next if previously_allocated?(address)
    next if not_retained?(address)

    @output_diff_file.puts original_line if @output_diff_file

    hash = diff["#{parsed['type']},#{parsed['file']},#{parsed['line']}"]
    hash["count"] += 1
    hash["memsize"] += parsed["memsize"] || 0
    hash["type"] ||= parsed["type"]
    hash["file"] ||= parsed["file"]
    hash["line"] ||= parsed["line"]
  end

  @output_diff_file.close if @output_diff_file
  @before_address_hash.clear
  @retained_address_hash.clear

  total_memsize = diff.inject(0){|sum,(_,v)| sum + v["memsize"] }

  diff.sort_by do |k,v|
    v["count"]
  end.reverse.each do |key, data|
    @io.puts "#{@retained_file ? "Retained" : "Allocated"} #{data['type']} #{data['count']} objects of size #{data['memsize']}/#{total_memsize} (in bytes) at: #{data['file']}:#{data['line']}"
  end

  @io.puts "\nWriting heap dump diff to #{@output_diff_file.path}\n" if @output_diff_file
end

Private Instance Methods

is_retained?(address) click to toggle source
# File lib/heapy/diff.rb, line 79
        def is_retained?(address)
  return true if @retained_file.nil?
  @retained_address_hash[address]
end
not_retained?(address) click to toggle source
# File lib/heapy/diff.rb, line 84
        def not_retained?(address)
  !is_retained?(address)
end
previously_allocated?(address) click to toggle source
# File lib/heapy/diff.rb, line 88
        def previously_allocated?(address)
  @before_address_hash[address]
end
read(filename) { |parsed, line| ... } click to toggle source
# File lib/heapy/diff.rb, line 92
        def read(filename)
  File.open(filename) do |f|
    f.each_line do |line|
      begin
        parsed = JSON.parse(line)
        yield parsed, line
      rescue JSON::ParserError
        puts "Could not parse #{line}"
      end
    end
  end
end