pax_global_header00006660000000000000000000000064143261120150014505gustar00rootroot0000000000000052 comment=296764ffd254077cbf13f810ef16e50fc936a365 memory_profiler-1.0.1/000077500000000000000000000000001432611201500147165ustar00rootroot00000000000000memory_profiler-1.0.1/.github/000077500000000000000000000000001432611201500162565ustar00rootroot00000000000000memory_profiler-1.0.1/.github/workflows/000077500000000000000000000000001432611201500203135ustar00rootroot00000000000000memory_profiler-1.0.1/.github/workflows/ci.yml000066400000000000000000000006261432611201500214350ustar00rootroot00000000000000name: CI on: pull_request: push: jobs: build: runs-on: ubuntu-latest name: Ruby ${{ matrix.ruby }} strategy: matrix: ruby: ["2.6", "2.7", "3.0", "3.1"] steps: - uses: actions/checkout@v2 - uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Tests run: bundle exec rake test memory_profiler-1.0.1/.gitignore000066400000000000000000000003751432611201500167130ustar00rootroot00000000000000*.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 memory_profiler-1.0.1/.rubocop.yml000066400000000000000000000001741432611201500171720ustar00rootroot00000000000000inherit_from: https://raw.githubusercontent.com/discourse/discourse/master/.rubocop.yml AllCops: TargetRubyVersion: 2.3 memory_profiler-1.0.1/CHANGELOG.md000066400000000000000000000036601432611201500165340ustar00rootroot00000000000000# Changelog ## 1.0.1 - 23-10-2022 - Adapts tests to Ruby 3.0 / 3.1 - Lazy report evaluation - Tested under Truffle Ruby ## 1.0.0 - 02-12-2020 - Added new CLI `ruby-memory-profiler` which can be used to profile scripts @fatkodima - Reduced memory usage when generating reports - Some optimizations for Ruby 2.7 - Remove EOL Rubies: 2.3 and 2.4 are no longer supported (use an earlier version of the gem if needed) ## 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 memory_profiler-1.0.1/Gemfile000066400000000000000000000006021432611201500162070ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/Guardfile000066400000000000000000000003641432611201500165460ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/LICENSE.txt000066400000000000000000000020541432611201500165420ustar00rootroot00000000000000Copyright (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. memory_profiler-1.0.1/README.md000066400000000000000000000547221432611201500162070ustar00rootroot00000000000000[![CI](https://github.com/SamSaffron/memory_profiler/workflows/CI/badge.svg)](https://github.com/SamSaffron/memory_profiler/actions?query=workflow%3ACI) [![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.5.0 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 There are two ways to use `memory_profiler`: * command line * convenience API ### Command Line The easiest way to use memory_profiler is via the command line, which requires no modifications to your program. The basic usage is: ``` $ ruby-memory-profiler [options] [--] [script-options] ``` Where `script.rb` is the program you want to profile. For a full list of options, execute the following command: ``` ruby-memory-profiler -h ``` ### Convenience API ```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 Integer * `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 memory_profiler-1.0.1/Rakefile000066400000000000000000000002471432611201500163660ustar00rootroot00000000000000# 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" memory_profiler-1.0.1/bin/000077500000000000000000000000001432611201500154665ustar00rootroot00000000000000memory_profiler-1.0.1/bin/ruby-memory-profiler000077500000000000000000000002551432611201500215250ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true $LOAD_PATH.unshift File.expand_path("../lib", __dir__) require "memory_profiler" exit MemoryProfiler::CLI.new.run(ARGV) memory_profiler-1.0.1/lib/000077500000000000000000000000001432611201500154645ustar00rootroot00000000000000memory_profiler-1.0.1/lib/memory_profiler.rb000066400000000000000000000014121432611201500212210ustar00rootroot00000000000000# 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" require "memory_profiler/cli" 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 memory_profiler-1.0.1/lib/memory_profiler/000077500000000000000000000000001432611201500206765ustar00rootroot00000000000000memory_profiler-1.0.1/lib/memory_profiler/cli.rb000066400000000000000000000103151432611201500217720ustar00rootroot00000000000000# frozen_string_literal: true require "optparse" module MemoryProfiler class CLI BIN_NAME = "ruby-memory-profiler" VERSION_INFO = "#{BIN_NAME} #{MemoryProfiler::VERSION}" STATUS_SUCCESS = 0 STATUS_ERROR = 1 DEFAULTS = { ignore_files: "memory_profiler/lib" }.freeze REPORTER_KEYS = [ :top, :trace, :ignore_files, :allow_files ].freeze RESULTS_KEYS = [ :to_file, :color_output, :retained_strings, :allocated_strings, :detailed_report, :scale_bytes, :normalize_paths ].freeze private_constant :BIN_NAME, :VERSION_INFO,:STATUS_SUCCESS, :STATUS_ERROR, :DEFAULTS, :REPORTER_KEYS, :RESULTS_KEYS # def run(argv) options = {} parser = option_parser(options) parser.parse!(argv) options = DEFAULTS.merge(options) # Make sure the user specified at least one file unless (script = argv.shift) puts parser puts "" puts "#{VERSION_INFO} | ERROR: Must specify a script to run" return STATUS_ERROR end MemoryProfiler.start(reporter_options(options)) load script STATUS_SUCCESS rescue OptionParser::InvalidOption, OptionParser::InvalidArgument, OptionParser::MissingArgument => e puts parser puts e.message STATUS_ERROR ensure report = MemoryProfiler.stop report&.pretty_print(**results_options(options)) end private def option_parser(options) OptionParser.new do |opts| opts.banner = <<~BANNER #{VERSION_INFO} A Memory Profiler for Ruby Usage: #{BIN_NAME} [options] [--] [script-options] BANNER opts.separator "" opts.separator "Options:" # Reporter options opts.on("-m", "--max=NUM", Integer, "Max number of entries to output. (Defaults to 50)") do |arg| options[:top] = arg end opts.on("--classes=CLASSES", Array, "A class or list of classes you explicitly want to trace.") do |arg| options[:trace] = arg.map { |klass| Object.const_get(klass) } end opts.on("--ignore-files=REGEXP", "A regular expression used to exclude certain files from tracing.") do |arg| options[:ignore_files] = "#{arg}|memory_profiler/lib" end opts.on("--allow-files=FILES", Array, "A string or list of strings to selectively include in tracing.") do |arg| options[:allow_files] = arg end opts.separator "" # Results options opts.on("-o", "--out=FILE", "Write output to a file instead of STDOUT.") do |arg| options[:to_file] = arg end opts.on("--[no-]color", "Force color output on or off. (Enabled by default)") do |arg| options[:color_output] = arg end opts.on("--retained-strings=NUM", Integer, "How many retained strings to print.") do |arg| options[:retained_strings] = arg end opts.on("--allocated-strings=NUM", Integer, "How many allocated strings to print.") do |arg| options[:allocated_strings] = arg end opts.on("--[no-]detailed", "Print detailed information. (Enabled by default)") do |arg| options[:detailed_report] = arg end opts.on("--scale-bytes", "Calculates unit prefixes for the numbers of bytes.") do options[:scale_bytes] = true end opts.on("--normalize-paths", "Print location paths relative to gem's source directory.") do options[:normalize_paths] = true end opts.on("--pretty", "Easily enable options 'scale-bytes' and 'normalize-paths'") do options[:scale_bytes] = options[:normalize_paths] = true end opts.separator "" opts.on_tail("-h", "--help", "Show this help message.") do puts opts exit end opts.on_tail("-v", "--version", "Show program version.") do puts VERSION_INFO exit end end end def reporter_options(options) options.select { |k, _v| REPORTER_KEYS.include?(k) } end def results_options(options) options.select { |k, _v| RESULTS_KEYS.include?(k) } end end end memory_profiler-1.0.1/lib/memory_profiler/helpers.rb000066400000000000000000000042301432611201500226640ustar00rootroot00000000000000# 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\/\d\.[^\/]+\/(?[^\/\.]+)/ =~ path stdlib elsif /(?[^\/]+\/(bin|app|lib))/ =~ path app else "other" end end def lookup_location(file, line) @location_cache[file][line] ||= "#{file}:#{line}" end KERNEL_CLASS_METHOD = Kernel.instance_method(:class) if UnboundMethod.method_defined?(:bind_call) def object_class(obj) klass = obj.class rescue nil unless Class === klass # attempt to determine the true Class when .class returns something other than a Class klass = KERNEL_CLASS_METHOD.bind_call(obj) end klass end else def object_class(obj) klass = obj.class rescue nil unless Class === klass # attempt to determine the true Class when .class returns something other than a Class klass = KERNEL_CLASS_METHOD.bind(obj).call end klass end end if Object.name.frozen? # Since Ruby 2.7 Module#name no longer allocate a new string def lookup_class_name(klass) ((klass.is_a?(Class) && klass.name) || '<>').to_s end else def lookup_class_name(klass) @class_name_cache[klass] ||= ((klass.is_a?(Class) && klass.name) || '<>').to_s end 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 memory_profiler-1.0.1/lib/memory_profiler/monochrome.rb000066400000000000000000000003131432611201500233660ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/lib/memory_profiler/polychrome.rb000066400000000000000000000024311432611201500234040ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class Polychrome def path(text) blue(text) end def string(text) green(text) end def line(text) cyan(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 memory_profiler-1.0.1/lib/memory_profiler/reporter.rb000066400000000000000000000103241432611201500230650ustar00rootroot00000000000000# 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 3.times { 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 # for whatever reason doing GC in a block is more effective at # freeing objects. # full_mark: true, immediate_mark: true, immediate_sweep: true are already default 3.times { GC.start } # another start outside of the block to release the block 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) 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 = helper.object_class(obj) 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 = GC::INTERNAL_CONSTANTS[: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 memory_profiler-1.0.1/lib/memory_profiler/results.rb000066400000000000000000000202061432611201500227240ustar00rootroot00000000000000# 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| class_eval <<~RUBY, __FILE__, __LINE__ + 1 def #{type}_#{metric}_by_#{name} # def allocated_memory_by_file @#{type}_#{metric}_by ||= {} # @allocated_memory_by ||= {} # @#{type}_#{metric}_by['#{name}'] ||= begin # @allocated_memory_by['file'] ||= begin _, stat_attribute = @@lookups.find { |(n, _stat_attribute)| n == '#{name}' } # _, stat_attribute = @@lookups.find { |(n, _stat_attribute)| n == 'file' } @#{type}.top_n_#{metric}(@top, stat_attribute) # @allocated.top_n_memory(@top, stat_attribute) end # end end # end RUBY end end end register_type 'gem', :gem register_type 'file', :file register_type 'location', :location register_type 'class', :class_name attr_writer :strings_retained, :strings_allocated attr_accessor :total_retained, :total_allocated attr_accessor :total_retained_memsize, :total_allocated_memsize def initialize @allocated = StatHash.new @retained = StatHash.new @top = 50 end def register_results(allocated, retained, top) @allocated = allocated @retained = retained @top = top self.total_allocated = allocated.size self.total_allocated_memsize = total_memsize(allocated) self.total_retained = retained.size self.total_retained_memsize = total_memsize(retained) self end def strings_allocated @strings_allocated ||= string_report(@allocated, @top) end def strings_retained @strings_retained ||= string_report(@retained, @top) 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 = Hash.new { |hash, key| hash[key] = [] } data.each_value do |stat| if stat.string_value grouped_strings[stat.string_value.object_id] << stat end end grouped_strings = grouped_strings.values if grouped_strings.size > top grouped_strings.sort_by!(&:size) grouped_strings = grouped_strings.drop(grouped_strings.size - top) end grouped_strings .sort! { |a, b| a.size == b.size ? a[0].string_value <=> b[0].string_value : b.size <=> a.size } .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 io.puts print_string_reports(io, options) end 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/\d\.[^/]+/(?[^/.]+)(?.*)! =~ path "ruby/lib/#{stdlib}#{rest}" elsif %r!(?[^/]+/(bin|app|lib))(?.*)! =~ path "#{app}#{rest}" else path end end end private def total_memsize(stat_hash) sum = 0 stat_hash.each_value do |stat| sum += stat.memsize end sum end 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 memory_profiler-1.0.1/lib/memory_profiler/stat.rb000066400000000000000000000006131432611201500221760ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/lib/memory_profiler/stat_hash.rb000066400000000000000000000001501432611201500231750ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler class StatHash < Hash include TopN end end memory_profiler-1.0.1/lib/memory_profiler/top_n.rb000066400000000000000000000022501432611201500223410ustar00rootroot00000000000000# 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) [ top_n_memory(max, metric_method), top_n_objects(max, metric_method) ] end def top_n_memory(max, metric_method) metric_memsize = Hash.new(0) each_value do |value| metric = value.send(metric_method) metric_memsize[metric] += value.memsize end metric_memsize .to_a .sort_by! { |metric, memsize| [-memsize, metric] } .take(max) .map! { |metric, memsize| { data: metric, count: memsize } } end def top_n_objects(max, metric_method) metric_objects_count = Hash.new(0) each_value do |value| metric = value.send(metric_method) metric_objects_count[metric] += 1 end metric_objects_count .to_a .sort_by! { |metric, count| [-count, metric] } .take(max) .map! { |metric, count| { data: metric, count: count } } end end end memory_profiler-1.0.1/lib/memory_profiler/version.rb000066400000000000000000000001151432611201500227050ustar00rootroot00000000000000# frozen_string_literal: true module MemoryProfiler VERSION = "1.0.1" end memory_profiler-1.0.1/memory_profiler.gemspec000066400000000000000000000014771432611201500215060ustar00rootroot00000000000000# 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.5+} spec.summary = %q{Memory profiling routines for Ruby 2.5+} spec.homepage = "https://github.com/SamSaffron/memory_profiler" spec.license = "MIT" spec.executables = ["ruby-memory-profiler"] spec.files = Dir["README.md", "CHANGELOG.md", "LICENSE.txt", "lib/**/*", "bin/ruby-memory-profiler"] spec.required_ruby_version = ">= 2.5.0" end memory_profiler-1.0.1/test/000077500000000000000000000000001432611201500156755ustar00rootroot00000000000000memory_profiler-1.0.1/test/fixtures/000077500000000000000000000000001432611201500175465ustar00rootroot00000000000000memory_profiler-1.0.1/test/fixtures/gems/000077500000000000000000000000001432611201500205015ustar00rootroot00000000000000memory_profiler-1.0.1/test/fixtures/gems/longhorn-0.1.0/000077500000000000000000000000001432611201500227615ustar00rootroot00000000000000memory_profiler-1.0.1/test/fixtures/gems/longhorn-0.1.0/lib/000077500000000000000000000000001432611201500235275ustar00rootroot00000000000000memory_profiler-1.0.1/test/fixtures/gems/longhorn-0.1.0/lib/longhorn.rb000066400000000000000000000005211432611201500257000ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/test/fixtures/gems/longhorn-0.1.0/longhorn.gemspec000066400000000000000000000004651432611201500261610ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/test/fixtures/script.rb000066400000000000000000000001401432611201500213720ustar00rootroot00000000000000# frozen_string_literal: true require_relative "gems/longhorn-0.1.0/lib/longhorn" Longhorn.run memory_profiler-1.0.1/test/test_cli.rb000066400000000000000000000054161432611201500200360ustar00rootroot00000000000000# frozen_string_literal: true require_relative 'test_helper' class TestCLI < Minitest::Test def setup @cli = MemoryProfiler::CLI.new @script_file = File.expand_path("fixtures/script.rb", __dir__) end def test_traces_only_specified_classes out, _err = capture_io do @cli.run(["--classes=String", @script_file]) end assert_includes out, "String" refute_includes out, "Array" end def test_ignore_specific_files out, _err = capture_io do @cli.run([@script_file]) end assert_includes out, "set.rb" out, _err = capture_io do @cli.run(["--ignore-files=set.rb", @script_file]) end refute_includes out, "set.rb" end def test_allow_specific_files assert_output(/set\.rb/) { @cli.run([@script_file]) } out, _err = capture_io do @cli.run(["--allow-files=longhorn", @script_file]) end refute_includes out, "set.rb" end def test_redirects_output_to_specific_file tmpdir = File.expand_path("../tmp", __dir__) Dir.mkdir(tmpdir) unless Dir.exist?(tmpdir) outfile = File.join(tmpdir, "out.txt") assert_silent { @cli.run(["--out", outfile, @script_file]) } assert_includes File.read(outfile), "Total allocated:" end def test_color_output assert_output(/\033/) { @cli.run(["--color", @script_file]) } end def test_no_color_output out, _err = capture_io do @cli.run(["--no-color", @script_file]) end refute_includes out, "\033" end def test_detailed_report assert_output(/allocated objects by location\n--------/) do @cli.run(["--detailed", @script_file]) end end def test_no_detailed_report out, _err = capture_io do @cli.run(["--no-detailed", @script_file]) end refute_includes out, "allocated objects by location\n--------" end def test_scale_bytes assert_output(/\d kB/) { @cli.run(["--scale-bytes", @script_file]) } end def test_normalize_paths out, _err = capture_io do @cli.run(["--normalize-paths", @script_file]) end assert_match(%r!\d+\s{2}longhorn-0.1.0/lib/longhorn.rb:\d+!, out) assert_match(%r!ruby/lib/set.rb!, out) end def test_pretty out, _err = capture_io do @cli.run(["--pretty", @script_file]) end assert_match(/\d kB/, out) assert_match(%r!\d+\s{2}longhorn-0.1.0/lib/longhorn.rb:\d+!, out) assert_match(%r!ruby/lib/set.rb!, out) end def test_prints_help_when_script_not_specified assert_output(/Must specify a script to run/) { @cli.run([]) } end def test_returns_success_when_everything_ok capture_io do result = @cli.run([@script_file]) assert_equal 0, result end end def test_returns_error_when_script_not_specified capture_io do result = @cli.run([]) assert_equal 1, result end end end memory_profiler-1.0.1/test/test_helper.rb000066400000000000000000000006031432611201500205370ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/test/test_helpers.rb000066400000000000000000000031161432611201500207240ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/test/test_reporter.rb000066400000000000000000000225771432611201500211400ustar00rootroot00000000000000# 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 if RUBY_VERSION < '3' # 3 times "0", 2 times for interpolated strings total_allocated = 5 * (3 + 2 + 2 + 2) unique = 20 elsif RUBY_VERSION < '3.1' # 3 times "0", 2 times for short interpolated strings, 3 times for long interpolated strings total_allocated = 5 * (3 + 2 + 3 + 3) unique = 20 else # 2 times for short interpolated strings, 3 times for long interpolated strings total_allocated = 5 * (2 + 3 + 3) unique = 15 end assert_equal(total_allocated, results.total_allocated, "#{total_allocated} strings should be allocated") assert_equal(unique, results.strings_allocated.size, "#{unique} 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 strings_allocated = RUBY_VERSION < '3' ? 2 : 1 assert_equal(strings_allocated + 1, results.total_allocated) assert_includes(0..1, results.total_retained) assert_equal(1, results.strings_allocated.size) assert_equal('String', results.allocated_objects_by_class[0][:data]) assert_equal(strings_allocated, 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 memory_profiler-1.0.1/test/test_reporter_private_start_stop.rb000066400000000000000000000021571432611201500251440ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/test/test_reporter_public_start_stop.rb000066400000000000000000000030301432611201500247370ustar00rootroot00000000000000# 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 memory_profiler-1.0.1/test/test_results.rb000066400000000000000000000053521432611201500207670ustar00rootroot00000000000000# 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(/# 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