New upstream version 12.1.13

This commit is contained in:
Sruthi Chandran 2019-10-03 20:49:30 +05:30
parent f63e0b96b8
commit 6dae2bc86e
35 changed files with 1243 additions and 0 deletions

BIN
heapy/.DS_Store vendored Normal file

Binary file not shown.

11
heapy/.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
/.bundle/
/.yardoc
/Gemfile.lock
/_yardoc/
/coverage/
/doc/
/pkg/
/spec/reports/
/tmp/
tmp
.DS_Store

2
heapy/.rspec Normal file
View file

@ -0,0 +1,2 @@
--format documentation
--color

4
heapy/.travis.yml Normal file
View file

@ -0,0 +1,4 @@
language: ruby
rvm:
- 2.3.2
before_install: gem install bundler -v 1.10.6

15
heapy/CHANGELOG.md Normal file
View file

@ -0,0 +1,15 @@
## 0.1.4 - 2018-07-25
- Bundler is no longer required so heapy can now be used via a simple
gem install.
## 0.1.3 - 2017-09-07
- I'm really bad at keeping a log of changes on this project sorry
- Printing out the memory size for a generation
- Printing out the total number of objects for the heap in the summary
## 0.1.1 - 2015-10-15
- Less memory retention when parsing large heap dumps.
- Improved output format.

13
heapy/CODE_OF_CONDUCT.md Normal file
View file

