├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── Gemfile ├── LICENSE.txt ├── README.md ├── Rakefile ├── example.rb ├── lib └── memprof2.rb ├── memprof2.gemspec ├── test ├── helper.rb └── test_memprof2.rb └── tmp └── .gitkeep /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | .yardoc 6 | Gemfile.lock 7 | InstalledFiles 8 | _yardoc 9 | coverage 10 | doc/ 11 | lib/bundler/man 12 | pkg 13 | rdoc 14 | spec/reports 15 | test/tmp 16 | test/version_tmp 17 | tmp 18 | example.out 19 | .ruby-version 20 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: ruby 2 | rvm: 3 | - 2.1 4 | - 2.2 5 | script: 'bundle exec rake test' 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.2 (2018/10/05) 2 | 3 | Enhancements: 4 | 5 | * Add `Memprof2.run_with_report` (thanks to @takkanm) 6 | 7 | # 0.1.1 (2015/10/10) 8 | 9 | Changes 10 | 11 | * Change report file mode from 'w' to 'a' (append) 12 | 13 | # 0.1.0 (2015/02/19) 14 | 15 | Enhancements 16 | 17 | * Use imcompatible memsize_of of ruby 2.2 which enables to get memsize correctly 18 | 19 | # 0.0.2 (2014/12/12) 20 | 21 | Enhancements 22 | 23 | * Add `Memprof2.report!` to clear out tracking data after printing out results. 24 | 25 | # 0.0.1 26 | 27 | Initial version 28 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Naotoshi Seo 2 | 3 | MIT License 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # memprof2 2 | 3 | [![Build Status](https://secure.travis-ci.org/sonots/memprof2.png?branch=master)](http://travis-ci.org/sonots/memprof2) 4 | 5 | Memprof2 is a Ruby memory profiler for >= Ruby 2.1.0. 6 | 7 | # Installation 8 | 9 | Execute 10 | 11 | ``` 12 | $ gem install memprof2 13 | ``` 14 | 15 | or ddd the following to your `Gemfile`: 16 | 17 | ```ruby 18 | gem 'memprof2' 19 | ``` 20 | 21 | And then execute: 22 | 23 | ```plain 24 | $ bundle 25 | ``` 26 | 27 | # API 28 | 29 | ## Memprof2.report 30 | 31 | ```ruby 32 | Memprof2.start 33 | 12.times{ "abc" } 34 | Memprof2.report(out: "/path/to/file") 35 | Memprof2.stop 36 | ``` 37 | 38 | Start tracking file/line memory size (bytes) information for objects created after calling `Memprof2.start`, and print out a summary of file:line:class pairs created. 39 | 40 | ``` 41 | 480 file.rb:2:String 42 | ``` 43 | 44 | *Note*: Call `Memprof2.report` again after `GC.start` to see which objects are cleaned up by the garbage collector: 45 | 46 | ```ruby 47 | Memprof2.start 48 | 10.times{ $last_str = "abc" } 49 | 50 | puts '=== Before GC' 51 | Memprof2.report 52 | 53 | puts '=== After GC' 54 | GC.start 55 | Memprof2.report 56 | 57 | Memprof2.stop 58 | ``` 59 | 60 | After `GC.start`, only the very last instance of `"abc"` will still exist: 61 | 62 | ``` 63 | === Before GC 64 | 400 file.rb:2:String 65 | === After GC 66 | 40 file.rb:2:String 67 | ``` 68 | 69 | *Note*: Use `Memprof2.report!` to clear out tracking data after printing out results. 70 | 71 | Use `trace` and `ignore` options to restrict files to report. You can write patterns by regular expressions: 72 | 73 | ``` 74 | Memprof2.start 75 | 10.times{ $last_str = "abc" } 76 | GC.start 77 | Memprof2.report!(trace: /file\.rb/, ignore: /ignore_me/, out: "/path/to/file") 78 | Memprof2.stop 79 | ``` 80 | 81 | ## Memprof2.run 82 | 83 | A shorthand for `Memprof2.start/stop` that will start/stop memprof around a given block of ruby code. 84 | 85 | ```ruby 86 | Memprof2.run do 87 | 100.times{ "abc" } 88 | 100.times{ 1.23 + 1 } 89 | 100.times{ Module.new } 90 | Memprof2.report(out: "/path/to/file") 91 | end 92 | ``` 93 | 94 | For the block of ruby code, print out file:line:class pairs for ruby objects created. 95 | 96 | ``` 97 | 4000 file.rb:2:String 98 | 4000 file.rb:3:Float 99 | 4000 file.rb:4:Module 100 | ``` 101 | 102 | *Note*: You can call GC.start at the end of the block to print out only objects that are 'leaking' (i.e. objects that still have inbound references). 103 | 104 | ## Memprof2.run_with_report 105 | 106 | A shorthand for `Memprof2.start/report/stop`. 107 | 108 | Following codes work exactly same with the above example. 109 | 110 | ```ruby 111 | Memprof2.run_with_report(out: "/path/to/file") do 112 | 100.times{ "abc" } 113 | 100.times{ 1.23 + 1 } 114 | 100.times{ Module.new } 115 | end 116 | ``` 117 | 118 | ## ChangeLog 119 | 120 | See [CHANGELOG.md](CHANGELOG.md) for details. 121 | 122 | ## See Also 123 | 124 | * [Ruby でラインメモリプロファイラ](http://qiita.com/sonots/items/c14b3e3ca8e6f7dfb651) (Japanese) 125 | 126 | ## Contributing 127 | 128 | 1. Fork it 129 | 2. Create your feature branch (`git checkout -b my-new-feature`) 130 | 3. Commit your changes (`git commit -am 'Add some feature'`) 131 | 4. Push to the branch (`git push origin my-new-feature`) 132 | 5. Create new [Pull Request](../../pull/new/master) 133 | 134 | ## Copyright 135 | 136 | Copyright (c) 2014 Naotoshi Seo. See [LICENSE.txt](LICENSE.txt) for details. 137 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | require "bundler/gem_tasks" 3 | 4 | require 'rake/testtask' 5 | desc 'Run test' 6 | Rake::TestTask.new do |t| 7 | t.pattern = "test/test_*.rb" 8 | end 9 | task :default => :test 10 | 11 | desc 'Open an irb session preloaded with the gem library' 12 | task :console do 13 | sh 'irb -rubygems -I lib' 14 | end 15 | task :c => :console 16 | -------------------------------------------------------------------------------- /example.rb: -------------------------------------------------------------------------------- 1 | require 'memprof2' 2 | 3 | ############## 4 | Memprof2.start 5 | 12.times{ "abc" } 6 | Memprof2.report 7 | Memprof2.stop 8 | 9 | 10 | ############## 11 | Memprof2.start 12 | 10.times{ $last_str = "abc" } 13 | 14 | puts '=== Before GC' 15 | Memprof2.report 16 | 17 | puts '=== After GC' 18 | GC.start 19 | Memprof2.report 20 | 21 | Memprof2.stop 22 | 23 | ############# 24 | Memprof2.run do 25 | 100.times{ "abc" } 26 | 100.times{ 1.23 + 1 } 27 | 100.times{ Module.new } 28 | Memprof2.report(out: "example.out") 29 | end 30 | 31 | ############ 32 | Memprof2.run_with_report(out: "example.out") do 33 | 100.times{ "abc" } 34 | 100.times{ 1.23 + 1 } 35 | 100.times{ Module.new } 36 | end 37 | -------------------------------------------------------------------------------- /lib/memprof2.rb: -------------------------------------------------------------------------------- 1 | require 'objspace' 2 | 3 | class Memprof2 4 | class << self 5 | def start 6 | ObjectSpace.trace_object_allocations_start 7 | end 8 | 9 | def stop 10 | ObjectSpace.trace_object_allocations_stop 11 | ObjectSpace.trace_object_allocations_clear 12 | end 13 | 14 | def run(&block) 15 | ObjectSpace.trace_object_allocations(&block) 16 | end 17 | 18 | def report(opts = {}) 19 | ObjectSpace.trace_object_allocations_stop 20 | self.new.report(opts) 21 | ensure 22 | ObjectSpace.trace_object_allocations_start 23 | end 24 | 25 | def report!(opts = {}) 26 | report(opts) 27 | ObjectSpace.trace_object_allocations_clear 28 | end 29 | 30 | def run_with_report(opts = {}, &block) 31 | start 32 | yield 33 | report(opts) 34 | ensure 35 | stop 36 | end 37 | end 38 | 39 | def report(opts={}) 40 | configure(opts) 41 | results = collect_info 42 | File.open(@out, 'a') do |io| 43 | results.each do |location, memsize| 44 | io.puts "#{memsize} #{location}" 45 | end 46 | end 47 | end 48 | 49 | def configure(opts = {}) 50 | @rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] 51 | if @trace = opts[:trace] 52 | raise ArgumentError, "`trace` option must be a Regexp object" unless @trace.is_a?(Regexp) 53 | end 54 | if @ignore = opts[:ignore] 55 | raise ArgumentError, "`ignore` option must be a Regexp object" unless @ignore.is_a?(Regexp) 56 | end 57 | @out = opts[:out] || default_output 58 | self 59 | end 60 | 61 | def default_output 62 | if RUBY_PLATFORM =~ /mswin|msys|mingw|cygwin|bccwin|wince|emc/ 63 | "con" 64 | else 65 | "/dev/stdout" 66 | end 67 | end 68 | 69 | def collect_info 70 | results = {} 71 | ObjectSpace.each_object do |o| 72 | next unless (file = ObjectSpace.allocation_sourcefile(o)) 73 | next if file == __FILE__ 74 | next if (@trace and @trace !~ file) 75 | next if (@ignore and @ignore =~ file) 76 | line = ObjectSpace.allocation_sourceline(o) 77 | if RUBY_VERSION >= "2.2.0" 78 | # Ruby 2.2.0 takes into account sizeof(RVALUE) 79 | # https://bugs.ruby-lang.org/issues/8984 80 | memsize = ObjectSpace.memsize_of(o) 81 | else 82 | # Ruby < 2.2.0 does not have ways to get memsize correctly, but let me make a bid as much as possible 83 | # https://twitter.com/sonots/status/544334978515869698 84 | memsize = ObjectSpace.memsize_of(o) + @rvalue_size 85 | end 86 | memsize = @rvalue_size if memsize > 100_000_000_000 # compensate for API bug 87 | klass = o.class.name rescue "BasicObject" 88 | location = "#{file}:#{line}:#{klass}" 89 | results[location] ||= 0 90 | results[location] += memsize 91 | end 92 | results 93 | end 94 | end 95 | 96 | 97 | -------------------------------------------------------------------------------- /memprof2.gemspec: -------------------------------------------------------------------------------- 1 | Gem::Specification.new do |spec| 2 | spec.name = "memprof2" 3 | spec.version = "0.1.2" 4 | spec.authors = ["Naotoshi Seo"] 5 | spec.email = ["sonots@gmail.com"] 6 | spec.description = %q{Ruby memory profiler for >= Ruby 2.1.0} 7 | spec.summary = %q{Ruby memory profiler for >= Ruby 2.1.0} 8 | spec.homepage = "https://github.com/sonots/memprof2" 9 | spec.license = "MIT" 10 | 11 | spec.files = `git ls-files`.split($/) 12 | spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } 13 | spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) 14 | spec.require_paths = ["lib"] 15 | 16 | spec.add_development_dependency "rake" 17 | spec.add_development_dependency "test-unit" 18 | end 19 | -------------------------------------------------------------------------------- /test/helper.rb: -------------------------------------------------------------------------------- 1 | require 'test/unit' 2 | require 'memprof2' 3 | -------------------------------------------------------------------------------- /test/test_memprof2.rb: -------------------------------------------------------------------------------- 1 | require_relative 'helper' 2 | 3 | class TestMemprof2 < Test::Unit::TestCase 4 | def rvalue_size 5 | GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] 6 | end 7 | 8 | def find(results, key_match) 9 | results.find {|k, v| k =~ key_match }.last 10 | end 11 | 12 | def test_start_stop 13 | memprof = Memprof2.new.configure({}) 14 | Memprof2.start 15 | _a = "abc" 16 | 12.times{ "abc" } 17 | results = memprof.collect_info 18 | Memprof2.stop 19 | assert_equal(rvalue_size, find(results, /test_memprof2.rb:15:String/)) 20 | assert_equal(rvalue_size * 12, find(results, /test_memprof2.rb:16:String/)) 21 | end 22 | 23 | def test_run 24 | memprof = Memprof2.new.configure({}) 25 | results = nil 26 | Memprof2.run do 27 | _a = "abc" 28 | results = memprof.collect_info 29 | end 30 | assert_equal(rvalue_size, find(results, /test_memprof2.rb:27:String/)) 31 | end 32 | 33 | def test_report_out 34 | opts = {out: "tmp/test_report.out"} 35 | Memprof2.start 36 | _a = "abc" 37 | Memprof2.report(opts) 38 | Memprof2.stop 39 | assert_match("test/test_memprof2.rb:36:String\n", File.read(opts[:out])) 40 | end 41 | 42 | def test_report! 43 | opts_bang = {out: "tmp/test_report_bang.out"} 44 | opts = {out: "tmp/test_report.out"} 45 | Memprof2.start 46 | _a = "abc" 47 | Memprof2.report!(opts_bang) # clear 48 | Memprof2.report(opts) 49 | Memprof2.stop 50 | assert_match("test/test_memprof2.rb:46:String\n", File.read(opts_bang[:out])) 51 | assert_match("", File.read(opts[:out])) 52 | end 53 | 54 | def test_run_with_result 55 | opts = {out: "tmp/test_run_with_report.out"} 56 | Memprof2.run_with_report(opts) do 57 | _a = "abc" 58 | end 59 | assert_match("test/test_memprof2.rb:57:String\n", File.read(opts[:out])) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /tmp/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sonots/memprof2/56b106cb859a5e9ff302c484c0fae18fccfcee7c/tmp/.gitkeep --------------------------------------------------------------------------------