heapy 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- 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
|