pax_global_header00006660000000000000000000000064135272502020014510gustar00rootroot0000000000000052 comment=4238342e9970ee76ebf1c5bf60374645d6b735db ruby-memory-profiler-0.9.14/000077500000000000000000000000001352725020200157125ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/.gitignore000066400000000000000000000003751352725020200177070ustar00rootroot00000000000000*.gem *.rbc .bundle .byebug_history .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp .rubocop-https---raw-githubusercontent-com-discourse-discourse-master--rubocop-yml ruby-memory-profiler-0.9.14/.rubocop.yml000066400000000000000000000001741352725020200201660ustar00rootroot00000000000000inherit_from: https://raw.githubusercontent.com/discourse/discourse/master/.rubocop.yml AllCops: TargetRubyVersion: 2.3 ruby-memory-profiler-0.9.14/.travis.yml000066400000000000000000000002511352725020200200210ustar00rootroot00000000000000language: ruby rvm: - 2.3 - 2.4 - 2.5 - 2.6 - ruby-head before_install: - gem install bundler cache: bundler matrix: allow_failures: - rvm: ruby-head ruby-memory-profiler-0.9.14/CHANGELOG.md000066400000000000000000000030331352725020200175220ustar00rootroot00000000000000# Changelog ## 0.9.14 - 28-06-2019 - Pass 'normalize_path: true' to pretty_print to have locations stripped - Improve number formatting ## 0.9.13 - 22-03-2019 - remove support explicitly for all EOL rubies, 2.1 and 2.2 - frozen string literal comment @RST-J - scale_bytes option @RST-J ## 0.9.12 - Correct bug under-reporting memory for large string allocation @sam ## 0.9.11 - Reduce memory needed for string allocation tracing @dgynn - Use yield rather than block.call to reduce an allocation @dgynn - Ensure string allocation locations sort consistently @dgynn ## 0.9.10 - Add better detection for stdlib "gems" ## 0.9.9 - Add options for pretty printer to customize report ## 0.9.8 - Add optional start/stop sematics to memory profiler api @nicklamuro @dgynn ## 0.9.7 - Improved class name detection for proxy objects, BasicObject objects, and other edge cases @inossidabile @Hamdiakoguz @dgynn ## 0.9.6 - FIX: pretty_print was failing under some conditions @vincentwoo - FIX: if #class is somehow nil don't crash @vincentwoo ## 0.9.5 - Improved stability and performance @dgynn ## 0.9.4 - FIX: remove incorrect RVALUE offset on 2.2 @dgynn - FEATURE: add total memory usage @dgynn ## 0.9.3 - Add class reporting ## 0.9.2 - Fix incorrect syntax in rescue clause ## 0.9.0 - This is quite stable, upping version to reflect - Fixed bug where it would crash when location was nil for some reason ## 0.0.4 - Added compatibility with released version of Ruby 2.1.0 - Cleanup to use latest APIs available in 2.1.0 ## 0.0.3 - Added string analysis ruby-memory-profiler-0.9.14/Gemfile000066400000000000000000000006021352725020200172030ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' # Specify your gem's dependencies in memory_profiler.gemspec gemspec group :development, :test do gem 'rake', require: false gem 'minitest', require: false gem 'guard', platforms: [:mri_22, :mri_23] gem 'guard-minitest', platforms: [:mri_22, :mri_23] gem 'longhorn', path: 'test/fixtures/gems/longhorn-0.1.0' end ruby-memory-profiler-0.9.14/Guardfile000066400000000000000000000003641352725020200175420ustar00rootroot00000000000000# frozen_string_literal: true guard :minitest do # with Minitest::Unit watch(%r{^test/(.*)\/?test_(.*)\.rb$}) watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "test/test_#{m[2]}.rb" } watch(%r{^test/test_helper\.rb$}) { 'test' } end ruby-memory-profiler-0.9.14/LICENSE.txt000066400000000000000000000020541352725020200175360ustar00rootroot00000000000000Copyright (c) 2013 Sam Saffron MIT License 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. ruby-memory-profiler-0.9.14/README.md000066400000000000000000000537471352725020200172110ustar00rootroot00000000000000[![Build Status](https://travis-ci.org/SamSaffron/memory_profiler.svg?branch=master)](https://travis-ci.org/SamSaffron/memory_profiler) [![Gem Version](https://badge.fury.io/rb/memory_profiler.svg)](https://rubygems.org/gems/memory_profiler) # MemoryProfiler A memory profiler for Ruby ## Requirements Ruby(MRI) Version 2.3.8 and above. ## Installation Add this line to your application's Gemfile: gem 'memory_profiler' And then execute: $ bundle Or install it yourself as: $ gem install memory_profiler ## Usage ```ruby require 'memory_profiler' report = MemoryProfiler.report do # run your code here end report.pretty_print ``` Or, you can use the `.start`/`.stop` API as well: ```ruby require 'memory_profiler' MemoryProfiler.start # run your code report = MemoryProfiler.stop report.pretty_print ``` **NOTE:** `.start`/`.stop` can only be run once per report, and `.stop` will be the only time you can retrieve the report using this API. ## Options ### `report` The `report` method can take a few options: * `top`: maximum number of entries to display in a report (default is 50) * `allow_files`: include only certain files from tracing - can be given as a String, Regexp, or array of Strings * `ignore_files`: exclude certain files from tracing - can be given as a String or Regexp * `trace`: an array of classes for which you explicitly want to trace object allocations Check out `Reporter#new` for more details. ``` pry> require 'memory_profiler' pry> MemoryProfiler.report(allow_files: 'rubygems'){ require 'mime-types' }.pretty_print Total allocated 82375 Total retained 22618 allocated memory by gem ----------------------------------- rubygems x 305879 allocated memory by file ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb x 285433 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb x 18597 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems.rb x 2218 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/specification.rb x 1169 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/defaults.rb x 520 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_gem.rb x 80 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/version.rb x 80 . . . ``` ### `pretty_print` The `pretty_print` method can take a few options: * `to_file`: a path to your log file - can be given a String * `color_output`: a flag for whether to colorize output - can be given a Boolean * `retained_strings`: how many retained strings to print - can be given an Integer * `allocated_strings`: how many allocated strings to print - can be given a String * `detailed_report`: should report include detailed information - can be given a Boolean * `scale_bytes`: flag to convert byte units (e.g. 183200000 is reported as 183.2 MB, rounds with a precision of 2 decimal digits) - can be given a Boolean * `normalize_paths`: flag to remove a gem's directory path from printed locations - can be given a Boolean *Note: normalized path of a "location" from Ruby's stdlib will be prefixed with `ruby/lib/`. e.g.: `ruby/lib/set.rb`, `ruby/lib/pathname.rb`, etc.* Check out `Results#pretty_print` for more details. For example to report to file, use `pretty_print` method with `to_file` option and `path_to_your_log_file` string: ``` $ pry pry> require 'memory_profiler' pry> MemoryProfiler.report(allow_files: 'rubygems'){ require 'mime-types' }.pretty_print(to_file: 'path_to_your_log_file') $ less my_report.txt Total allocated 82375 Total retained 22618 allocated memory by gem ----------------------------------- rubygems x 305879 . . . ``` ## Example Session You can easily use memory_profiler to profile require impact of a gem, for example: ``` pry> require 'memory_profiler' pry> MemoryProfiler.report{ require 'mime-types' }.pretty_print Total allocated 82375 Total retained 22618 allocated memory by gem ----------------------------------- mime-types-2.0 x 3668277 2.1.0-github/lib x 2035704 rubygems x 305879 other x 40 allocated memory by file ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb x 3391763 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb x 2021853 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb x 285433 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader.rb x 227033 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb x 48663 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb x 18597 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/parser.so x 8200 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/generator.so x 2463 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems.rb x 2218 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/specification.rb x 1169 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/defaults.rb x 520 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader_path.rb x 417 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/version.rb x 409 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types.rb x 361 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/generic_object.rb x 241 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/ext.rb x 200 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json.rb x 120 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_gem.rb x 80 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/version.rb x 80 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime-types.rb x 40 (pry) x 40 allocated memory by location ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 1985709 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:522 x 927503 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 827676 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:302 x 499525 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:625 x 281047 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:521 x 265920 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 265920 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:523 x 265920 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 222705 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader.rb:215 x 218105 [REDACTED] allocated]objects by gem ----------------------------------- mime-types-2.0 x 56564 2.1.0-github/lib x 22210 rubygems x 3600 other x 1 allocated objects by file ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb x 56237 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb x 21978 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb x 3388 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb x 291 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb x 169 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/parser.so x 124 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems.rb x 53 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/generator.so x 37 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/specification.rb x 26 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader.rb x 24 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/defaults.rb x 13 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/version.rb x 7 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types.rb x 6 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader_path.rb x 5 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/ext.rb x 5 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json.rb x 3 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/generic_object.rb x 3 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_gem.rb x 2 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/version.rb x 2 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime-types.rb x 1 (pry) x 1 allocated objects by location ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:522 x 21337 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 21095 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 9972 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:523 x 6648 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:521 x 6648 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 6648 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 3307 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:302 x 2955 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:625 x 1663 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:60 x 837 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:224 x 312 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb:62 x 287 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/parser.so:0 x 124 [REDACTED] retained memory by gem ----------------------------------- mime-types-2.0 x 1496813 2.1.0-github/lib x 519954 rubygems x 76195 retained memory by file ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb x 1447316 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb x 516679 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb x 75946 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb x 48583 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/generator.so x 2263 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/parser.so x 892 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader.rb x 577 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader_path.rb x 297 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb x 169 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/specification.rb x 80 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types.rb x 40 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/generic_object.rb x 40 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/ext.rb x 40 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/version.rb x 40 retained memory by location ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 516181 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:302 x 499525 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 414007 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:625 x 280878 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 132960 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:523 x 66480 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:135 x 58151 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:224 x 52728 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb:62 x 48503 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 17795 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/generator.so:0 x 2263 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/parser.so:0 x 892 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader.rb:239 x 577 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader_path.rb:15 x 257 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb:124 x 169 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:122 x 89 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:46 x 89 [REDUCTED] retained objects by gem ----------------------------------- mime-types-2.0 x 15211 2.1.0-github/lib x 7089 rubygems x 318 retained objects by file ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb x 14918 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb x 7045 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb x 315 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb x 289 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/generator.so x 32 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/parser.so x 9 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/specification.rb x 2 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader_path.rb x 2 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/loader.rb x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types.rb x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/version.rb x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/generic_object.rb x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/ext.rb x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/basic_specification.rb x 1 retained objects by location ----------------------------------- /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 7035 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 4987 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 3324 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:302 x 2955 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:625 x 1662 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:523 x 1662 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:224 x 312 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 300 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/types/cache.rb:62 x 287 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/x86_64-linux/json/ext/generator.so:0 x 32 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:135 x 15 [REDUCTED] Allocated String Report ----------------------------------- "application" x 12050 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:522 x 4820 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:521 x 2410 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 2410 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 2410 "" x 6669 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:522 x 6648 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 11 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:266 x 4 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:71 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:72 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:69 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:0 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_gem.rb:39 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:70 x 1 "/" x 3342 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:523 x 3324 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 13 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:60 x 4 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:135 x 1 "encoding" x 3336 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 3324 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 10 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:135 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:60 x 1 "registered" x 3328 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 3324 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 4 "content-type" x 3327 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 3324 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 3 "references" x 2944 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 2940 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 4 "IANA" x 2824 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 1412 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:302 x 1412 [REDUCTED] Retained String Report ----------------------------------- "IANA" x 2824 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 1412 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:302 x 1412 "application" x 2410 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 1205 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 1205 "base64" x 1527 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/json/common.rb:155 x 1525 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:70 x 1 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 1 "audio" x 300 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 150 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 150 "video" x 188 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 94 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 94 "text" x 155 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:629 x 77 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/gems/2.1.0/gems/mime-types-2.0/lib/mime/type.rb:632 x 77 /home/sam/.rbenv/versions/2.1.0-github/lib/ruby/2.1.0/rubygems/core_ext/kernel_require.rb:55 x 1 [REDUCTED] ``` The data is also available in the MemoryProfiler::Results object returned. ### Retained vs Allocated The report breaks down 2 key concepts. **Retained**: long lived memory use and object count retained due to the execution of the code block. **Allocated**: All object allocation and memory allocation during code block. As a general rule "retained" will always be smaller than or equal to allocated. Memory profiler will tell you aggregate costs of the above, for example requiring the mime-types gem above results in approx 2MB of retained memory in 22K or so objects. The actual RSS cost will always be slightly higher as MRI heaps are not squashed to size and memory fragments. In future we may be able to calculate a rough long term GC cost of retained objects (for major GCs). Memory profiler also performs some String analysis to help you find strings that would heavily benefit from #freeze. In the example above the string IANA is retained in memory 2824 times, this costs you a minimum of RVALUE_SIZE (40 on x64) * 2824. ## Contributing 1. Fork it 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create new Pull Request ruby-memory-profiler-0.9.14/Rakefile000066400000000000000000000002471352725020200173620ustar00rootroot00000000000000# frozen_string_literal: true require "bundler/gem_tasks" require "rake/testtask" Rake::TestTask.new do |t| t.pattern = "test/test_*.rb" end task default: "test" ruby-memory-profiler-0.9.14/lib/000077500000000000000000000000001352725020200164605ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/lib/memory_profiler.rb000066400000000000000000000013541352725020200222220ustar00rootroot00000000000000# frozen_string_literal: true require "memory_profiler/version" require "memory_profiler/helpers" require "memory_profiler/polychrome" require "memory_profiler/monochrome" require "memory_profiler/top_n" require "memory_profiler/stat" require "memory_profiler/stat_hash" require "memory_profiler/results" require "memory_profiler/reporter" module MemoryProfiler def self.report(opts = {}, &block) Reporter.report(opts, &block) end def self.start(opts = {}) unless Reporter.current_reporter Reporter.current_reporter = Reporter.new(opts) Reporter.current_reporter.start end end def self.stop Reporter.current_reporter.stop if Reporter.current_reporter ensure Reporter.current_reporter = nil end end ruby-memory-profiler-0.9.14/lib/memory_profiler/000077500000000000000000000000001352725020200216725ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/lib/memory_profiler/helpers.rb000066400000000000000000000024101352725020200236560ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class Helpers def initialize @gem_guess_cache = Hash.new @location_cache = Hash.new { |h, k| h[k] = Hash.new.compare_by_identity } @class_name_cache = Hash.new.compare_by_identity @string_cache = Hash.new end def guess_gem(path) @gem_guess_cache[path] ||= if /(\/gems\/.*)*\/gems\/(?[^\/]+)/ =~ path gemname elsif /\/rubygems[\.\/]/ =~ path "rubygems" elsif /ruby\/2\.[^\/]+\/(?[^\/\.]+)/ =~ path stdlib elsif /(?[^\/]+\/(bin|app|lib))/ =~ path app else "other" end end def lookup_location(file, line) @location_cache[file][line] ||= "#{file}:#{line}" end def lookup_class_name(klass) @class_name_cache[klass] ||= ((klass.is_a?(Class) && klass.name) || '<>').to_s end def lookup_string(obj) # This string is shortened to 200 characters which is what the string report shows # The string report can still list unique strings longer than 200 characters # separately because the object_id of the shortened string will be different @string_cache[obj] ||= String.new << obj[0, 200] end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/monochrome.rb000066400000000000000000000003131352725020200243620ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class Monochrome def path(text) text end def string(text) text end def line(text) text end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/polychrome.rb000066400000000000000000000024311352725020200244000ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class Polychrome def path(text) gray(text) end def string(text) green(text) end def line(text) gray(text) end private def black(str) "\033[30m#{str}\033[0m" end def red(str) "\033[31m#{str}\033[0m" end def green(str) "\033[32m#{str}\033[0m" end def brown(str) "\033[33m#{str}\033[0m" end def blue(str) "\033[34m#{str}\033[0m" end def magenta(str) "\033[35m#{str}\033[0m" end def cyan(str) "\033[36m#{str}\033[0m" end def gray(str) "\033[37m#{str}\033[0m" end def bg_black(str) "\033[40m#{str}\033[0m" end def bg_red(str) "\033[41m#{str}\033[0m" end def bg_green(str) "\033[42m#{str}\033[0m" end def bg_brown(str) "\033[43m#{str}\033[0m" end def bg_blue(str) "\033[44m#{str}\033[0m" end def bg_magenta(str) "\033[45m#{str}\033[0m" end def bg_cyan(str) "\033[46m#{str}\033[0m" end def bg_gray(str) "\033[47m#{str}\033[0m" end def bold(str) "\033[1m#{str}\033[22m" end def reverse_color(str) "\033[7m#{str}\033[27m" end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/reporter.rb000066400000000000000000000103141352725020200240600ustar00rootroot00000000000000# frozen_string_literal: true require 'objspace' module MemoryProfiler # Reporter is the top level API used for generating memory reports. # # @example Measure object allocation in a block # report = Reporter.report(top: 50) do # 5.times { "foo" } # end class Reporter class << self attr_accessor :current_reporter end attr_reader :top, :trace, :generation, :report_results def initialize(opts = {}) @top = opts[:top] || 50 @trace = opts[:trace] && Array(opts[:trace]) @ignore_files = opts[:ignore_files] && Regexp.new(opts[:ignore_files]) @allow_files = opts[:allow_files] && /#{Array(opts[:allow_files]).join('|')}/ end # Helper for generating new reporter and running against block. # @param [Hash] opts the options to create a report with # @option opts :top max number of entries to output # @option opts :trace a class or an array of classes you explicitly want to trace # @option opts :ignore_files a regular expression used to exclude certain files from tracing # @option opts :allow_files a string or array of strings to selectively include in tracing # @return [MemoryProfiler::Results] def self.report(opts = {}, &block) self.new(opts).run(&block) end def start GC.start GC.start GC.start GC.disable @generation = GC.count ObjectSpace.trace_object_allocations_start end def stop ObjectSpace.trace_object_allocations_stop allocated = object_list(generation) retained = StatHash.new.compare_by_identity GC.enable GC.start GC.start GC.start # Caution: Do not allocate any new Objects between the call to GC.start and the completion of the retained # lookups. It is likely that a new Object would reuse an object_id from a GC'd object. ObjectSpace.each_object do |obj| next unless ObjectSpace.allocation_generation(obj) == generation found = allocated[obj.__id__] retained[obj.__id__] = found if found end ObjectSpace.trace_object_allocations_clear @report_results = Results.new @report_results.register_results(allocated, retained, top) end # Collects object allocation and memory of ruby code inside of passed block. def run(&block) start begin yield rescue Exception ObjectSpace.trace_object_allocations_stop GC.enable raise else stop end end private # Iterates through objects in memory of a given generation. # Stores results along with meta data of objects collected. def object_list(generation) rvalue_size = GC::INTERNAL_CONSTANTS[:RVALUE_SIZE] helper = Helpers.new result = StatHash.new.compare_by_identity ObjectSpace.each_object do |obj| next unless ObjectSpace.allocation_generation(obj) == generation file = ObjectSpace.allocation_sourcefile(obj) || "(no name)" next if @ignore_files && @ignore_files =~ file next if @allow_files && !(@allow_files =~ file) klass = obj.class rescue nil unless Class === klass # attempt to determine the true Class when .class returns something other than a Class klass = Kernel.instance_method(:class).bind(obj).call end next if @trace && !trace.include?(klass) begin line = ObjectSpace.allocation_sourceline(obj) location = helper.lookup_location(file, line) class_name = helper.lookup_class_name(klass) gem = helper.guess_gem(file) # we do memsize first to avoid freezing as a side effect and shifting # storage to the new frozen string, this happens on @hash[s] in lookup_string memsize = ObjectSpace.memsize_of(obj) string = klass == String ? helper.lookup_string(obj) : nil # compensate for API bug memsize = rvalue_size if memsize > 100_000_000_000 result[obj.__id__] = MemoryProfiler::Stat.new(class_name, gem, file, location, memsize, string) rescue # give up if any any error occurs inspecting the object end end result end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/results.rb000066400000000000000000000163061352725020200237260ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class Results UNIT_PREFIXES = { 0 => 'B', 3 => 'kB', 6 => 'MB', 9 => 'GB', 12 => 'TB', 15 => 'PB', 18 => 'EB', 21 => 'ZB', 24 => 'YB' }.freeze TYPES = ["allocated", "retained"].freeze METRICS = ["memory", "objects"].freeze NAMES = ["gem", "file", "location", "class"].freeze def self.register_type(name, stat_attribute) @@lookups ||= [] @@lookups << [name, stat_attribute] TYPES.each do |type| METRICS.each do |metric| attr_accessor "#{type}_#{metric}_by_#{name}" end end end register_type 'gem', :gem register_type 'file', :file register_type 'location', :location register_type 'class', :class_name attr_accessor :strings_retained, :strings_allocated attr_accessor :total_retained, :total_allocated attr_accessor :total_retained_memsize, :total_allocated_memsize def register_results(allocated, retained, top) @@lookups.each do |name, stat_attribute| memsize_results, count_results = allocated.top_n(top, stat_attribute) self.send("allocated_memory_by_#{name}=", memsize_results) self.send("allocated_objects_by_#{name}=", count_results) memsize_results, count_results = retained.top_n(top, stat_attribute) self.send("retained_memory_by_#{name}=", memsize_results) self.send("retained_objects_by_#{name}=", count_results) end self.strings_allocated = string_report(allocated, top) self.strings_retained = string_report(retained, top) self.total_allocated = allocated.size self.total_allocated_memsize = allocated.values.map!(&:memsize).inject(0, :+) self.total_retained = retained.size self.total_retained_memsize = retained.values.map!(&:memsize).inject(0, :+) self end def scale_bytes(bytes) return "0 B" if bytes.zero? scale = Math.log10(bytes).div(3) * 3 scale = 24 if scale > 24 "%.2f #{UNIT_PREFIXES[scale]}" % (bytes / 10.0**scale) end def string_report(data, top) grouped_strings = data.values .keep_if { |stat| stat.string_value } .group_by { |stat| stat.string_value.object_id } .values if grouped_strings.size > top cutoff = grouped_strings.sort_by!(&:size)[-top].size grouped_strings.keep_if { |list| list.size >= cutoff } end grouped_strings .sort! { |a, b| a.size == b.size ? a[0].string_value <=> b[0].string_value : b.size <=> a.size } .first(top) .map! do |list| # Return array of [string, [[location, count], [location, count], ...] [ list[0].string_value, list.group_by { |stat| stat.location } .map { |location, stat_list| [location, stat_list.size] } .sort_by!(&:last) .reverse! ] end end # Output the results of the report # @param [Hash] options the options for output # @option opts [String] :to_file a path to your log file # @option opts [Boolean] :color_output a flag for whether to colorize output # @option opts [Integer] :retained_strings how many retained strings to print # @option opts [Integer] :allocated_strings how many allocated strings to print # @option opts [Boolean] :detailed_report should report include detailed information # @option opts [Boolean] :scale_bytes calculates unit prefixes for the numbers of bytes # @option opts [Boolean] :normalize_paths print location paths relative to gem's source directory. def pretty_print(io = $stdout, **options) # Handle the special case that Ruby PrettyPrint expects `pretty_print` # to be a customized pretty printing function for a class return io.pp_object(self) if defined?(PP) && io.is_a?(PP) io = File.open(options[:to_file], "w") if options[:to_file] color_output = options.fetch(:color_output) { io.respond_to?(:isatty) && io.isatty } @colorize = color_output ? Polychrome.new : Monochrome.new if options[:scale_bytes] total_allocated_output = scale_bytes(total_allocated_memsize) total_retained_output = scale_bytes(total_retained_memsize) else total_allocated_output = "#{total_allocated_memsize} bytes" total_retained_output = "#{total_retained_memsize} bytes" end io.puts "Total allocated: #{total_allocated_output} (#{total_allocated} objects)" io.puts "Total retained: #{total_retained_output} (#{total_retained} objects)" unless options[:detailed_report] == false TYPES.each do |type| METRICS.each do |metric| NAMES.each do |name| dump_data(io, type, metric, name, options) end end end end io.puts print_string_reports(io, options) io.close if io.is_a? File end def print_string_reports(io, options) TYPES.each do |type| dump_opts = { normalize_paths: options[:normalize_paths], limit: options["#{type}_strings".to_sym] } dump_strings(io, type, dump_opts) end end def normalize_path(path) @normalize_path ||= {} @normalize_path[path] ||= begin if %r!(/gems/.*)*/gems/(?[^/]+)(?.*)! =~ path "#{gemname}#{rest}" elsif %r!ruby/2\.[^/]+/(?[^/.]+)(?.*)! =~ path "ruby/lib/#{stdlib}#{rest}" elsif %r!(?[^/]+/(bin|app|lib))(?.*)! =~ path "#{app}#{rest}" else path end end end private def print_title(io, title) io.puts io.puts title io.puts @colorize.line("-----------------------------------") end def print_output(io, topic, detail) io.puts "#{@colorize.path(topic.to_s.rjust(10))} #{detail}" end def dump_data(io, type, metric, name, options) print_title io, "#{type} #{metric} by #{name}" data = self.send "#{type}_#{metric}_by_#{name}" scale_data = metric == "memory" && options[:scale_bytes] normalize_paths = options[:normalize_paths] if data && !data.empty? data.each do |item| count = scale_data ? scale_bytes(item[:count]) : item[:count] value = normalize_paths ? normalize_path(item[:data]) : item[:data] print_output io, count, value end else io.puts "NO DATA" end nil end def dump_strings(io, type, options) strings = self.send("strings_#{type}") || [] return if strings.empty? options = {} unless options.is_a?(Hash) if (limit = options[:limit]) return if limit == 0 strings = strings[0...limit] end normalize_paths = options[:normalize_paths] print_title(io, "#{type.capitalize} String Report") strings.each do |string, stats| print_output io, (stats.reduce(0) { |a, b| a + b[1] }), @colorize.string(string.inspect) stats.sort_by { |x, y| [-y, x] }.each do |location, count| location = normalize_path(location) if normalize_paths print_output io, count, location end io.puts end nil end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/stat.rb000066400000000000000000000006131352725020200231720ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class Stat attr_reader :class_name, :gem, :file, :location, :memsize, :string_value def initialize(class_name, gem, file, location, memsize, string_value) @class_name = class_name @gem = gem @file = file @location = location @memsize = memsize @string_value = string_value end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/stat_hash.rb000066400000000000000000000001501352725020200241710ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class StatHash < Hash include TopN end end ruby-memory-profiler-0.9.14/lib/memory_profiler/top_n.rb000066400000000000000000000017151352725020200233420ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler module TopN # Fast approach for determining the top_n entries in a Hash of Stat objects. # Returns results for both memory (memsize summed) and objects allocated (count) as a tuple. def top_n(max, metric_method) stat_totals = self.values .group_by(&metric_method) .map do |metric, stats| [metric, stats.reduce(0) { |sum, stat| sum + stat.memsize }, stats.size] end stats_by_memsize = stat_totals .sort_by! { |metric, memsize, _count| [-memsize, metric] } .take(max) .map! { |metric, memsize, _count| { data: metric, count: memsize } } stats_by_count = stat_totals .sort_by! { |metric, _memsize, count| [-count, metric] } .take(max) .map! { |metric, _memsize, count| { data: metric, count: count } } [stats_by_memsize, stats_by_count] end end end ruby-memory-profiler-0.9.14/lib/memory_profiler/version.rb000066400000000000000000000001161352725020200237020ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler VERSION = "0.9.14" end ruby-memory-profiler-0.9.14/memory_profiler.gemspec000066400000000000000000000013631352725020200224740ustar00rootroot00000000000000# coding: utf-8 # frozen_string_literal: true lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'memory_profiler/version' Gem::Specification.new do |spec| spec.name = "memory_profiler" spec.version = MemoryProfiler::VERSION spec.authors = ["Sam Saffron"] spec.email = ["sam.saffron@gmail.com"] spec.description = %q{Memory profiling routines for Ruby 2.3+} spec.summary = %q{Memory profiling routines for Ruby 2.3+} spec.homepage = "https://github.com/SamSaffron/memory_profiler" spec.license = "MIT" spec.files = Dir["README.md", "CHANGELOG.md", "LICENSE.txt", "lib/**/*"] spec.required_ruby_version = ">= 2.3.0" end ruby-memory-profiler-0.9.14/test/000077500000000000000000000000001352725020200166715ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/test/fixtures/000077500000000000000000000000001352725020200205425ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/test/fixtures/gems/000077500000000000000000000000001352725020200214755ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/test/fixtures/gems/longhorn-0.1.0/000077500000000000000000000000001352725020200237555ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/test/fixtures/gems/longhorn-0.1.0/lib/000077500000000000000000000000001352725020200245235ustar00rootroot00000000000000ruby-memory-profiler-0.9.14/test/fixtures/gems/longhorn-0.1.0/lib/longhorn.rb000066400000000000000000000005211352725020200266740ustar00rootroot00000000000000# frozen_string_literal: true require 'set' module Longhorn def self.run result = Set.new ["allocated", "retained"] .product(["memory", "objects"]) .product(["gem", "file", "location", "class"]) .each do |(type, metric), name| result << "#{type} #{metric} by #{name}" end result end end ruby-memory-profiler-0.9.14/test/fixtures/gems/longhorn-0.1.0/longhorn.gemspec000066400000000000000000000004651352725020200271550ustar00rootroot00000000000000# frozen_string_literal: true Gem::Specification.new do |s| s.name = "longhorn" s.version = "0.1.0" s.licenses = ["MIT"] s.summary = "A gem to test MemoryProfiler" s.authors = ["John Doe"] s.files = ["lib/longhorn.rb"] s.homepage = "https://github.com/SamSaffron/memory_profiler" end ruby-memory-profiler-0.9.14/test/test_helper.rb000066400000000000000000000006031352725020200215330ustar00rootroot00000000000000# frozen_string_literal: true require 'memory_profiler' require 'minitest/pride' require 'minitest/autorun' require_relative 'test_helpers' FIXTURE_DIR = File.expand_path('fixtures', __dir__).freeze def require_fixture_gem(name) lib_path = File.join(FIXTURE_DIR, 'gems', "#{name}-0.1.0", 'lib') $LOAD_PATH.unshift(lib_path) unless $LOAD_PATH.include?(lib_path) require name end ruby-memory-profiler-0.9.14/test/test_helpers.rb000066400000000000000000000031161352725020200217200ustar00rootroot00000000000000# frozen_string_literal: true class Foo; end class BasicObjectSubclass < BasicObject ; end class NilReportingClass def class # return nil when asked for the class nil end end class StringReportingClass def class # return a string when asked for the class 'StringReportingClass' end end class NonStringNamedClass # return a symbol when the class is asked for the name def self.name :Symbol end end module MemoryProfiler class TestHelpers < Minitest::Test def assert_gem_parse(expected, path) helper = Helpers.new assert_equal(expected, helper.guess_gem(path)) end def test_stdlib_parse assert_gem_parse("psych", "/home/sam/.rbenv/versions/2.5.0/lib/ruby/2.5.0/psych.rb") assert_gem_parse("rss", "/home/sam/.rbenv/versions/2.5.0/lib/ruby/2.5.0/rss/utils.rb") end def test_rubygems_parse assert_gem_parse("rubygems", "/home/sam/.rbenv/versions/ruby-head/lib/ruby/2.1.0/rubygems/version.rb") end def test_standard_parse assert_gem_parse("rails_multisite", "/home/sam/Source/discourse/vendor/gems/rails_multisite/lib") end def test_another_standard_parse assert_gem_parse("activesupport-3.2.12", "/home/sam/.rbenv/versions/ruby-head/lib/ruby/gems/2.1.0/gems/activesupport-3.2.12/lib/active_support/dependencies.rb") end def test_app_path_parse assert_gem_parse("discourse/app", "/home/sam/Source/discourse/app/assets") end end end ruby-memory-profiler-0.9.14/test/test_reporter.rb000066400000000000000000000214101352725020200221150ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_helper' class TestReporter < Minitest::Test def setup @retained = [] end # Reusable block for reporting. def default_block # Create an object from a gem outside memory_profiler which allocates # its own objects internally MiniTest::Reporter.new # Create 10 strings 10.times { |i| i.to_s } # Create 1 string and retain it @retained << +"hello" # Create one object defined by the test_helpers file Foo.new end # Shared method that creates a Results with 1 retained object using options provided def create_report(options = {}, &profiled_block) profiled_block ||= -> { default_block } MemoryProfiler::Reporter.report(options, &profiled_block) end def test_basic_object results = create_report do @retained << BasicObject.new @retained << BasicObjectSubclass.new end assert_equal(2, results.total_allocated) assert_equal(2, results.total_retained) assert_equal('BasicObject', results.allocated_objects_by_class[0][:data]) assert_equal('BasicObjectSubclass', results.allocated_objects_by_class[1][:data]) assert_equal(2, results.retained_objects_by_location.length) end def test_anonymous_class_object anon_class1 = Class.new anon_class2 = Class.new(String) results = create_report do @retained << anon_class1.new @retained << anon_class2.new end assert_equal(2, results.total_allocated) assert_equal(2, results.total_retained) assert_equal('<>', results.allocated_objects_by_class[0][:data]) assert_equal(2, results.retained_objects_by_location.length) end def test_nil_reporting_class results = create_report do @retained << NilReportingClass.new end assert_equal(1, results.total_allocated) assert_equal(1, results.total_retained) assert_equal('NilReportingClass', results.allocated_objects_by_class[0][:data]) assert_equal(1, results.retained_objects_by_location.length) end def test_string_reporting_class results = create_report do @retained << StringReportingClass.new end assert_equal(1, results.total_allocated) assert_equal(1, results.total_retained) assert_equal('StringReportingClass', results.allocated_objects_by_class[0][:data]) assert_equal(1, results.retained_objects_by_location.length) end def test_counts results = create_report assert_equal(16, results.total_allocated) assert_equal(1, results.total_retained) assert_equal(1, results.retained_objects_by_location.length) end def test_class_tracing_with_array results = create_report(trace: [Foo]) assert_equal(1, results.total_allocated) assert_equal(0, results.total_retained) end def test_class_tracing_with_value results = create_report(trace: Foo) assert_equal(1, results.total_allocated) assert_equal(0, results.total_retained) end def test_ignore_file_with_regex results = create_report(ignore_files: /test_reporter\.rb/) assert_equal(3, results.total_allocated) assert_equal(0, results.total_retained) end def test_ignore_file_with_string results = create_report(ignore_files: 'test_reporter.rb|another_file.rb') assert_equal(3, results.total_allocated) assert_equal(0, results.total_retained) end def test_allow_files_with_string results = create_report(allow_files: 'test_reporter') assert_equal(13, results.total_allocated) assert_equal(1, results.total_retained) end def test_allow_files_with_array results = create_report(allow_files: ['test_reporter', 'another_file']) assert_equal(13, results.total_allocated) assert_equal(1, results.total_retained) end def test_no_color_output results = create_report io = StringIO.new results.pretty_print io, color_output: false assert(!io.string.include?("\033"), 'excludes color information') end def test_color_output results = create_report io = StringIO.new results.pretty_print io, color_output: true assert(io.string.include?("\033"), 'includes color information') end class StdoutMock < StringIO def isatty true end end def test_color_output_defaults_to_true_when_run_from_tty results = create_report io = StdoutMock.new results.pretty_print io assert(io.string.include?("\033"), 'includes color information') end def test_mono_output_defaults_to_true_when_not_run_from_tty results = create_report io = StringIO.new results.pretty_print io assert(!io.string.include?("\033"), 'excludes color information') end def test_reports_can_be_reused_with_different_color_options results = create_report io = StringIO.new results.pretty_print io, color_output: true assert(io.string.include?("\033"), 'includes color information') io = StringIO.new results.pretty_print io, color_output: false assert(!io.string.include?("\033"), 'excludes color information') end def test_non_string_named_class retained = [] results = create_report do retained << NonStringNamedClass.new retained << +"test" end io = StringIO.new results.pretty_print io assert_equal(2, results.total_allocated) assert_equal(2, results.total_retained) assert_equal('String', results.allocated_objects_by_class[0][:data]) assert_equal('Symbol', results.allocated_objects_by_class[1][:data]) assert_equal(2, results.retained_objects_by_location.length) end def test_exception_handling results = nil assert_raises Exception do results = create_report do @retained << +"hello" raise Exception, +"Raising exception" end end assert_nil(results) refute(GC.enable, "Re-enabling GC should return false because it is already enabled") # Verify that memory_profiler is not reporting on itself following the exception results = create_report(allow_files: 'lib/memory_profiler') assert_equal(0, results.total_allocated) end def test_strings_report # Note: There is something strange about `string *`. The first time it is dup'd it allocates 2 objects. string_250 = String.new("1234567890" * 25) string_300 = String.new("1234567890" * 30) results = create_report do string_250.dup string_250.dup 5.times { string_250.dup } string_300.dup end assert_equal(8, results.total_allocated, "8 strings should be allocated") assert_equal(2, results.strings_allocated.size, "2 unique strings should be observed") assert_equal(results.strings_allocated[0][0], results.strings_allocated[1][0], "The 2 unique strings have the same summary string") assert_equal(200, results.strings_allocated[0][0].size, "The first string summary should be shortened to 200 chars") assert_equal(3, results.strings_allocated[0][1].size, "The first string was allocated in 3 locations") assert_equal(5, results.strings_allocated[0][1][0][1], "The first string was allocated 5 times in the first location") end def test_no_strings_retained_report # Strings longer than 23 characters share a reference to a "shared" frozen string which should also be GC'd results = create_report do 5.times do |i| short_text = "SHORT TEXT ##{i}" short_text.dup long_text = "LONG TEXT ##{i} 12345678901234567890123456789012345678901234567890" long_text.dup very_long_text = "VERY LONG TEXT ##{i} 12345678901234567890123456789012345678901234567890 12345678901234567890123456789012345678901234567890 12345678901234567890123456789012345678901234567890 12345678901234567890123456789012345678901234567890" very_long_text.dup nil # Prevent the last frozen string from being the return value of the block end end assert_equal(45, results.total_allocated, "45 strings should be allocated") assert_equal(20, results.strings_allocated.size, "20 unique strings should be observed") assert_equal(0, results.strings_retained.size, "0 unique strings should be retained") end def test_symbols_report string = "this is a string" results = create_report do string.to_sym end assert_equal(3, results.total_allocated) assert_equal(0, results.total_retained) assert_equal(1, results.strings_allocated.size) assert_equal('String', results.allocated_objects_by_class[0][:data]) assert_equal(2, results.allocated_objects_by_class[0][:count]) assert_equal('Symbol', results.allocated_objects_by_class[1][:data]) assert_equal(1, results.allocated_objects_by_class[1][:count]) end def test_yield_block results = MemoryProfiler.report do # Do not allocate anything end assert_equal(0, results.total_allocated) assert_equal(0, results.total_retained) assert_equal(0, results.retained_objects_by_location.length) end end ruby-memory-profiler-0.9.14/test/test_reporter_private_start_stop.rb000066400000000000000000000021571352725020200261400ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_reporter' class TestReporterPrivateStartStop < TestReporter # This class extends TestReporter so it includes all of the tests from # TestReporter plus any additional test_* cases below and it # overrides create_report to use the start/stop methods # This specifically tests the private API of the start and stop methods of # the MemoryProfiler::Reporter class, and doesn't make use of the auto # instantiation of the class which is provided in the public API. # # This is meant to be a "base case" for the public API: if things are # failing when testing the functionality of the public API, but not here, # then there is something wrong when handling the `current_report` variable # that needs to be addressed. def create_report(options = {}, &profiled_block) profiled_block ||= -> { default_block } reporter = MemoryProfiler::Reporter.new(options) reporter.start profiled_block.call rescue nil reporter.stop end def test_exception_handling # This overrides and skips exception handling from the base TestReporter end end ruby-memory-profiler-0.9.14/test/test_reporter_public_start_stop.rb000066400000000000000000000030301352725020200257330ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_reporter' class TestReporterPublicStartStop < TestReporter # This class extends TestReporter so it includes all of the tests from # TestReporter plus any additional test_* cases below and it # overrides create_report to use the start/stop methods # This specifically tests the public API of the start and stop methods of the # MemoryProfiler module itself, and even does some extra tests exercising # edge case handling of `current_reporter` which is done in those methods. # # When something fails here, and not in the private api tests, then there is # something wrong specifically in the methods handling the `current_reporter` # that needs to be fixed. def create_report(options = {}, &profiled_block) profiled_block ||= -> { default_block } MemoryProfiler.start(options) profiled_block.call rescue nil MemoryProfiler.stop end def test_module_stop_with_no_start results = MemoryProfiler.stop assert_nil(results) end def test_module_double_start MemoryProfiler.start reporter = MemoryProfiler::Reporter.current_reporter MemoryProfiler.start same_reporter = MemoryProfiler::Reporter.current_reporter default_block results = MemoryProfiler.stop assert_equal(reporter, same_reporter) # Some extra here due to variables needed in the test above assert_equal(17, results.total_allocated) end def test_exception_handling # This overrides and skips exception handling from the base TestReporter end end ruby-memory-profiler-0.9.14/test/test_results.rb000066400000000000000000000053541352725020200217650ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_helper' class TestResults < Minitest::Test def test_pretty_print_works_with_no_args assert_output(/^Total allocated/, '') { MemoryProfiler::Results.new.pretty_print } end def test_pretty_print_works_with_io_arg io = StringIO.new assert_silent { MemoryProfiler::Results.new.pretty_print(io) } assert_match(/^Total allocated/, io.string) end def test_no_conflict_with_pretty_print require 'pp' assert_output(/#/) { pp(MemoryProfiler::Results.new) } end def scale_bytes_result MemoryProfiler.report { 1000.times { Array['a'..'z'][Array(0..25).sample] } } end def verify_unscaled_result(result, io) total_size = result.total_allocated_memsize array_size = result.allocated_memory_by_class.detect { |h| h[:data] == 'Array' }[:count] assert_match(/^Total allocated: #{total_size} bytes/, io.string, 'The total allocated memsize is unscaled.') assert_match(/^ +#{array_size} Array$/, io.string, 'The allocated memsize for Array is unscaled.') end def test_scale_bytes_default result = scale_bytes_result io = StringIO.new result.pretty_print(io) verify_unscaled_result result, io end def test_scale_bytes_off result = scale_bytes_result io = StringIO.new result.pretty_print(io, scale_bytes: false) verify_unscaled_result result, io end def test_scale_bytes_true result = scale_bytes_result total_size = result.total_allocated_memsize / 1000.0 array_size = result.allocated_memory_by_class.detect { |h| h[:data] == 'Array' }[:count] / 1000.0 io = StringIO.new result.pretty_print(io, scale_bytes: true) assert_match(/^Total allocated: #{"%.2f" % total_size} kB/, io.string, 'The total allocated memsize is scaled.') assert_match(/^ +#{"%.2f" % array_size} kB Array$/, io.string, 'The allocated memsize for Array is scaled.') end def normalized_paths_report MemoryProfiler.report do require_fixture_gem 'longhorn' Longhorn.run end end def test_normalize_paths_default report = normalized_paths_report io = StringIO.new report.pretty_print(io) assert_match(%r!fixtures/gems/longhorn-0.1.0/lib/longhorn.rb!, io.string) end def test_normalize_paths_false report = normalized_paths_report io = StringIO.new report.pretty_print(io, normalize_paths: false) assert_match(%r!fixtures/gems/longhorn-0.1.0/lib/longhorn.rb!, io.string) end def test_normalize_paths_true report = normalized_paths_report io = StringIO.new report.pretty_print(io, normalize_paths: true) assert_match(%r!\d+\s{2}longhorn-0.1.0/lib/longhorn.rb:\d+!, io.string) assert_match(%r!ruby/lib/set.rb!, io.string) end end ruby-memory-profiler-0.9.14/test/test_stat_hash.rb000066400000000000000000000001501352725020200222270ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_helper' class TestStatHash < Minitest::Test end ruby-memory-profiler-0.9.14/test/test_top_n.rb000066400000000000000000000035031352725020200213750ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_helper' class HashWithTopN < Hash include MemoryProfiler::TopN end class TestTopN < Minitest::Test TestItem = Struct.new(:metric1, :metric2, :memsize) def sample_data HashWithTopN.new.merge!( 1 => TestItem.new('class1', 'gem1', 100), 2 => TestItem.new('class1', 'gem2', 100), 3 => TestItem.new('class1', 'gem3', 100), 4 => TestItem.new('class2', 'gem1', 100), 5 => TestItem.new('class2', 'gem1', 100), 6 => TestItem.new('class2', 'gem1', 100), 7 => TestItem.new('class3', 'gem2', 1000) ) end def test_top_n_small_n data = sample_data metric1_results = data.top_n(2, :metric1) metric2_results = data.top_n(2, :metric2) assert_equal([[{ data: "class3", count: 1000 }, { data: "class1", count: 300 }], [{ data: "class1", count: 3 }, { data: "class2", count: 3 }]], metric1_results) assert_equal([[{ data: "gem2", count: 1100 }, { data: "gem1", count: 400 }], [{ data: "gem1", count: 4 }, { data: "gem2", count: 2 }]], metric2_results) end def test_top_n_large_n data = sample_data metric1_results = data.top_n(50, :metric1) metric2_results = data.top_n(50, :metric2) assert_equal([[{ data: "class3", count: 1000 }, { data: "class1", count: 300 }, { data: "class2", count: 300 }], [{ data: "class1", count: 3 }, { data: "class2", count: 3 }, { data: "class3", count: 1 }]], metric1_results) assert_equal([[{ data: "gem2", count: 1100 }, { data: "gem1", count: 400 }, { data: "gem3", count: 100 }], [{ data: "gem1", count: 4 }, { data: "gem2", count: 2 }, { data: "gem3", count: 1 }]], metric2_results) end end