@ -0,0 +1,13 @@
# Contributor Code of Conduct
As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, or religion.
Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
This Code of Conduct is adapted from the [Contributor Covenant](http://contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)

4
heapy/Gemfile Normal file
View file

@ -0,0 +1,4 @@
source 'https://rubygems.org'
# Specify your gem's dependencies in heapy.gemspec
gemspec

21
heapy/LICENSE.txt Normal file
View file

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015 schneems
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

101
heapy/README.md Normal file
View file

@ -0,0 +1,101 @@
# Heapy (Ruby Heap Dump Inspector)
[![Help Contribute to Open Source](https://www.codetriage.com/schneems/heapy/badges/users.svg)](https://www.codetriage.com/schneems/heapy) ![Supports Ruby 2.3+](https://img.shields.io/badge/ruby-2.3+-green.svg)
A CLI for analyzing Ruby Heap dumps. Thanks to [Sam Saffron](http://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby) for the idea and initial code.
## Installation
Add this line to your application's Gemfile:
```ruby
gem 'heapy'
```
And then execute:
$ bundle
Or install it yourself as:
$ gem install heapy
## Usage
Step 1) Generate a heap dump. You could [do this manually](http://samsaffron.com/archive/2015/03/31/debugging-memory-leaks-in-ruby). Or you can use a tool like [derailed_benchmarks](https://github.com/schneems/derailed_benchmarks)
Step 2) Once you've got the heap dump, you can analyze it using this CLI:
```
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump
Generation: nil object count: 209191
Generation: 14 object count: 407
Generation: 15 object count: 638
Generation: 16 object count: 748
Generation: 17 object count: 1023
Generation: 18 object count: 805
# ...
```
NOTE: The reason you may be getting a "nil" generation is these objects were loaded into memory before your code began tracking the allocations. To ensure all allocations are tracked you can execute your ruby script this trick. First create a file `trace.rb` that only starts allocation tracing:
```
# trace.rb
require 'objspace'
ObjectSpace.trace_object_allocations_start
```
Now make sure this command is loaded before you run your script, you can use Ruby's `-I` to specify a load path and `-r` to specify a library to require, in this case our trace file
```
$ ruby -I ./ -r trace script_name.rb
```
If the last line of your file is invalid JSON, make sure that you are closing the file after writing the ruby heap dump to it.
## Digging into a Generation
You can drill down into a specific generation. In the previous example, the 17'th generation looks strangely large, you can drill into it:
```
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump 17
Analyzing Heap (Generation: 17)
-------------------------------
allocated by memory (44061517) (in bytes)
==============================
39908512 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:79
1284993 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/openssl/buffering.rb:182
201068 /app/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:223
189272 /app/vendor/bundle/ruby/2.2.0/gems/newrelic_rpm-3.13.2.302/lib/new_relic/agent/stats_engine/stats_hash.rb:39
172531 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/net/http/header.rb:172
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131
```
### Reviewing all generations
If you want to read all generations you can use the "all" directive
```
$ heapy read tmp/2015-10-01T10:18:59-05:00-heap.dump all
```
You can also use T-Lo's online JS based [Heap Analyzer](http://tenderlove.github.io/heap-analyzer/) for visualizations.
## Development
After checking out the repo, run `$ bundle install` to install dependencies. Then, run `rake spec` to run the tests.
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
## Contributing
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.
## License
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).

7
heapy/Rakefile Normal file
View file

@ -0,0 +1,7 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"
RSpec::Core::RakeTask.new(:spec)
task :default => :spec
task :test => :spec

7
heapy/bin/heapy Executable file
View file

@ -0,0 +1,7 @@
#!/usr/bin/env ruby
require "bundler/setup" if defined?(Bundler)
require "heapy"
Heapy::CLI.new(ARGV).run

27
heapy/heapy.gemspec Normal file
View file

@ -0,0 +1,27 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'heapy/version'
Gem::Specification.new do |spec|
spec.name = "heapy"
spec.version = Heapy::VERSION
spec.authors = ["schneems"]
spec.email = ["richard.schneeman@gmail.com"]
spec.summary = %q{Inspects Ruby heap dumps}
spec.description = %q{Got a heap dump? Great. Use this tool to see what's in it!}
spec.homepage = "https://github.com/schneems/heapy"
spec.license = "MIT"
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
spec.bindir = "bin"
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
spec.require_paths = ["lib"]
spec.add_development_dependency "bundler", "~> 1.10"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec"
spec.required_ruby_version = '~> 2.3'
end

70
heapy/lib/heapy.rb Normal file
View file

@ -0,0 +1,70 @@
require 'json'
require "heapy/version"
module Heapy
class CLI
def initialize(argv)
@cmd = argv.shift
@file = argv.shift
@number = argv.shift
@args = argv
end
def help
puts <<-HALP
$ heapy read <file|command> <number>
When run with only a file, it will output the generation and count pairs:
$ heapy read tmp/2015-09-30-heap.dump
Generation: nil object count: 209191
Generation: 14 object count: 407
Generation: 15 object count: 638
Generation: 16 object count: 748
Generation: 17 object count: 1023
Generation: 18 object count: 805
When run with a file and a number it will output detailed information for that
generation:
$ heapy read tmp/2015-09-30-heap.dump 17
Analyzing Heap (Generation: 17)
-------------------------------
allocated by memory (44061517) (in bytes)
==============================
39908512 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/timeout.rb:79
1284993 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/openssl/buffering.rb:182
201068 /app/vendor/bundle/ruby/2.2.0/gems/json-1.8.3/lib/json/common.rb:223
189272 /app/vendor/bundle/ruby/2.2.0/gems/newrelic_rpm-3.13.2.302/lib/new_relic/agent/stats_engine/stats_hash.rb:39
172531 /app/vendor/ruby-2.2.3/lib/ruby/2.2.0/net/http/header.rb:172
92200 /app/vendor/bundle/ruby/2.2.0/gems/activesupport-4.2.3/lib/active_support/core_ext/numeric/conversions.rb:131
HALP
end
def run
case @cmd
when "--help"
help
when nil
help
when "read"
if @number
Analyzer.new(@file).drill_down(@number)
else
Analyzer.new(@file).analyze
end
else
help
end
end
end
end
require 'heapy/analyzer'
require 'heapy/alive'

269
heapy/lib/heapy/alive.rb Normal file
View file

@ -0,0 +1,269 @@
require 'objspace'
require 'stringio'
module Heapy
# This is an experimental module and likely to change. Don't use in production.
#
# Use at your own risk. APIs are not stable.
#
# == What
#
# You can use it to trace objects to see if they are still "alive" in memory.
# Unlike the heapy CLI this is meant to be used in live running code.
#
# This works by retaining an object's address in memory, then running GC
# and taking a heap dump. If the object exists in the heap dump, it is retained.
# Since we have the whole heap dump we can also do things like find what is retaining
# your object preventing it from being collected.
#
# == Use It
#
# You need to first start tracing objects:
#
# Heapy::Alive.start_object_trace!(heap_file: "./tmp/heap.json")
#
# Next in your code you want to specify the object ato trace
#
# string = "hello world"
# Heapy::Alive.trace_without_retain(string)
#
# When the code is done executing you can get a reference to all "tracer"
# objects by running:
#
# Heapy::Alive.traced_objects.each do |tracer|
# puts tracer.raw_json_hash if tracer.object_retained?
# end
#
# A few helpful methods on `tracer` objects:
#
# - `raw_json_hash` returns the hash of the object from the heap dump.
# - `object_retained?` returns truthy if the object was still present in the heap dump.
# - `address` a string of the memory address of the object you're tracing.
# - `tracked_to_s` a string that represents the object you're tracing (default
# is result of calling inspect on the method). You can pass in a custom representation
# when initializing the object. Can be useful for when `inspect` on the object you
# are tracing is too verbose.
# - `id2ref` returns the original object being traced (if it is still in memory).
# - `root?` returns false if the tracer isn't the root object.
#
# See `ObjectTracker` for more methods.
#
# If you want to see what retains an object, you can use `ObectTracker#retained_by`
# method (caution this is extremely expensive and requires re-walking the whole heap dump:
#
# Heapy::Alive.traced_objects.each do |tracer|
# if tracer.object_retained?
# puts "Traced: #{tracer.raw_json_hash}"
# tracer.retained_by.each do |retainer|
# puts " Retained by: #{retainer.raw_json_hash}"
# end
# end
# end
#
# You can iterate up the whole retained tree by using the `retained_by` method on tracers
# returned. But again it's expensive. If you have large heap dump or if you're tracing a bunch
# of objects, continuously calling `retained_by` will take lots of time. We also don't
# do any circular dependency detection so if you have two objects that depend on each other,
# you may hit an infinite loop.
#
# If you know that you'll need the retained objects of the main objects you're tracing you can
# save re-walking the heap the first N times by using the `retained_by` flag:
#
# Heapy::Alive.traced_objects(retained_by: true) do |tracer|
# # ...
# end
#
# This will pre-fetch the first level of "parents" for each object you're tracing.
#
# Did I mention this is all experimental and may change?
module Alive
@mutex = Mutex.new
@retain_hash = {}
@heap_file = nil
@started = false
def self.address_to_object(address)
obj_id = address.to_i(16) / 2
ObjectSpace._id2ref(obj_id)
rescue RangeError
nil
end
def self.start_object_trace!(heap_file: "./tmp/heap.json")
@mutex.synchronize do
@started ||= true && ObjectSpace.trace_object_allocations_start
@heap_file ||= heap_file
end
end
def self.trace_without_retain(object, to_s: nil)
tracker = ObjectTracker.new(object_id: object.object_id, to_s: to_s || object.inspect)
@mutex.synchronize do
@retain_hash[tracker.address] = tracker
end
end
def self.retained_by(tracer: nil, address: nil)
target_address = address || tracer.address
tracer = tracer || @retain_hash[address]
raise "not a valid address #{target_address}" if target_address.nil?
retainer_array = []
Analyzer.new(@heap_file).read do |json_hash|
retainers_from_json_hash(json_hash, target_address: target_address, retainer_array: retainer_array)
end
retainer_array
end
class << self
private def retainers_from_json_hash(json_hash, retainer_array:, target_address:)
references = json_hash["references"]
return unless references
references.each do |address|
next unless address == target_address
if json_hash["root"]
retainer = RootTracker.new(json_hash)
else
address = json_hash["address"]
representation = self.address_to_object(address)&.inspect || "object not traced".freeze
retainer = ObjectTracker.new(address: address, to_s: representation)
retainer.raw_json_hash = json_hash
end
retainer_array << retainer
end
end
end
private
@string_io = StringIO.new
# GIANT BALL OF HACKS || THERE BE DRAGONS
#
# There is so much I don't understand on why I need to do the things
# I'm doing in this method.
#
# Also see `living_dead` https://github.com/schneems/living_dead
def self.gc_start
# During debugging I found calling "puts" made some things
# mysteriously work, I have no idea why. If you remove this line
# then (more) tests fail. Maybe it has something to do with the way
# GC interacts with IO? I seriously have no idea.
#
@string_io.puts "=="
# Calling flush so we don't create a memory leak.
# Funny enough maybe calling flush without `puts` also works?
# IDK
#
@string_io.flush
# Calling GC multiple times fixes a different class of things
# Specifically the singleton_class.instance_eval tests.
# It might also be related to calling GC in a block, but changing
# to 1.times brings back failures.
#
# Calling 2 times results in eventual failure https://twitter.com/schneems/status/804369346910896128
# Calling 5 times results in eventual failure https://twitter.com/schneems/status/804382968307445760
# Trying 10 times
#
10.times { GC.start }
end
public
def self.traced_objects(retained_by: false)
raise "You aren't tracing anything call Heapy::Alive.trace_without_retain first" if @retain_hash.empty?
self.gc_start
ObjectSpace.dump_all(output: File.open(@heap_file,'w'))
retainer_address_array_hash = {}
Analyzer.new(@heap_file).read do |json_hash|
address = json_hash["address"]
tracer = @retain_hash[address]
next unless tracer
tracer.raw_json_hash = json_hash
if retained_by
retainers_from_json_hash(json_hash, target_address: address, retainer_array: tracer.retained_by)
end
end
@retain_hash.values
end
class RootTracker
def initialize(json)
@raw_json_hash = json
end
def references
[]
end
def id2ref
raise "cannot turn root object into an object"
end
def root?
true
end
def address
raise "root does not have an address"
end
def object_retained?
true
end
def tracked_to_s
"ROOT"
end
end
class ObjectTracker
attr_reader :address, :tracked_to_s
def initialize(object_id: nil, address: nil, to_s: )
if object_id
@address = "0x#{ (object_id << 1).to_s(16) }"
else
@address = address
end
raise "must provide address: #{@address.inspect}" if @address.nil?
@tracked_to_s = to_s.dup
@retained_by = nil
end
def id2ref
Heapy::Alive.address_to_object(address)
end
def root?
false
end
def object_retained?
raw_json_hash && raw_json_hash["address"]
end
def retainer_array
@retained_by ||= []
@retained_by
end
def retained_by
@retained_by || Heapy::Alive.retained_by(tracer: self)
end
attr_accessor :raw_json_hash
end
end
end

151
heapy/lib/heapy/analyzer.rb Normal file
View file

@ -0,0 +1,151 @@
module Heapy
class Analyzer
def initialize(filename)
@filename = filename
end
def read
File.open(@filename) do |f|
f.each_line do |line|
begin
parsed = JSON.parse(line)
yield parsed
rescue JSON::ParserError
puts "Could not parse #{line}"
end
end
end
end
def drill_down(generation_to_inspect)
puts ""
puts "Analyzing Heap (Generation: #{generation_to_inspect})"
puts "-------------------------------"
puts ""
generation_to_inspect = Integer(generation_to_inspect) unless generation_to_inspect == "all"
#
memsize_hash = Hash.new { |h, k| h[k] = 0 }
count_hash = Hash.new { |h, k| h[k] = 0 }
string_count = Hash.new { |h, k| h[k] = Hash.new { |h, k| h[k] = 0 } }
reference_hash = Hash.new { |h, k| h[k] = 0 }
read do |parsed|
generation = parsed["generation"] || 0
if generation_to_inspect == "all".freeze || generation == generation_to_inspect
next unless parsed["file"]
key = "#{ parsed["file"] }:#{ parsed["line"] }"
memsize_hash[key] += parsed["memsize"] || 0
count_hash[key] += 1
if parsed["type"] == "STRING".freeze
string_count[parsed["value"]][key] += 1 if parsed["value"]
end
if parsed["references"]
reference_hash[key] += parsed["references"].length
end
end
end
raise "not a valid Generation: #{generation_to_inspect.inspect}" if memsize_hash.empty?
total_memsize = memsize_hash.inject(0){|count, (k, v)| count += v}
# /Users/richardschneeman/Documents/projects/codetriage/app/views/layouts/application.html.slim:1"=>[{"address"=>"0x7f8a4fbf2328", "type"=>"STRING", "class"=>"0x7f8a4d5dec68", "bytesize"=>223051, "capacity"=>376832, "encoding"=>"UTF-8", "file"=>"/Users/richardschneeman/Documents/projects/codetriage/app/views/layouts/application.html.slim", "line"=>1, "method"=>"new", "generation"=>36, "memsize"=>377065, "flags"=>{"wb_protected"=>true, "old"=>true, "long_lived"=>true, "marked"=>true}}]}
puts "allocated by memory (#{total_memsize}) (in bytes)"
puts "=============================="
memsize_hash = memsize_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
longest = memsize_hash.first[1].to_s.length
memsize_hash.each do |file_line, memsize|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
end
total_count = count_hash.inject(0){|count, (k, v)| count += v}
puts ""
puts "object count (#{total_count})"
puts "=============================="
count_hash = count_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
longest = count_hash.first[1].to_s.length
count_hash.each do |file_line, memsize|
puts " #{memsize.to_s.rjust(longest)} #{file_line}"
end
puts ""
puts "High Ref Counts"
puts "=============================="
puts ""
reference_hash = reference_hash.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
longest = count_hash.first[1].to_s.length
reference_hash.each do |file_line, count|
puts " #{count.to_s.rjust(longest)} #{file_line}"
end
if !string_count.empty?
puts ""
puts "Duplicate strings"
puts "=============================="
puts ""
value_count = {}
string_count.each do |string, location_count_hash|
value_count[string] = location_count_hash.values.inject(&:+)
end
value_count = value_count.sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.first(50)
longest = value_count.first[1].to_s.length
value_count.each do |string, c1|
puts " #{c1.to_s.rjust(longest)} #{string.inspect}"
string_count[string].sort {|(k1, v1), (k2, v2)| v2 <=> v1 }.each do |file_line, c2|
puts " #{c2.to_s.rjust(longest)} #{file_line}"
end
puts ""
end
end
end
def analyze
puts ""
puts "Analyzing Heap"
puts "=============="
default_key = "nil".freeze
# generation number is key, value is count
data = Hash.new {|h, k| h[k] = 0 }
mem = Hash.new {|h, k| h[k] = 0 }
total_count = 0
total_mem = 0
read do |parsed|
data[parsed["generation"] || 0] += 1
mem[parsed["generation"] || 0] += parsed["memsize"] || 0
end
data = data.sort {|(k1,v1), (k2,v2)| k1 <=> k2 }
max_length = [data.last[0].to_s.length, default_key.length].max
data.each do |generation, count|
generation = default_key if generation == 0
total_count += count
total_mem += mem[generation]
puts "Generation: #{ generation.to_s.rjust(max_length) } object count: #{ count }, mem: #{(mem[generation].to_f / 1024).round(1)} kb"
end
puts ""
puts "Heap total"
puts "=============="
puts "Generations (active): #{data.length}"
puts "Count: #{total_count}"
puts "Memory: #{(total_mem.to_f / 1024).round(1)} kb"
end
end
end

View file

@ -0,0 +1,3 @@
module Heapy
VERSION = "0.1.4"
end

64
heapy/scratch.rb Normal file
View file

@ -0,0 +1,64 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../lib")))
load File.expand_path(File.join(__FILE__, "../lib/heapy.rb"))
# class Foo
# end
# class Bar
# end
# class Baz
# end
# def run
# foo = Foo.new
# Heapy::Alive.trace_without_retain(foo)
# foo.singleton_class
# foo = nil
# bar = Bar.new
# Heapy::Alive.trace_without_retain(bar)
# bar.singleton_class
# bar = nil
# baz = Baz.new
# Heapy::Alive.trace_without_retain(baz)
# baz.singleton_class
# baz = nil
# nil
# end
# Heapy::Alive.start_object_trace!
# run
# objects = Heapy::Alive.traced_objects.each do |obj|
# puts "Address: #{obj.address} #{obj.tracked_to_s}\n #{obj.raw_json_hash || "not found" }"
# end
Heapy::Alive.start_object_trace!
def run
foo = ""
Heapy::Alive.trace_without_retain(foo)
b = []
b << foo
b
end
c = run
objects = Heapy::Alive.traced_objects.each do |tracer|
puts "== Address: #{tracer.address} #{tracer.tracked_to_s}\n #{tracer.raw_json_hash || "not found" }"
# tracer.raw_json_hash["references"].each do |address|
# puts Heapy::Alive.address_to_object(address)
# end
Heapy::Alive.retained_by(tracer: tracer).each do |obj|
puts obj.inspect
end
end

3
heapy/trace.rb Normal file
View file

@ -0,0 +1,3 @@
require 'objspace'
ObjectSpace.trace_object_allocations_start

31
heapy/weird_memory/run.rb Normal file
View file

@ -0,0 +1,31 @@
arg = ARGV.shift
@fail_count = 0
require 'fileutils'
FileUtils.mkdir_p("tmp")
def run(file, fail_count: @fail_count)
cmd = "bundle exec ruby #{file}"
puts " $ #{ cmd }"
result = `#{cmd}`
@fail_count += 1 if result.match(/FAIL/)
puts " " + result
end
if arg.nil? || arg.downcase == "all"
puts "== Running all directories (#{`ruby -v`.strip})"
Dir.glob("weird_memory/**/*.rb").each do |file|
next if file == __FILE__
run(file)
end
else
puts "== Running examples in `#{arg}` directory (#{`ruby -v`.strip})"
Dir.glob("weird_memory/#{arg}/**/*.rb").each do |file|
run(file)
end
end
puts
puts "Total failed: #{@fail_count}"

View file

@ -0,0 +1,26 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class
string
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,29 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
class Runner
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class
string
return nil
end
end
Runner.new.run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,28 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
-> {
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class
string
}.call
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,26 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class
string
return nil
end
-> { run }.call
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,27 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class.instance_eval do
end
string
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,29 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
class Runner
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class.instance_eval do
end
string
return nil
end
end
Runner.new.run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,29 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
-> {
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class.instance_eval do
end
string
}.call
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,27 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string.singleton_class.instance_eval do
end
string
return nil
end
-> { run }.call
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,25 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,27 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
class Runner
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string
return nil
end
end
Runner.new.run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,26 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
-> {
string = ""
Heapy::Alive.trace_without_retain(string)
string
}.call
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,25 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
string = ""
Heapy::Alive.trace_without_retain(string)
string
return nil
end
-> {run}.call
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,28 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
array = 1.times.map {
string = ""
Heapy::Alive.trace_without_retain(string)
string
}
array = nil
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,29 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
class Runner
def run
array = 1.times.map {
string = ""
Heapy::Alive.trace_without_retain(string)
string
}
array = nil
return nil
end
end
Runner.new.run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,30 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
-> {
array = 1.times.map {
string = ""
Heapy::Alive.trace_without_retain(string)
string
}
array = nil
}.call
return nil
end
run
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"

View file

@ -0,0 +1,29 @@
$LOAD_PATH.unshift(File.expand_path(File.join(__FILE__, "../../../lib")))
require 'heapy'
Heapy::Alive.start_object_trace!(heap_file: ENV.fetch('HEAP_FILE') { 'tmp/heap.json' })
def run
array = 1.times.map {
string = ""
Heapy::Alive.trace_without_retain(string)
string
}
array = nil
return nil
end
-> { run }.call
alive_count = Heapy::Alive.traced_objects.select {|tracer|
tracer.object_retained?
}.length
# should return 0, no traced objects are returned
expected = 0
actual = alive_count
result = expected == actual ? "PASS" : "FAIL"
puts "#{result}: expected: #{expected}, actual: #{actual}"