1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
module Heapy
# Used for inspecting contents of a heap dump
#
# To glance all contents at a glance run:
#
# Analyzer.new(file_name).analyze
#
# To inspect contents of a specific generation run:
#
# Analyzer.new(file_name).drill_down(generation, Float::INFINITY)
class Analyzer
def initialize(filename)
@filename = filename
end
def read
File.open(@filename) do |f|
f.each_line do |line|
begin
parsed = JSON.parse(line)
yield parsed
rescue JSON::ParserError
puts "Could not parse #{line}"
end
end
end
end
def drill_down(generation_to_inspect, max_items_to_display)
puts ""
puts "Analyzing Heap (Generation: #{generation_to_inspect})"
puts "-------------------------------"
puts ""
generation_to_inspect = Integer(generation_to_inspect) unless generation_to_inspect == "all"
memsize_hash = Hash.new { |h, k| h[k] = 0 }
count_hash = Hash.new { |h, k| h[k] = 0 }
string_count = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = 0 } }
reference_hash = Hash.new { |h, k| h[k] = 0 }
read do |parsed|
generation = parsed["generation"] || 0
if generation_to_inspect == "all".freeze || generation == generation_to_inspect
next unless parsed["file"]
key = "#{ parsed["file"] }:#{ parsed["line"] }"
memsize_hash[key] += parsed["memsize"] || 0
count_hash[key] += 1
if parsed["type"] == "STRING".freeze
string_count[parsed["value"]][key] += 1 if parsed["value"]
end
if parsed["references"]
reference_hash[key] += parsed["references"].length
end
end
end
raise "not a valid Generation: #{generation_to_inspect.inspect}" if memsize_hash.empty?
total_memsize = memsize_hash.inject(0){|count, (k, v)| count += v}
# /Users/richardschneeman/Documents/projects/codetriage/app/views/layouts/application.html.slim:1"=>[{"address"=>"0x7f8a4fbf2328", "type"=>"STRING", "class"=>"0x7f8a4d5dec68", "bytesize"=>223051, "capacity"=>376832, "encoding"=>"UTF-8", "file"=>"/Users/richardschneeman/Documents/projects/codetriage/app/views/layouts/application.html.slim", "line"=>1, "method"=>"new", "generation"=>36, "memsize"=>377065, "flags"=>{"wb_protected"=>true, "old"=>true, "long_lived"=>true, "marked"=>true}}]}
puts "allocated by memory (#{total_memsize}) (in bytes)"
puts "=============================="
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
longest = memsize_hash.first[1].to_s.length
memsize_hash.each do |file_line, memsize|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
end
total_count = count_hash.inject(0){|count, (k, v)| count += v}
puts ""
puts "object count (#{total_count})"
puts "=============================="
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
longest = count_hash.first[1].to_s.length
count_hash.each do |file_line, memsize|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
end
puts ""
puts "High Ref Counts"
puts "=============================="
puts ""
reference_hash = reference_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
longest = count_hash.first[1].to_s.length
reference_hash.each do |file_line, count|
puts " #{count.to_s.rjust(longest)} #{file_line}"
end
if !string_count.empty?
puts ""
puts "Duplicate strings"
puts "=============================="
puts ""
value_count = {}
string_count.each do |string, location_count_hash|
value_count[string] = location_count_hash.values.inject(&:+)
end
value_count = value_count.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(max_items_to_display)
longest = value_count.first[1].to_s.length
value_count.each do |string, c1|
puts " #{c1.to_s.rjust(longest)} #{string.inspect}"
string_count[string].sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.each do |file_line, c2|
puts " #{c2.to_s.rjust(longest)} #{file_line}"
end
puts ""
end
end
end
def analyze
puts ""
puts "Analyzing Heap"
puts "=============="
default_key = "nil".freeze
# generation number is key, value is count
data = Hash.new {|h, k| h[k] = 0 }
mem = Hash.new {|h, k| h[k] = 0 }
total_count = 0
total_mem = 0
read do |parsed|
data[parsed["generation"] || 0] += 1
mem[parsed["generation"] || 0] += parsed["memsize"] || 0
end
data = data.sort {|(k1,v1), (k2,v2)| k1 <=> k2 }
max_length = [data.last[0].to_s.length, default_key.length].max
data.each do |generation, count|
generation = default_key if generation == 0
total_count += count
total_mem += mem[generation]
puts "Generation: #{ generation.to_s.rjust(max_length) } object count: #{ count }, mem: #{(mem[generation].to_f / 1024).round(1)} kb"
end
puts ""
puts "Heap total"
puts "=============="
puts "Generations (active): #{data.length}"
puts "Count: #{total_count}"
puts "Memory: #{(total_mem.to_f / 1024).round(1)} kb"
end
end
end
|