File: analyzer.rb

package info (click to toggle)
ruby-heapy 0.2.0-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm, forky, sid, trixie
  • size: 97,184 kB
  • sloc: ruby: 394; makefile: 4
file content (160 lines) | stat: -rw-r--r-- 5,563 bytes parent folder | download
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