heapy-0.1.4/0000755000175000017500000000000013576447370013442 5ustar abspythonabspythonheapy-0.1.4/heapy.gemspec0000644000175000017500000000174613576447370016125 0ustar abspythonabspython# 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 heapy-0.1.4/.rspec0000644000175000017500000000003713576447370014557 0ustar abspythonabspython--format documentation --color heapy-0.1.4/CODE_OF_CONDUCT.md0000644000175000017500000000263213576447370016244 0ustar abspythonabspython# 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/) heapy-0.1.4/.gitignore0000644000175000017500000000014513576447370015432 0ustar abspythonabspython/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ tmp .DS_Store heapy-0.1.4/LICENSE.txt0000644000175000017500000000206313576447370015266 0ustar abspythonabspythonThe 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. heapy-0.1.4/scratch.rb0000644000175000017500000000232413576447370015417 0ustar abspythonabspython $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 heapy-0.1.4/README.md0000644000175000017500000000770213576447370014727 0ustar abspythonabspython# 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). heapy-0.1.4/.DS_Store0000644000175000017500000001400413576447370015124 0ustar abspythonabspythonBud1 bwspblobùspecbwspblobùbplist00Ù  ]ShowStatusBar[ShowSidebar[ShowPathbar[ShowToolbar[ShowTabView_ContainerShowSidebar\WindowBounds\SidebarWidth_PreviewPaneVisibility   _{{810, 316}, {770, 449}}•)5AMYp}Š¢£¤¥¦§¨ÃÅÆspecvSrnlong  @€ @€ @€ @ E DSDB `€ @€ @€ @heapy-0.1.4/Rakefile0000644000175000017500000000021313576447370015103 0ustar abspythonabspythonrequire "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task :default => :spec task :test => :specheapy-0.1.4/lib/0000755000175000017500000000000013576447370014210 5ustar abspythonabspythonheapy-0.1.4/lib/heapy/0000755000175000017500000000000013576447370015316 5ustar abspythonabspythonheapy-0.1.4/lib/heapy/alive.rb0000644000175000017500000002035613576447370016751 0ustar abspythonabspythonrequire '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 endheapy-0.1.4/lib/heapy/version.rb0000644000175000017500000000004513576447370017327 0ustar abspythonabspythonmodule Heapy VERSION = "0.1.4" end heapy-0.1.4/lib/heapy/analyzer.rb0000644000175000017500000001212613576447370017472 0ustar abspythonabspythonmodule 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 heapy-0.1.4/lib/heapy.rb0000644000175000017500000000340213576447370015642 0ustar abspythonabspythonrequire '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 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' heapy-0.1.4/.travis.yml0000644000175000017500000000011413576447370015547 0ustar abspythonabspythonlanguage: ruby rvm: - 2.3.2 before_install: gem install bundler -v 1.10.6 heapy-0.1.4/CHANGELOG.md0000644000175000017500000000064513576447370015260 0ustar abspythonabspython## 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. heapy-0.1.4/bin/0000755000175000017500000000000013576447370014212 5ustar abspythonabspythonheapy-0.1.4/bin/heapy0000755000175000017500000000015513576447370015247 0ustar abspythonabspython#!/usr/bin/env ruby require "bundler/setup" if defined?(Bundler) require "heapy" Heapy::CLI.new(ARGV).run heapy-0.1.4/Gemfile0000644000175000017500000000013213576447370014731 0ustar abspythonabspythonsource 'https://rubygems.org' # Specify your gem's dependencies in heapy.gemspec gemspec heapy-0.1.4/trace.rb0000644000175000017500000000007713576447370015071 0ustar abspythonabspythonrequire 'objspace' ObjectSpace.trace_object_allocations_start heapy-0.1.4/weird_memory/0000755000175000017500000000000013576447370016144 5ustar abspythonabspythonheapy-0.1.4/weird_memory/singleton_class/0000755000175000017500000000000013576447370021333 5ustar abspythonabspythonheapy-0.1.4/weird_memory/singleton_class/singleton_class_in_proc.rb0000644000175000017500000000114513576447370026561 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/singleton_class/singleton_class_method_in_proc.rb0000644000175000017500000000113113576447370030114 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/singleton_class/singleton_class_in_class.rb0000644000175000017500000000117013576447370026721 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/singleton_class/singleton_class.rb0000644000175000017500000000111513576447370025045 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/string/0000755000175000017500000000000013576447370017452 5ustar abspythonabspythonheapy-0.1.4/weird_memory/string/string.rb0000644000175000017500000000106413576447370021306 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/string/string_in_class.rb0000644000175000017500000000113413576447370023157 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/string/string_method_in_proc.rb0000644000175000017500000000107613576447370024362 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/string/string_in_proc.rb0000644000175000017500000000111113576447370023010 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/run.rb0000644000175000017500000000122613576447370017276 0ustar abspythonabspythonarg = 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}" heapy-0.1.4/weird_memory/times_map/0000755000175000017500000000000013576447370020122 5ustar abspythonabspythonheapy-0.1.4/weird_memory/times_map/times_map_method_in_proc.rb0000644000175000017500000000116113576447370025475 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/times_map/times_map_in_proc.rb0000644000175000017500000000120013576447370024127 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/times_map/times_map.rb0000644000175000017500000000114413576447370022425 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/times_map/times_map_in_class.rb0000644000175000017500000000122113576447370024274 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/singleton_class_instance_eval/0000755000175000017500000000000013576447370024226 5ustar abspythonabspythonheapy-0.1.4/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_class.rb0000644000175000017500000000122013576447370034503 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval.rb0000644000175000017500000000114413576447370032635 0ustar abspythonabspython$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}" ././@LongLink0000644000000000000000000000014700000000000011605 Lustar rootrootheapy-0.1.4/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.rbheapy-0.1.4/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_method_in_proc.0000644000175000017500000000116013576447370035360 0ustar abspythonabspython$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}" heapy-0.1.4/weird_memory/singleton_class_instance_eval/singleton_class_instance_eval_in_proc.rb0000644000175000017500000000117613576447370034353 0ustar abspythonabspython$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}"