heapy 0.1.1 → 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +32 -7
- data/lib/heapy.rb +89 -37
- data/lib/heapy/version.rb +1 -1
- data/scratch.rb +16 -0
- data/trace.rb +3 -0
- metadata +5 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ababd2495856e5682c0f7f136ee0fe112304bc1a
|
4
|
+
data.tar.gz: db47b39aa2d4207dc27a1753c1444830aba758bb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5695269c2619549c931188225fd6b52f0d2acd343f92aba069a8e3cd937d0c97d2b6b15f2585bd96ae78c905708b8612a0ff51bcbfb4ce746f6bd6df423c62f4
|
7
|
+
data.tar.gz: b70fbde861babd816b71ee8da6c8ecafb06192740dc4dcbdbb19ad930578925d62ffbdf295566f9e2d938366e385281303a4b1c16ad86c9486ff6f4bb8131d13
|
data/README.md
CHANGED
@@ -27,16 +27,35 @@ Step 2) Once you've got the heap dump, you can analyze it using this CLI:
|
|
27
27
|
```
|
28
28
|
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump
|
29
29
|
|
30
|
-
Generation:
|
31
|
-
Generation:
|
32
|
-
Generation:
|
33
|
-
Generation:
|
34
|
-
Generation:
|
35
|
-
Generation:
|
30
|
+
Generation: nil object count: 209191
|
31
|
+
Generation: 14 object count: 407
|
32
|
+
Generation: 15 object count: 638
|
33
|
+
Generation: 16 object count: 748
|
34
|
+
Generation: 17 object count: 1023
|
35
|
+
Generation: 18 object count: 805
|
36
36
|
# ...
|
37
37
|
```
|
38
38
|
|
39
|
-
|
39
|
+
NOTE: The reason you may be getting a "nil" generation is these objects were loaded into memory before your code began tracking the allocations. To ensure all allocations are tracked you can execute your ruby script this trick. First create a file `trace.rb` that only starts allocation tracing:
|
40
|
+
|
41
|
+
```
|
42
|
+
# trace.rb
|
43
|
+
require 'objspace'
|
44
|
+
|
45
|
+
ObjectSpace.trace_object_allocations_start
|
46
|
+
```
|
47
|
+
|
48
|
+
Now make sure this command is loaded before you run your script, you can use Ruby's `-I` to specify a load path and `-r` to specify a library to require, in this case our trace file
|
49
|
+
|
50
|
+
```
|
51
|
+
$ ruby -I ./ -r trace script_name.rb
|
52
|
+
```
|
53
|
+
|
54
|
+
If the last line of your file is invalid JSON, make sure that you are closing the file after writing the ruby heap dump to it.
|
55
|
+
|
56
|
+
## Digging into a Generation
|
57
|
+
|
58
|
+
You can drill down into a specific generation. In the previous example, the 17'th generation looks strangely large, you can drill into it:
|
40
59
|
|
41
60
|
|
42
61
|
```
|
@@ -54,6 +73,12 @@ $ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump 17
|
|
54
73
|
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131
|
55
74
|
```
|
56
75
|
|
76
|
+
If you want to read all generations you can use the "all" directive
|
77
|
+
|
78
|
+
```
|
79
|
+
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump all
|
80
|
+
```
|
81
|
+
|
57
82
|
You can also use T-Lo's online JS based [Heap Analyzer](http://tenderlove.github.io/heap-analyzer/) for visualizations.
|
58
83
|
|
59
84
|
## Development
|
data/lib/heapy.rb
CHANGED
@@ -19,12 +19,12 @@ $ heapy read <file|command> <number>
|
|
19
19
|
When run with only a file, it will output the generation and count pairs:
|
20
20
|
|
21
21
|
$ heapy read tmp/2015-09-30-heap.dump
|
22
|
-
Generation:
|
23
|
-
Generation:
|
24
|
-
Generation:
|
25
|
-
Generation:
|
26
|
-
Generation:
|
27
|
-
Generation:
|
22
|
+
Generation: nil object count: 209191
|
23
|
+
Generation: 14 object count: 407
|
24
|
+
Generation: 15 object count: 638
|
25
|
+
Generation: 16 object count: 748
|
26
|
+
Generation: 17 object count: 1023
|
27
|
+
Generation: 18 object count: 805
|
28
28
|
|
29
29
|
When run with a file and a number it will output detailed information for that
|
30
30
|
generation:
|
@@ -69,38 +69,61 @@ HALP
|
|
69
69
|
@filename = filename
|
70
70
|
end
|
71
71
|
|
72
|
-
def
|
73
|
-
puts ""
|
74
|
-
puts "Analyzing Heap (Generation: #{generation})"
|
75
|
-
puts "-------------------------------"
|
76
|
-
puts ""
|
77
|
-
|
78
|
-
generation = Integer(generation)
|
79
|
-
|
80
|
-
#
|
81
|
-
memsize_hash = Hash.new { |h, k| h[k] = 0 }
|
82
|
-
count_hash = Hash.new { |h, k| h[k] = 0 }
|
72
|
+
def read
|
83
73
|
File.open(@filename) do |f|
|
84
74
|
f.each_line do |line|
|
85
75
|
begin
|
86
76
|
parsed = JSON.parse(line)
|
87
|
-
|
88
|
-
key = "#{ parsed["file"] }:#{ parsed["line"] }"
|
89
|
-
memsize_hash[key] += parsed["memsize"]
|
90
|
-
count_hash[key] += 1
|
91
|
-
end
|
77
|
+
yield parsed
|
92
78
|
rescue JSON::ParserError
|
93
79
|
puts "Could not parse #{line}"
|
94
80
|
end
|
95
81
|
end
|
96
82
|
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def drill_down(generation_to_inspect)
|
86
|
+
puts ""
|
87
|
+
puts "Analyzing Heap (Generation: #{generation_to_inspect})"
|
88
|
+
puts "-------------------------------"
|
89
|
+
puts ""
|
90
|
+
|
91
|
+
generation_to_inspect = Integer(generation_to_inspect) unless generation_to_inspect == "all"
|
92
|
+
|
93
|
+
#
|
94
|
+
memsize_hash = Hash.new { |h, k| h[k] = 0 }
|
95
|
+
count_hash = Hash.new { |h, k| h[k] = 0 }
|
96
|
+
string_count = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = 0 } }
|
97
|
+
|
98
|
+
reference_hash = Hash.new { |h, k| h[k] = 0 }
|
99
|
+
|
100
|
+
read do |parsed|
|
101
|
+
generation = parsed["generation"] || 0
|
102
|
+
if generation_to_inspect == "all".freeze || generation == generation_to_inspect
|
103
|
+
next unless parsed["file"]
|
104
|
+
|
105
|
+
key = "#{ parsed["file"] }:#{ parsed["line"] }"
|
106
|
+
memsize_hash[key] += parsed["memsize"] || 0
|
107
|
+
count_hash[key] += 1
|
108
|
+
|
109
|
+
if parsed["type"] == "STRING".freeze
|
110
|
+
string_count[parsed["value"]][key] += 1 if parsed["value"]
|
111
|
+
end
|
112
|
+
|
113
|
+
if parsed["references"]
|
114
|
+
reference_hash[key] += parsed["references"].length
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
raise "not a valid Generation: #{generation_to_inspect.inspect}" if memsize_hash.empty?
|
97
120
|
|
98
121
|
total_memsize = memsize_hash.inject(0){|count, (k, v)| count += v}
|
99
122
|
|
100
123
|
# /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}}]}
|
101
124
|
puts "allocated by memory (#{total_memsize}) (in bytes)"
|
102
125
|
puts "=============================="
|
103
|
-
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }
|
126
|
+
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
104
127
|
longest = memsize_hash.first[1].to_s.length
|
105
128
|
memsize_hash.each do |file_line, memsize|
|
106
129
|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
@@ -110,37 +133,66 @@ HALP
|
|
110
133
|
|
111
134
|
puts ""
|
112
135
|
puts "object count (#{total_count})"
|
113
|
-
puts "
|
114
|
-
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }
|
136
|
+
puts "=============================="
|
137
|
+
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
115
138
|
longest = count_hash.first[1].to_s.length
|
116
139
|
count_hash.each do |file_line, memsize|
|
117
140
|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
118
141
|
end
|
142
|
+
|
143
|
+
puts ""
|
144
|
+
puts "High Ref Counts"
|
145
|
+
puts "=============================="
|
146
|
+
puts ""
|
147
|
+
|
148
|
+
reference_hash = reference_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
149
|
+
longest = count_hash.first[1].to_s.length
|
150
|
+
|
151
|
+
reference_hash.each do |file_line, count|
|
152
|
+
puts " #{count.to_s.rjust(longest)} #{file_line}"
|
153
|
+
end
|
154
|
+
|
155
|
+
puts ""
|
156
|
+
puts "Duplicate strings"
|
157
|
+
puts "=============================="
|
158
|
+
puts ""
|
159
|
+
value_count = {}
|
160
|
+
|
161
|
+
string_count.each do |string, location_count_hash|
|
162
|
+
value_count[string] = location_count_hash.values.inject(&:+)
|
163
|
+
end
|
164
|
+
|
165
|
+
value_count = value_count.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
166
|
+
longest = value_count.first[1].to_s.length
|
167
|
+
|
168
|
+
value_count.each do |string, c1|
|
169
|
+
|
170
|
+
puts " #{c1.to_s.rjust(longest)} #{string.inspect}"
|
171
|
+
string_count[string].sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.each do |file_line, c2|
|
172
|
+
puts " #{c2.to_s.rjust(longest)} #{file_line}"
|
173
|
+
end
|
174
|
+
puts ""
|
175
|
+
end
|
176
|
+
|
119
177
|
end
|
120
178
|
|
121
179
|
def analyze
|
122
180
|
puts ""
|
123
181
|
puts "Analyzing Heap"
|
124
182
|
puts "=============="
|
183
|
+
default_key = "nil".freeze
|
125
184
|
|
126
185
|
# generation number is key, value is count
|
127
186
|
data = Hash.new {|h, k| h[k] = 0 }
|
128
187
|
|
129
|
-
|
130
|
-
|
131
|
-
begin
|
132
|
-
line = line.chomp
|
133
|
-
json = JSON.parse(line)
|
134
|
-
data[json["generation"]||0] += 1
|
135
|
-
rescue JSON::ParserError
|
136
|
-
puts "Could not parse #{line}"
|
137
|
-
end
|
138
|
-
end
|
188
|
+
read do |parsed|
|
189
|
+
data[parsed["generation"] || 0] += 1
|
139
190
|
end
|
140
191
|
|
141
|
-
data = data.sort
|
142
|
-
max_length = data.last[0].to_s.length
|
192
|
+
data = data.sort {|(k1,v1), (k2,v2)| k1 <=> k2 }
|
193
|
+
max_length = [data.last[0].to_s.length, default_key.length].max
|
143
194
|
data.each do |generation, count|
|
195
|
+
generation = default_key if generation == 0
|
144
196
|
puts "Generation: #{ generation.to_s.rjust(max_length) } object count: #{ count }"
|
145
197
|
end
|
146
198
|
end
|
data/lib/heapy/version.rb
CHANGED
data/scratch.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'objspace'
|
2
|
+
|
3
|
+
ObjectSpace.trace_object_allocations_start
|
4
|
+
|
5
|
+
array = []
|
6
|
+
10_000.times do |x|
|
7
|
+
a = "#{x}_foo"
|
8
|
+
array << a
|
9
|
+
end
|
10
|
+
|
11
|
+
# GC.start
|
12
|
+
|
13
|
+
file_name = "/tmp/#{Time.now.to_f}-heap.dump"
|
14
|
+
ObjectSpace.dump_all(output: File.open(file_name, 'w'))
|
15
|
+
|
16
|
+
puts "bin/heapy read #{file_name}"
|
data/trace.rb
ADDED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heapy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.2
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-11-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -74,6 +74,8 @@ files:
|
|
74
74
|
- heapy.gemspec
|
75
75
|
- lib/heapy.rb
|
76
76
|
- lib/heapy/version.rb
|
77
|
+
- scratch.rb
|
78
|
+
- trace.rb
|
77
79
|
homepage: https://github.com/schneems/heapy
|
78
80
|
licenses:
|
79
81
|
- MIT
|
@@ -94,7 +96,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
94
96
|
version: '0'
|
95
97
|
requirements: []
|
96
98
|
rubyforge_project:
|
97
|
-
rubygems_version: 2.
|
99
|
+
rubygems_version: 2.5.0
|
98
100
|
signing_key:
|
99
101
|
specification_version: 4
|
100
102
|
summary: Inspects Ruby heap dumps
|