heapy 0.1.4 → 0.2.0

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.
Files changed (31) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/check_changelog.yml +13 -0
  3. data/.gitignore +1 -0
  4. data/CHANGELOG.md +8 -0
  5. data/README.md +38 -4
  6. data/bin/heapy +1 -2
  7. data/heapy.gemspec +4 -4
  8. data/lib/heapy.rb +90 -55
  9. data/lib/heapy/analyzer.rb +15 -6
  10. data/lib/heapy/diff.rb +105 -0
  11. data/lib/heapy/version.rb +1 -1
  12. metadata +30 -35
  13. data/lib/heapy/alive.rb +0 -269
  14. data/scratch.rb +0 -64
  15. data/weird_memory/run.rb +0 -31
  16. data/weird_memory/singleton_class/singleton_class.rb +0 -26
  17. data/weird_memory/singleton_class/singleton_class_in_class.rb +0 -29
  18. data/weird_memory/singleton_class/singleton_class_in_proc.rb +0 -28
  19. data/weird_memory/singleton_class/singleton_class_method_in_proc.rb +0 -26
  20. data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval.rb +0 -27
  21. data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_class.rb +0 -29
  22. data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_proc.rb +0 -29
  23. data/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.rb +0 -27
  24. data/weird_memory/string/string.rb +0 -25
  25. data/weird_memory/string/string_in_class.rb +0 -27
  26. data/weird_memory/string/string_in_proc.rb +0 -26
  27. data/weird_memory/string/string_method_in_proc.rb +0 -25
  28. data/weird_memory/times_map/times_map.rb +0 -28
  29. data/weird_memory/times_map/times_map_in_class.rb +0 -29
  30. data/weird_memory/times_map/times_map_in_proc.rb +0 -30
  31. data/weird_memory/times_map/times_map_method_in_proc.rb +0 -29
@@ -1,3 +1,3 @@
1
1
  module Heapy
2
- VERSION = "0.1.4"
2
+ VERSION = "0.2.0"
3
3
  end
metadata CHANGED
@@ -1,41 +1,55 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: heapy
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - schneems
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-07-25 00:00:00.000000000 Z
11
+ date: 2020-08-26 00:00:00.000000000 Z
12
12
  dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: thor
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
13
27
  - !ruby/object:Gem::Dependency
14
28
  name: bundler
15
29
  requirement: !ruby/object:Gem::Requirement
16
30
  requirements:
17
- - - "~>"
31
+ - - ">"
18
32
  - !ruby/object:Gem::Version
19
- version: '1.10'
33
+ version: '1'
20
34
  type: :development
21
35
  prerelease: false
22
36
  version_requirements: !ruby/object:Gem::Requirement
23
37
  requirements:
24
- - - "~>"
38
+ - - ">"
25
39
  - !ruby/object:Gem::Version
26
- version: '1.10'
40
+ version: '1'
27
41
  - !ruby/object:Gem::Dependency
28
42
  name: rake
29
43
  requirement: !ruby/object:Gem::Requirement
30
44
  requirements:
31
- - - "~>"
45
+ - - ">"
32
46
  - !ruby/object:Gem::Version
33
47
  version: '10.0'
34
48
  type: :development
35
49
  prerelease: false
36
50
  version_requirements: !ruby/object:Gem::Requirement
37
51
  requirements:
38
- - - "~>"
52
+ - - ">"
39
53
  - !ruby/object:Gem::Version
40
54
  version: '10.0'
41
55
  - !ruby/object:Gem::Dependency
@@ -60,7 +74,7 @@ executables:
60
74
  extensions: []
61
75
  extra_rdoc_files: []
62
76
  files:
63
- - ".DS_Store"
77
+ - ".github/workflows/check_changelog.yml"
64
78
  - ".gitignore"
65
79
  - ".rspec"
66
80
  - ".travis.yml"
@@ -73,50 +87,31 @@ files:
73
87
  - bin/heapy
74
88
  - heapy.gemspec
75
89
  - lib/heapy.rb
76
- - lib/heapy/alive.rb
77
90
  - lib/heapy/analyzer.rb
91
+ - lib/heapy/diff.rb
78
92
  - lib/heapy/version.rb
79
- - scratch.rb
80
93
  - 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
98
94
  homepage: https://github.com/schneems/heapy
99
95
  licenses:
100
96
  - MIT
101
97
  metadata: {}
102
- post_install_message:
98
+ post_install_message:
103
99
  rdoc_options: []
104
100
  require_paths:
105
101
  - lib
106
102
  required_ruby_version: !ruby/object:Gem::Requirement
107
103
  requirements:
108
- - - "~>"
104
+ - - ">="
109
105
  - !ruby/object:Gem::Version
110
- version: '2.3'
106
+ version: '0'
111
107
  required_rubygems_version: !ruby/object:Gem::Requirement
112
108
  requirements:
113
109
  - - ">="
114
110
  - !ruby/object:Gem::Version
115
111
  version: '0'
116
112
  requirements: []
117
- rubyforge_project:
118
- rubygems_version: 2.7.6
119
- signing_key:
113
+ rubygems_version: 3.1.2
114
+ signing_key:
120
115
  specification_version: 4
121
116
  summary: Inspects Ruby heap dumps
122
117
  test_files: []
@@ -1,269 +0,0 @@
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
data/scratch.rb DELETED
@@ -1,64 +0,0 @@
1
-
2
-
3
-
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
28
-
29
- # baz = Baz.new
30
- # Heapy::Alive.trace_without_retain(baz)
31
- # baz.singleton_class
32
- # baz = nil
33
- # nil
34
- # end
35
-
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