heapy 0.1.2 → 0.1.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +2 -0
- data/.travis.yml +1 -1
- data/CHANGELOG.md +6 -0
- data/README.md +3 -1
- data/lib/heapy.rb +3 -133
- data/lib/heapy/alive.rb +269 -0
- data/lib/heapy/analyzer.rb +151 -0
- data/lib/heapy/version.rb +1 -1
- data/scratch.rb +59 -11
- data/weird_memory/run.rb +31 -0
- data/weird_memory/singleton_class/singleton_class.rb +26 -0
- data/weird_memory/singleton_class/singleton_class_in_class.rb +29 -0
- data/weird_memory/singleton_class/singleton_class_in_proc.rb +28 -0
- data/weird_memory/singleton_class/singleton_class_method_in_proc.rb +26 -0
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval.rb +27 -0
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_class.rb +29 -0
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_proc.rb +29 -0
- data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.rb +27 -0
- data/weird_memory/string/string.rb +25 -0
- data/weird_memory/string/string_in_class.rb +27 -0
- data/weird_memory/string/string_in_proc.rb +26 -0
- data/weird_memory/string/string_method_in_proc.rb +25 -0
- data/weird_memory/times_map/times_map.rb +28 -0
- data/weird_memory/times_map/times_map_in_class.rb +29 -0
- data/weird_memory/times_map/times_map_in_proc.rb +30 -0
- data/weird_memory/times_map/times_map_method_in_proc.rb +29 -0
- metadata +22 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3e96b6f7d59e467d22dfbd41cb13ce09e262b951
|
4
|
+
data.tar.gz: 2538b65bed82efdf2d1e11cc6f769189fcc31e9a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 79d5a795873c51551eb56082e5832f03a4f5a9c6d837d14f8b4d0deb2a78c7a7682bef9183d469cb18bd32c0f87e16b596a8c6db61ce7117e62533352735d2f6
|
7
|
+
data.tar.gz: 797916602227e525d04af3fb9d83cb373c954a6f3ec3cfec9aa38f1df81ae2e317681565518cd97d5751e85b703e096acd9d6802047c9c040921413b12fa612b
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
## 0.1.3
|
2
|
+
|
3
|
+
- I'm really bad at keeping a log of changes on this project sorry
|
4
|
+
- Printing out the memory size for a generation
|
5
|
+
- Printing out the total number of objects for the heap in the summary
|
6
|
+
|
1
7
|
## 0.1.1
|
2
8
|
|
3
9
|
- Less memory retention when parsing large heap dumps.
|
data/README.md
CHANGED
@@ -73,6 +73,8 @@ $ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump 17
|
|
73
73
|
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131
|
74
74
|
```
|
75
75
|
|
76
|
+
### Reviewing all generations
|
77
|
+
|
76
78
|
If you want to read all generations you can use the "all" directive
|
77
79
|
|
78
80
|
```
|
@@ -89,7 +91,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To
|
|
89
91
|
|
90
92
|
## Contributing
|
91
93
|
|
92
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
94
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/schneems/heapy. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](contributor-covenant.org) code of conduct.
|
93
95
|
|
94
96
|
|
95
97
|
## License
|
data/lib/heapy.rb
CHANGED
@@ -63,138 +63,8 @@ HALP
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
end
|
66
|
+
end
|
66
67
|
|
67
|
-
|
68
|
-
|
69
|
-
@filename = filename
|
70
|
-
end
|
71
|
-
|
72
|
-
def read
|
73
|
-
File.open(@filename) do |f|
|
74
|
-
f.each_line do |line|
|
75
|
-
begin
|
76
|
-
parsed = JSON.parse(line)
|
77
|
-
yield parsed
|
78
|
-
rescue JSON::ParserError
|
79
|
-
puts "Could not parse #{line}"
|
80
|
-
end
|
81
|
-
end
|
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?
|
120
|
-
|
121
|
-
total_memsize = memsize_hash.inject(0){|count, (k, v)| count += v}
|
122
|
-
|
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}}]}
|
124
|
-
puts "allocated by memory (#{total_memsize}) (in bytes)"
|
125
|
-
puts "=============================="
|
126
|
-
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
127
|
-
longest = memsize_hash.first[1].to_s.length
|
128
|
-
memsize_hash.each do |file_line, memsize|
|
129
|
-
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
130
|
-
end
|
131
|
-
|
132
|
-
total_count = count_hash.inject(0){|count, (k, v)| count += v}
|
133
|
-
|
134
|
-
puts ""
|
135
|
-
puts "object count (#{total_count})"
|
136
|
-
puts "=============================="
|
137
|
-
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
138
|
-
longest = count_hash.first[1].to_s.length
|
139
|
-
count_hash.each do |file_line, memsize|
|
140
|
-
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
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
|
-
|
177
|
-
end
|
178
|
-
|
179
|
-
def analyze
|
180
|
-
puts ""
|
181
|
-
puts "Analyzing Heap"
|
182
|
-
puts "=============="
|
183
|
-
default_key = "nil".freeze
|
184
|
-
|
185
|
-
# generation number is key, value is count
|
186
|
-
data = Hash.new {|h, k| h[k] = 0 }
|
68
|
+
require 'heapy/analyzer'
|
69
|
+
require 'heapy/alive'
|
187
70
|
|
188
|
-
read do |parsed|
|
189
|
-
data[parsed["generation"] || 0] += 1
|
190
|
-
end
|
191
|
-
|
192
|
-
data = data.sort {|(k1,v1), (k2,v2)| k1 <=> k2 }
|
193
|
-
max_length = [data.last[0].to_s.length, default_key.length].max
|
194
|
-
data.each do |generation, count|
|
195
|
-
generation = default_key if generation == 0
|
196
|
-
puts "Generation: #{ generation.to_s.rjust(max_length) } object count: #{ count }"
|
197
|
-
end
|
198
|
-
end
|
199
|
-
end
|
200
|
-
end
|
data/lib/heapy/alive.rb
ADDED
@@ -0,0 +1,269 @@
|
|
1
|
+
require 'objspace'
|
2
|
+
require 'stringio'
|
3
|
+
|
4
|
+
module Heapy
|
5
|
+
|
6
|
+
# This is an experimental module and likely to change. Don't use in production.
|
7
|
+
#
|
8
|
+
# Use at your own risk. APIs are not stable.
|
9
|
+
#
|
10
|
+
# == What
|
11
|
+
#
|
12
|
+
# You can use it to trace objects to see if they are still "alive" in memory.
|
13
|
+
# Unlike the heapy CLI this is meant to be used in live running code.
|
14
|
+
#
|
15
|
+
# This works by retaining an object's address in memory, then running GC
|
16
|
+
# and taking a heap dump. If the object exists in the heap dump, it is retained.
|
17
|
+
# Since we have the whole heap dump we can also do things like find what is retaining
|
18
|
+
# your object preventing it from being collected.
|
19
|
+
#
|
20
|
+
# == Use It
|
21
|
+
#
|
22
|
+
# You need to first start tracing objects:
|
23
|
+
#
|
24
|
+
# Heapy::Alive.start_object_trace!(heap_file: "./tmp/heap.json")
|
25
|
+
#
|
26
|
+
# Next in your code you want to specify the object ato trace
|
27
|
+
#
|
28
|
+
# string = "hello world"
|
29
|
+
# Heapy::Alive.trace_without_retain(string)
|
30
|
+
#
|
31
|
+
# When the code is done executing you can get a reference to all "tracer"
|
32
|
+
# objects by running:
|
33
|
+
#
|
34
|
+
# Heapy::Alive.traced_objects.each do |tracer|
|
35
|
+
# puts tracer.raw_json_hash if tracer.object_retained?
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# A few helpful methods on `tracer` objects:
|
39
|
+
#
|
40
|
+
# - `raw_json_hash` returns the hash of the object from the heap dump.
|
41
|
+
# - `object_retained?` returns truthy if the object was still present in the heap dump.
|
42
|
+
# - `address` a string of the memory address of the object you're tracing.
|
43
|
+
# - `tracked_to_s` a string that represents the object you're tracing (default
|
44
|
+
# is result of calling inspect on the method). You can pass in a custom representation
|
45
|
+
# when initializing the object. Can be useful for when `inspect` on the object you
|
46
|
+
# are tracing is too verbose.
|
47
|
+
# - `id2ref` returns the original object being traced (if it is still in memory).
|
48
|
+
# - `root?` returns false if the tracer isn't the root object.
|
49
|
+
#
|
50
|
+
# See `ObjectTracker` for more methods.
|
51
|
+
#
|
52
|
+
# If you want to see what retains an object, you can use `ObectTracker#retained_by`
|
53
|
+
# method (caution this is extremely expensive and requires re-walking the whole heap dump:
|
54
|
+
#
|
55
|
+
# Heapy::Alive.traced_objects.each do |tracer|
|
56
|
+
# if tracer.object_retained?
|
57
|
+
# puts "Traced: #{tracer.raw_json_hash}"
|
58
|
+
# tracer.retained_by.each do |retainer|
|
59
|
+
# puts " Retained by: #{retainer.raw_json_hash}"
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# You can iterate up the whole retained tree by using the `retained_by` method on tracers
|
65
|
+
# returned. But again it's expensive. If you have large heap dump or if you're tracing a bunch
|
66
|
+
# of objects, continuously calling `retained_by` will take lots of time. We also don't
|
67
|
+
# do any circular dependency detection so if you have two objects that depend on each other,
|
68
|
+
# you may hit an infinite loop.
|
69
|
+
#
|
70
|
+
# If you know that you'll need the retained objects of the main objects you're tracing you can
|
71
|
+
# save re-walking the heap the first N times by using the `retained_by` flag:
|
72
|
+
#
|
73
|
+
# Heapy::Alive.traced_objects(retained_by: true) do |tracer|
|
74
|
+
# # ...
|
75
|
+
# end
|
76
|
+
#
|
77
|
+
# This will pre-fetch the first level of "parents" for each object you're tracing.
|
78
|
+
#
|
79
|
+
# Did I mention this is all experimental and may change?
|
80
|
+
module Alive
|
81
|
+
@mutex = Mutex.new
|
82
|
+
@retain_hash = {}
|
83
|
+
@heap_file = nil
|
84
|
+
@started = false
|
85
|
+
|
86
|
+
def self.address_to_object(address)
|
87
|
+
obj_id = address.to_i(16) / 2
|
88
|
+
ObjectSpace._id2ref(obj_id)
|
89
|
+
rescue RangeError
|
90
|
+
nil
|
91
|
+
end
|
92
|
+
|
93
|
+
def self.start_object_trace!(heap_file: "./tmp/heap.json")
|
94
|
+
@mutex.synchronize do
|
95
|
+
@started ||= true && ObjectSpace.trace_object_allocations_start
|
96
|
+
@heap_file ||= heap_file
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def self.trace_without_retain(object, to_s: nil)
|
101
|
+
tracker = ObjectTracker.new(object_id: object.object_id, to_s: to_s || object.inspect)
|
102
|
+
@mutex.synchronize do
|
103
|
+
@retain_hash[tracker.address] = tracker
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def self.retained_by(tracer: nil, address: nil)
|
108
|
+
target_address = address || tracer.address
|
109
|
+
tracer = tracer || @retain_hash[address]
|
110
|
+
|
111
|
+
raise "not a valid address #{target_address}" if target_address.nil?
|
112
|
+
|
113
|
+
retainer_array = []
|
114
|
+
Analyzer.new(@heap_file).read do |json_hash|
|
115
|
+
retainers_from_json_hash(json_hash, target_address: target_address, retainer_array: retainer_array)
|
116
|
+
end
|
117
|
+
|
118
|
+
retainer_array
|
119
|
+
end
|
120
|
+
|
121
|
+
class << self
|
122
|
+
private def retainers_from_json_hash(json_hash, retainer_array:, target_address:)
|
123
|
+
references = json_hash["references"]
|
124
|
+
return unless references
|
125
|
+
|
126
|
+
references.each do |address|
|
127
|
+
next unless address == target_address
|
128
|
+
|
129
|
+
if json_hash["root"]
|
130
|
+
retainer = RootTracker.new(json_hash)
|
131
|
+
else
|
132
|
+
address = json_hash["address"]
|
133
|
+
representation = self.address_to_object(address)&.inspect || "object not traced".freeze
|
134
|
+
retainer = ObjectTracker.new(address: address, to_s: representation)
|
135
|
+
retainer.raw_json_hash = json_hash
|
136
|
+
end
|
137
|
+
|
138
|
+
retainer_array << retainer
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
@string_io = StringIO.new
|
145
|
+
# GIANT BALL OF HACKS || THERE BE DRAGONS
|
146
|
+
#
|
147
|
+
# There is so much I don't understand on why I need to do the things
|
148
|
+
# I'm doing in this method.
|
149
|
+
#
|
150
|
+
# Also see `living_dead` https://github.com/schneems/living_dead
|
151
|
+
def self.gc_start
|
152
|
+
# During debugging I found calling "puts" made some things
|
153
|
+
# mysteriously work, I have no idea why. If you remove this line
|
154
|
+
# then (more) tests fail. Maybe it has something to do with the way
|
155
|
+
# GC interacts with IO? I seriously have no idea.
|
156
|
+
#
|
157
|
+
@string_io.puts "=="
|
158
|
+
|
159
|
+
# Calling flush so we don't create a memory leak.
|
160
|
+
# Funny enough maybe calling flush without `puts` also works?
|
161
|
+
# IDK
|
162
|
+
#
|
163
|
+
@string_io.flush
|
164
|
+
|
165
|
+
# Calling GC multiple times fixes a different class of things
|
166
|
+
# Specifically the singleton_class.instance_eval tests.
|
167
|
+
# It might also be related to calling GC in a block, but changing
|
168
|
+
# to 1.times brings back failures.
|
169
|
+
#
|
170
|
+
# Calling 2 times results in eventual failure https://twitter.com/schneems/status/804369346910896128
|
171
|
+
# Calling 5 times results in eventual failure https://twitter.com/schneems/status/804382968307445760
|
172
|
+
# Trying 10 times
|
173
|
+
#
|
174
|
+
10.times { GC.start }
|
175
|
+
end
|
176
|
+
public
|
177
|
+
|
178
|
+
def self.traced_objects(retained_by: false)
|
179
|
+
raise "You aren't tracing anything call Heapy::Alive.trace_without_retain first" if @retain_hash.empty?
|
180
|
+
self.gc_start
|
181
|
+
|
182
|
+
ObjectSpace.dump_all(output: File.open(@heap_file,'w'))
|
183
|
+
|
184
|
+
retainer_address_array_hash = {}
|
185
|
+
|
186
|
+
Analyzer.new(@heap_file).read do |json_hash|
|
187
|
+
address = json_hash["address"]
|
188
|
+
tracer = @retain_hash[address]
|
189
|
+
next unless tracer
|
190
|
+
tracer.raw_json_hash = json_hash
|
191
|
+
|
192
|
+
if retained_by
|
193
|
+
retainers_from_json_hash(json_hash, target_address: address, retainer_array: tracer.retained_by)
|
194
|
+
end
|
195
|
+
end
|
196
|
+
@retain_hash.values
|
197
|
+
end
|
198
|
+
|
199
|
+
class RootTracker
|
200
|
+
def initialize(json)
|
201
|
+
@raw_json_hash = json
|
202
|
+
end
|
203
|
+
|
204
|
+
def references
|
205
|
+
[]
|
206
|
+
end
|
207
|
+
|
208
|
+
def id2ref
|
209
|
+
raise "cannot turn root object into an object"
|
210
|
+
end
|
211
|
+
|
212
|
+
def root?
|
213
|
+
true
|
214
|
+
end
|
215
|
+
|
216
|
+
def address
|
217
|
+
raise "root does not have an address"
|
218
|
+
end
|
219
|
+
|
220
|
+
def object_retained?
|
221
|
+
true
|
222
|
+
end
|
223
|
+
|
224
|
+
def tracked_to_s
|
225
|
+
"ROOT"
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
class ObjectTracker
|
230
|
+
attr_reader :address, :tracked_to_s
|
231
|
+
|
232
|
+
def initialize(object_id: nil, address: nil, to_s: )
|
233
|
+
if object_id
|
234
|
+
@address = "0x#{ (object_id << 1).to_s(16) }"
|
235
|
+
else
|
236
|
+
@address = address
|
237
|
+
end
|
238
|
+
|
239
|
+
raise "must provide address: #{@address.inspect}" if @address.nil?
|
240
|
+
|
241
|
+
@tracked_to_s = to_s.dup
|
242
|
+
@retained_by = nil
|
243
|
+
end
|
244
|
+
|
245
|
+
def id2ref
|
246
|
+
Heapy::Alive.address_to_object(address)
|
247
|
+
end
|
248
|
+
|
249
|
+
def root?
|
250
|
+
false
|
251
|
+
end
|
252
|
+
|
253
|
+
def object_retained?
|
254
|
+
raw_json_hash && raw_json_hash["address"]
|
255
|
+
end
|
256
|
+
|
257
|
+
def retainer_array
|
258
|
+
@retained_by ||= []
|
259
|
+
@retained_by
|
260
|
+
end
|
261
|
+
|
262
|
+
def retained_by
|
263
|
+
@retained_by || Heapy::Alive.retained_by(tracer: self)
|
264
|
+
end
|
265
|
+
|
266
|
+
attr_accessor :raw_json_hash
|
267
|
+
end
|
268
|
+
end
|
269
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Heapy
|
2
|
+
class Analyzer
|
3
|
+
def initialize(filename)
|
4
|
+
@filename = filename
|
5
|
+
end
|
6
|
+
|
7
|
+
def read
|
8
|
+
File.open(@filename) do |f|
|
9
|
+
f.each_line do |line|
|
10
|
+
begin
|
11
|
+
parsed = JSON.parse(line)
|
12
|
+
yield parsed
|
13
|
+
rescue JSON::ParserError
|
14
|
+
puts "Could not parse #{line}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def drill_down(generation_to_inspect)
|
21
|
+
puts ""
|
22
|
+
puts "Analyzing Heap (Generation: #{generation_to_inspect})"
|
23
|
+
puts "-------------------------------"
|
24
|
+
puts ""
|
25
|
+
|
26
|
+
generation_to_inspect = Integer(generation_to_inspect) unless generation_to_inspect == "all"
|
27
|
+
|
28
|
+
#
|
29
|
+
memsize_hash = Hash.new { |h, k| h[k] = 0 }
|
30
|
+
count_hash = Hash.new { |h, k| h[k] = 0 }
|
31
|
+
string_count = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = 0 } }
|
32
|
+
|
33
|
+
reference_hash = Hash.new { |h, k| h[k] = 0 }
|
34
|
+
|
35
|
+
read do |parsed|
|
36
|
+
generation = parsed["generation"] || 0
|
37
|
+
if generation_to_inspect == "all".freeze || generation == generation_to_inspect
|
38
|
+
next unless parsed["file"]
|
39
|
+
|
40
|
+
key = "#{ parsed["file"] }:#{ parsed["line"] }"
|
41
|
+
memsize_hash[key] += parsed["memsize"] || 0
|
42
|
+
count_hash[key] += 1
|
43
|
+
|
44
|
+
if parsed["type"] == "STRING".freeze
|
45
|
+
string_count[parsed["value"]][key] += 1 if parsed["value"]
|
46
|
+
end
|
47
|
+
|
48
|
+
if parsed["references"]
|
49
|
+
reference_hash[key] += parsed["references"].length
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
raise "not a valid Generation: #{generation_to_inspect.inspect}" if memsize_hash.empty?
|
55
|
+
|
56
|
+
total_memsize = memsize_hash.inject(0){|count, (k, v)| count += v}
|
57
|
+
|
58
|
+
# /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}}]}
|
59
|
+
puts "allocated by memory (#{total_memsize}) (in bytes)"
|
60
|
+
puts "=============================="
|
61
|
+
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
62
|
+
longest = memsize_hash.first[1].to_s.length
|
63
|
+
memsize_hash.each do |file_line, memsize|
|
64
|
+
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
65
|
+
end
|
66
|
+
|
67
|
+
total_count = count_hash.inject(0){|count, (k, v)| count += v}
|
68
|
+
|
69
|
+
puts ""
|
70
|
+
puts "object count (#{total_count})"
|
71
|
+
puts "=============================="
|
72
|
+
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
73
|
+
longest = count_hash.first[1].to_s.length
|
74
|
+
count_hash.each do |file_line, memsize|
|
75
|
+
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
|
76
|
+
end
|
77
|
+
|
78
|
+
puts ""
|
79
|
+
puts "High Ref Counts"
|
80
|
+
puts "=============================="
|
81
|
+
puts ""
|
82
|
+
|
83
|
+
reference_hash = reference_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
84
|
+
longest = count_hash.first[1].to_s.length
|
85
|
+
|
86
|
+
reference_hash.each do |file_line, count|
|
87
|
+
puts " #{count.to_s.rjust(longest)} #{file_line}"
|
88
|
+
end
|
89
|
+
|
90
|
+
if !string_count.empty?
|
91
|
+
puts ""
|
92
|
+
puts "Duplicate strings"
|
93
|
+
puts "=============================="
|
94
|
+
puts ""
|
95
|
+
|
96
|
+
value_count = {}
|
97
|
+
|
98
|
+
string_count.each do |string, location_count_hash|
|
99
|
+
value_count[string] = location_count_hash.values.inject(&:+)
|
100
|
+
end
|
101
|
+
|
102
|
+
value_count = value_count.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
|
103
|
+
longest = value_count.first[1].to_s.length
|
104
|
+
|
105
|
+
value_count.each do |string, c1|
|
106
|
+
|
107
|
+
puts " #{c1.to_s.rjust(longest)} #{string.inspect}"
|
108
|
+
string_count[string].sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.each do |file_line, c2|
|
109
|
+
puts " #{c2.to_s.rjust(longest)} #{file_line}"
|
110
|
+
end
|
111
|
+
puts ""
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
def analyze
|
118
|
+
puts ""
|
119
|
+
puts "Analyzing Heap"
|
120
|
+
puts "=============="
|
121
|
+
default_key = "nil".freeze
|
122
|
+
|
123
|
+
# generation number is key, value is count
|
124
|
+
data = Hash.new {|h, k| h[k] = 0 }
|
125
|
+
mem = Hash.new {|h, k| h[k] = 0 }
|
126
|
+
total_count = 0
|
127
|
+
total_mem = 0
|
128
|
+
|
129
|
+
read do |parsed|
|
130
|
+
data[parsed["generation"] || 0] += 1
|
131
|
+
mem[parsed["generation"] || 0] += parsed["memsize"] || 0
|
132
|
+
end
|
133
|
+
|
134
|
+
data = data.sort {|(k1,v1), (k2,v2)| k1 <=> k2 }
|
135
|
+
max_length = [data.last[0].to_s.length, default_key.length].max
|
136
|
+
data.each do |generation, count|
|
137
|
+
generation = default_key if generation == 0
|
138
|
+
total_count += count
|
139
|
+
total_mem += mem[generation]
|
140
|
+
puts "Generation: #{ generation.to_s.rjust(max_length) } object count: #{ count }, mem: #{(mem[generation].to_f / 1024).round(1)} kb"
|
141
|
+
end
|
142
|
+
|
143
|
+
puts ""
|
144
|
+
puts "Heap total"
|
145
|
+
puts "=============="
|
146
|
+
puts "Generations (active): #{data.length}"
|
147
|
+
puts "Count: #{total_count}"
|
148
|
+
puts "Memory: #{(total_mem.to_f / 1024).round(1)} kb"
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
data/lib/heapy/version.rb
CHANGED
data/scratch.rb
CHANGED
@@ -1,16 +1,64 @@
|
|
1
|
-
require 'objspace'
|
2
1
|
|
3
|
-
ObjectSpace.trace_object_allocations_start
|
4
2
|
|
5
|
-
array = []
|
6
|
-
10_000.times do |x|
|
7
|
-
a = "#{x}_foo"
|
8
|
-
array << a
|
9
|
-
end
|
10
3
|
|
11
|
-
|
4
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../lib")))
|
5
|
+
|
6
|
+
load File.expand_path(File.join(__FILE__, "../lib/heapy.rb"))
|
7
|
+
|
8
|
+
|
9
|
+
# class Foo
|
10
|
+
# end
|
11
|
+
|
12
|
+
# class Bar
|
13
|
+
# end
|
14
|
+
|
15
|
+
# class Baz
|
16
|
+
# end
|
17
|
+
|
18
|
+
# def run
|
19
|
+
# foo = Foo.new
|
20
|
+
# Heapy::Alive.trace_without_retain(foo)
|
21
|
+
# foo.singleton_class
|
22
|
+
# foo = nil
|
23
|
+
|
24
|
+
# bar = Bar.new
|
25
|
+
# Heapy::Alive.trace_without_retain(bar)
|
26
|
+
# bar.singleton_class
|
27
|
+
# bar = nil
|
12
28
|
|
13
|
-
|
14
|
-
|
29
|
+
# baz = Baz.new
|
30
|
+
# Heapy::Alive.trace_without_retain(baz)
|
31
|
+
# baz.singleton_class
|
32
|
+
# baz = nil
|
33
|
+
# nil
|
34
|
+
# end
|
15
35
|
|
16
|
-
|
36
|
+
# Heapy::Alive.start_object_trace!
|
37
|
+
|
38
|
+
# run
|
39
|
+
|
40
|
+
# objects = Heapy::Alive.traced_objects.each do |obj|
|
41
|
+
# puts "Address: #{obj.address} #{obj.tracked_to_s}\n #{obj.raw_json_hash || "not found" }"
|
42
|
+
# end
|
43
|
+
|
44
|
+
Heapy::Alive.start_object_trace!
|
45
|
+
|
46
|
+
def run
|
47
|
+
foo = ""
|
48
|
+
Heapy::Alive.trace_without_retain(foo)
|
49
|
+
b = []
|
50
|
+
b << foo
|
51
|
+
b
|
52
|
+
end
|
53
|
+
|
54
|
+
c = run
|
55
|
+
|
56
|
+
objects = Heapy::Alive.traced_objects.each do |tracer|
|
57
|
+
puts "== Address: #{tracer.address} #{tracer.tracked_to_s}\n #{tracer.raw_json_hash || "not found" }"
|
58
|
+
# tracer.raw_json_hash["references"].each do |address|
|
59
|
+
# puts Heapy::Alive.address_to_object(address)
|
60
|
+
# end
|
61
|
+
Heapy::Alive.retained_by(tracer: tracer).each do |obj|
|
62
|
+
puts obj.inspect
|
63
|
+
end
|
64
|
+
end
|
data/weird_memory/run.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
arg = ARGV.shift
|
2
|
+
|
3
|
+
@fail_count = 0
|
4
|
+
|
5
|
+
require 'fileutils'
|
6
|
+
FileUtils.mkdir_p("tmp")
|
7
|
+
|
8
|
+
def run(file, fail_count: @fail_count)
|
9
|
+
cmd = "bundle exec ruby #{file}"
|
10
|
+
puts " $ #{ cmd }"
|
11
|
+
result = `#{cmd}`
|
12
|
+
@fail_count += 1 if result.match(/FAIL/)
|
13
|
+
puts " " + result
|
14
|
+
end
|
15
|
+
|
16
|
+
if arg.nil? || arg.downcase == "all"
|
17
|
+
puts "== Running all directories (#{`ruby -v`.strip})"
|
18
|
+
Dir.glob("weird_memory/**/*.rb").each do |file|
|
19
|
+
next if file == __FILE__
|
20
|
+
run(file)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
puts "== Running examples in `#{arg}` directory (#{`ruby -v`.strip})"
|
24
|
+
|
25
|
+
Dir.glob("weird_memory/#{arg}/**/*.rb").each do |file|
|
26
|
+
run(file)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
puts
|
31
|
+
puts "Total failed: #{@fail_count}"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
string = ""
|
9
|
+
Heapy::Alive.trace_without_retain(string)
|
10
|
+
string.singleton_class
|
11
|
+
string
|
12
|
+
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
run
|
17
|
+
|
18
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
19
|
+
tracer.object_retained?
|
20
|
+
}.length
|
21
|
+
# should return 0, no traced objects are returned
|
22
|
+
|
23
|
+
expected = 0
|
24
|
+
actual = alive_count
|
25
|
+
result = expected == actual ? "PASS" : "FAIL"
|
26
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
class Runner
|
8
|
+
|
9
|
+
def run
|
10
|
+
string = ""
|
11
|
+
Heapy::Alive.trace_without_retain(string)
|
12
|
+
string.singleton_class
|
13
|
+
string
|
14
|
+
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Runner.new.run
|
20
|
+
|
21
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
22
|
+
tracer.object_retained?
|
23
|
+
}.length
|
24
|
+
# should return 0, no traced objects are returned
|
25
|
+
|
26
|
+
expected = 0
|
27
|
+
actual = alive_count
|
28
|
+
result = expected == actual ? "PASS" : "FAIL"
|
29
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
-> {
|
9
|
+
string = ""
|
10
|
+
Heapy::Alive.trace_without_retain(string)
|
11
|
+
string.singleton_class
|
12
|
+
string
|
13
|
+
}.call
|
14
|
+
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
run
|
19
|
+
|
20
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
21
|
+
tracer.object_retained?
|
22
|
+
}.length
|
23
|
+
# should return 0, no traced objects are returned
|
24
|
+
|
25
|
+
expected = 0
|
26
|
+
actual = alive_count
|
27
|
+
result = expected == actual ? "PASS" : "FAIL"
|
28
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
string = ""
|
9
|
+
Heapy::Alive.trace_without_retain(string)
|
10
|
+
string.singleton_class
|
11
|
+
string
|
12
|
+
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
-> { run }.call
|
17
|
+
|
18
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
19
|
+
tracer.object_retained?
|
20
|
+
}.length
|
21
|
+
# should return 0, no traced objects are returned
|
22
|
+
|
23
|
+
expected = 0
|
24
|
+
actual = alive_count
|
25
|
+
result = expected == actual ? "PASS" : "FAIL"
|
26
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
string = ""
|
9
|
+
Heapy::Alive.trace_without_retain(string)
|
10
|
+
string.singleton_class.instance_eval do
|
11
|
+
end
|
12
|
+
string
|
13
|
+
|
14
|
+
return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
run
|
18
|
+
|
19
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
20
|
+
tracer.object_retained?
|
21
|
+
}.length
|
22
|
+
# should return 0, no traced objects are returned
|
23
|
+
|
24
|
+
expected = 0
|
25
|
+
actual = alive_count
|
26
|
+
result = expected == actual ? "PASS" : "FAIL"
|
27
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
class Runner
|
8
|
+
def run
|
9
|
+
string = ""
|
10
|
+
Heapy::Alive.trace_without_retain(string)
|
11
|
+
string.singleton_class.instance_eval do
|
12
|
+
end
|
13
|
+
string
|
14
|
+
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Runner.new.run
|
20
|
+
|
21
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
22
|
+
tracer.object_retained?
|
23
|
+
}.length
|
24
|
+
# should return 0, no traced objects are returned
|
25
|
+
|
26
|
+
expected = 0
|
27
|
+
actual = alive_count
|
28
|
+
result = expected == actual ? "PASS" : "FAIL"
|
29
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
-> {
|
9
|
+
string = ""
|
10
|
+
Heapy::Alive.trace_without_retain(string)
|
11
|
+
string.singleton_class.instance_eval do
|
12
|
+
end
|
13
|
+
string
|
14
|
+
}.call
|
15
|
+
|
16
|
+
return nil
|
17
|
+
end
|
18
|
+
|
19
|
+
run
|
20
|
+
|
21
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
22
|
+
tracer.object_retained?
|
23
|
+
}.length
|
24
|
+
# should return 0, no traced objects are returned
|
25
|
+
|
26
|
+
expected = 0
|
27
|
+
actual = alive_count
|
28
|
+
result = expected == actual ? "PASS" : "FAIL"
|
29
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
string = ""
|
9
|
+
Heapy::Alive.trace_without_retain(string)
|
10
|
+
string.singleton_class.instance_eval do
|
11
|
+
end
|
12
|
+
string
|
13
|
+
|
14
|
+
return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
-> { run }.call
|
18
|
+
|
19
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
20
|
+
tracer.object_retained?
|
21
|
+
}.length
|
22
|
+
# should return 0, no traced objects are returned
|
23
|
+
|
24
|
+
expected = 0
|
25
|
+
actual = alive_count
|
26
|
+
result = expected == actual ? "PASS" : "FAIL"
|
27
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
string = ""
|
9
|
+
Heapy::Alive.trace_without_retain(string)
|
10
|
+
string
|
11
|
+
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
|
15
|
+
run
|
16
|
+
|
17
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
18
|
+
tracer.object_retained?
|
19
|
+
}.length
|
20
|
+
# should return 0, no traced objects are returned
|
21
|
+
|
22
|
+
expected = 0
|
23
|
+
actual = alive_count
|
24
|
+
result = expected == actual ? "PASS" : "FAIL"
|
25
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,27 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
class Runner
|
8
|
+
def run
|
9
|
+
string = ""
|
10
|
+
Heapy::Alive.trace_without_retain(string)
|
11
|
+
string
|
12
|
+
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Runner.new.run
|
18
|
+
|
19
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
20
|
+
tracer.object_retained?
|
21
|
+
}.length
|
22
|
+
# should return 0, no traced objects are returned
|
23
|
+
|
24
|
+
expected = 0
|
25
|
+
actual = alive_count
|
26
|
+
result = expected == actual ? "PASS" : "FAIL"
|
27
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,26 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
-> {
|
9
|
+
string = ""
|
10
|
+
Heapy::Alive.trace_without_retain(string)
|
11
|
+
string
|
12
|
+
}.call
|
13
|
+
return nil
|
14
|
+
end
|
15
|
+
|
16
|
+
run
|
17
|
+
|
18
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
19
|
+
tracer.object_retained?
|
20
|
+
}.length
|
21
|
+
# should return 0, no traced objects are returned
|
22
|
+
|
23
|
+
expected = 0
|
24
|
+
actual = alive_count
|
25
|
+
result = expected == actual ? "PASS" : "FAIL"
|
26
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
string = ""
|
9
|
+
Heapy::Alive.trace_without_retain(string)
|
10
|
+
string
|
11
|
+
|
12
|
+
return nil
|
13
|
+
end
|
14
|
+
|
15
|
+
-> {run}.call
|
16
|
+
|
17
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
18
|
+
tracer.object_retained?
|
19
|
+
}.length
|
20
|
+
# should return 0, no traced objects are returned
|
21
|
+
|
22
|
+
expected = 0
|
23
|
+
actual = alive_count
|
24
|
+
result = expected == actual ? "PASS" : "FAIL"
|
25
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,28 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
array = 1.times.map {
|
9
|
+
string = ""
|
10
|
+
Heapy::Alive.trace_without_retain(string)
|
11
|
+
string
|
12
|
+
}
|
13
|
+
array = nil
|
14
|
+
return nil
|
15
|
+
end
|
16
|
+
|
17
|
+
run
|
18
|
+
|
19
|
+
|
20
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
21
|
+
tracer.object_retained?
|
22
|
+
}.length
|
23
|
+
# should return 0, no traced objects are returned
|
24
|
+
|
25
|
+
expected = 0
|
26
|
+
actual = alive_count
|
27
|
+
result = expected == actual ? "PASS" : "FAIL"
|
28
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
class Runner
|
8
|
+
def run
|
9
|
+
array = 1.times.map {
|
10
|
+
string = ""
|
11
|
+
Heapy::Alive.trace_without_retain(string)
|
12
|
+
string
|
13
|
+
}
|
14
|
+
array = nil
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Runner.new.run
|
20
|
+
|
21
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
22
|
+
tracer.object_retained?
|
23
|
+
}.length
|
24
|
+
# should return 0, no traced objects are returned
|
25
|
+
|
26
|
+
expected = 0
|
27
|
+
actual = alive_count
|
28
|
+
result = expected == actual ? "PASS" : "FAIL"
|
29
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,30 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
6
|
+
|
7
|
+
def run
|
8
|
+
-> {
|
9
|
+
array = 1.times.map {
|
10
|
+
string = ""
|
11
|
+
Heapy::Alive.trace_without_retain(string)
|
12
|
+
string
|
13
|
+
}
|
14
|
+
array = nil
|
15
|
+
}.call
|
16
|
+
|
17
|
+
return nil
|
18
|
+
end
|
19
|
+
|
20
|
+
run
|
21
|
+
|
22
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
23
|
+
tracer.object_retained?
|
24
|
+
}.length
|
25
|
+
# should return 0, no traced objects are returned
|
26
|
+
|
27
|
+
expected = 0
|
28
|
+
actual = alive_count
|
29
|
+
result = expected == actual ? "PASS" : "FAIL"
|
30
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
@@ -0,0 +1,29 @@
|
|
1
|
+
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
|
2
|
+
|
3
|
+
require 'heapy'
|
4
|
+
|
5
|
+
|
6
|
+
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
|
7
|
+
|
8
|
+
def run
|
9
|
+
array = 1.times.map {
|
10
|
+
string = ""
|
11
|
+
Heapy::Alive.trace_without_retain(string)
|
12
|
+
string
|
13
|
+
}
|
14
|
+
array = nil
|
15
|
+
return nil
|
16
|
+
end
|
17
|
+
|
18
|
+
-> { run }.call
|
19
|
+
|
20
|
+
alive_count = Heapy::Alive.traced_objects.select {|tracer|
|
21
|
+
tracer.object_retained?
|
22
|
+
}.length
|
23
|
+
# should return 0, no traced objects are returned
|
24
|
+
|
25
|
+
|
26
|
+
expected = 0
|
27
|
+
actual = alive_count
|
28
|
+
result = expected == actual ? "PASS" : "FAIL"
|
29
|
+
puts "#{result}: expected: #{expected}, actual: #{actual}"
|
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.3
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- schneems
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2017-09-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -73,9 +73,28 @@ files:
|
|
73
73
|
- bin/heapy
|
74
74
|
- heapy.gemspec
|
75
75
|
- lib/heapy.rb
|
76
|
+
- lib/heapy/alive.rb
|
77
|
+
- lib/heapy/analyzer.rb
|
76
78
|
- lib/heapy/version.rb
|
77
79
|
- scratch.rb
|
78
80
|
- trace.rb
|
81
|
+
- weird_memory/run.rb
|
82
|
+
- weird_memory/singleton_class/singleton_class.rb
|
83
|
+
- weird_memory/singleton_class/singleton_class_in_class.rb
|
84
|
+
- weird_memory/singleton_class/singleton_class_in_proc.rb
|
85
|
+
- weird_memory/singleton_class/singleton_class_method_in_proc.rb
|
86
|
+
- weird_memory/singleton_class_instance_eval/singleton_class_instance_eval.rb
|
87
|
+
- weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_class.rb
|
88
|
+
- weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_proc.rb
|
89
|
+
- weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.rb
|
90
|
+
- weird_memory/string/string.rb
|
91
|
+
- weird_memory/string/string_in_class.rb
|
92
|
+
- weird_memory/string/string_in_proc.rb
|
93
|
+
- weird_memory/string/string_method_in_proc.rb
|
94
|
+
- weird_memory/times_map/times_map.rb
|
95
|
+
- weird_memory/times_map/times_map_in_class.rb
|
96
|
+
- weird_memory/times_map/times_map_in_proc.rb
|
97
|
+
- weird_memory/times_map/times_map_method_in_proc.rb
|
79
98
|
homepage: https://github.com/schneems/heapy
|
80
99
|
licenses:
|
81
100
|
- MIT
|
@@ -96,7 +115,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
96
115
|
version: '0'
|
97
116
|
requirements: []
|
98
117
|
rubyforge_project:
|
99
|
-
rubygems_version: 2.
|
118
|
+
rubygems_version: 2.6.13
|
100
119
|
signing_key:
|
101
120
|
specification_version: 4
|
102
121
|
summary: Inspects Ruby heap dumps
|