test-prof 1.2.2 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +15 -2
- data/README.md +1 -1
- data/lib/minitest/test_prof_plugin.rb +10 -0
- data/lib/test_prof/any_fixture/dump/sqlite.rb +1 -1
- data/lib/test_prof/any_fixture.rb +1 -1
- data/lib/test_prof/before_all/adapters/active_record.rb +3 -1
- data/lib/test_prof/before_all.rb +1 -1
- data/lib/test_prof/core.rb +167 -0
- data/lib/test_prof/factory_all_stub.rb +1 -1
- data/lib/test_prof/factory_default.rb +1 -1
- data/lib/test_prof/memory_prof/minitest.rb +56 -0
- data/lib/test_prof/memory_prof/printer/number_to_human.rb +44 -0
- data/lib/test_prof/memory_prof/printer.rb +93 -0
- data/lib/test_prof/memory_prof/rspec.rb +76 -0
- data/lib/test_prof/memory_prof/tracker/linked_list.rb +119 -0
- data/lib/test_prof/memory_prof/tracker/rss_tool.rb +87 -0
- data/lib/test_prof/memory_prof/tracker.rb +84 -0
- data/lib/test_prof/memory_prof.rb +78 -0
- data/lib/test_prof/recipes/logging.rb +1 -1
- data/lib/test_prof/recipes/minitest/sample.rb +2 -2
- data/lib/test_prof/recipes/rspec/let_it_be.rb +1 -1
- data/lib/test_prof/ruby_prof.rb +19 -3
- data/lib/test_prof/stack_prof/rspec.rb +2 -1
- data/lib/test_prof/stack_prof.rb +3 -3
- data/lib/test_prof/utils/sized_ordered_set.rb +4 -0
- data/lib/test_prof/vernier/rspec.rb +59 -0
- data/lib/test_prof/vernier.rb +152 -0
- data/lib/test_prof/version.rb +1 -1
- data/lib/test_prof.rb +3 -165
- metadata +14 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: '0678bc968a267ebeece00e12338f6408683140428ecdd041ece64c70e6bb91c0'
         | 
| 4 | 
            +
              data.tar.gz: 3ab5bc88b7b5dea557847f05a16d7fc3e4c6c9212115cd16984a56b7dd7b1737
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: 5b32e7e689cef27e72ce5f7e65ca0463bae7b83dfcaf77eba17aa0cc9bae1b74d6223b6b141e805debb7640b74b75be34bf238a8f41cb3efaf0386929935d814
         | 
| 7 | 
            +
              data.tar.gz: 34200774bc30c39acc48109fbc5031fd8f48abb5f7175799a1e1bbfd7650a11a441c9ba63638bf186d78ef5cd15b7b33e64782c781a7f28bd43e43cb6d1e5588
         | 
    
        data/CHANGELOG.md
    CHANGED
    
    | @@ -2,6 +2,18 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            ## master (unreleased)
         | 
| 4 4 |  | 
| 5 | 
            +
            ## 1.3.0 (2023-11-21)
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            - Add Vernier integration. ([@palkan][])
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            - StackProf uses JSON format by default. ([@palkan][])
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            - MemoryProf ia added. ([@Vankiru][])
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            ## 1.2.3 (2023-09-11)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            - Minor fixes and dependencies upgrades.
         | 
| 16 | 
            +
             | 
| 5 17 | 
             
            ## 1.2.2 (2023-06-27)
         | 
| 6 18 |  | 
| 7 19 | 
             
            - Ignore inaccessible connection pools in `before_all`. ([@bf4][])
         | 
| @@ -156,7 +168,7 @@ And for every test run see the overall factories usage: | |
| 156 168 |  | 
| 157 169 | 
             
            ## 1.0.0.rc2 (2021-01-06)
         | 
| 158 170 |  | 
| 159 | 
            -
            - Make Rails fixtures  | 
| 171 | 
            +
            - Make Rails fixtures accessible in `before_all`. ([@palkan][])
         | 
| 160 172 |  | 
| 161 173 | 
             
            You can load and access fixtures when explicitly enabling them via `before_all(setup_fixtures: true, &block)`.
         | 
| 162 174 |  | 
| @@ -217,7 +229,7 @@ end | |
| 217 229 |  | 
| 218 230 | 
             
              See more in [#181](https://github.com/test-prof/test-prof/issues/181).
         | 
| 219 231 |  | 
| 220 | 
            -
            - Adds the ability to define stackprof | 
| 232 | 
            +
            - Adds the ability to define stackprof interval sampling by using `TEST_STACK_PROF_INTERVAL` env variable ([@LynxEyes][])
         | 
| 221 233 |  | 
| 222 234 | 
             
              Now you can use `$ TEST_STACK_PROF=1 TEST_STACK_PROF_INTERVAL=10000 rspec` to define a custom interval (in microseconds).
         | 
| 223 235 |  | 
| @@ -363,3 +375,4 @@ See [changelog](https://github.com/test-prof/test-prof/blob/v0.8.0/CHANGELOG.md) | |
| 363 375 | 
             
            [@cbarton]: https://github.com/cbarton
         | 
| 364 376 | 
             
            [@peret]: https://github.com/peret
         | 
| 365 377 | 
             
            [@bf4]: https://github.com/bf4
         | 
| 378 | 
            +
            [@Vankiru]: https://github.com/Vankiru
         | 
    
        data/README.md
    CHANGED
    
    
| @@ -2,6 +2,7 @@ | |
| 2 2 |  | 
| 3 3 | 
             
            require "test_prof/event_prof/minitest"
         | 
| 4 4 | 
             
            require "test_prof/factory_doctor/minitest"
         | 
| 5 | 
            +
            require "test_prof/memory_prof/minitest"
         | 
| 5 6 |  | 
| 6 7 | 
             
            module Minitest # :nodoc:
         | 
| 7 8 | 
             
              module TestProf # :nodoc:
         | 
| @@ -13,6 +14,8 @@ module Minitest # :nodoc: | |
| 13 14 | 
             
                    opts[:per_example] = true if ENV["EVENT_PROF_EXAMPLES"]
         | 
| 14 15 | 
             
                    opts[:fdoc] = true if ENV["FDOC"]
         | 
| 15 16 | 
             
                    opts[:sample] = true if ENV["SAMPLE"] || ENV["SAMPLE_GROUPS"]
         | 
| 17 | 
            +
                    opts[:mem_prof_mode] = ENV["TEST_MEM_PROF"] if ENV["TEST_MEM_PROF"]
         | 
| 18 | 
            +
                    opts[:mem_prof_top_count] = ENV["TEST_MEM_PROF_COUNT"] if ENV["TEST_MEM_PROF_COUNT"]
         | 
| 16 19 | 
             
                  end
         | 
| 17 20 | 
             
                end
         | 
| 18 21 | 
             
              end
         | 
| @@ -33,6 +36,12 @@ module Minitest # :nodoc: | |
| 33 36 | 
             
                opts.on "--factory-doctor", TrueClass, "Enable Factory Doctor for your examples" do |flag|
         | 
| 34 37 | 
             
                  options[:fdoc] = flag
         | 
| 35 38 | 
             
                end
         | 
| 39 | 
            +
                opts.on "--mem-prof=MODE", "Enable MemoryProf for your examples" do |flag|
         | 
| 40 | 
            +
                  options[:mem_prof_mode] = flag
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                opts.on "--mem-prof-top-count=N", "Limits MemoryProf results with N groups/examples" do |flag|
         | 
| 43 | 
            +
                  options[:mem_prof_top_count] = flag
         | 
| 44 | 
            +
                end
         | 
| 36 45 | 
             
              end
         | 
| 37 46 |  | 
| 38 47 | 
             
              def self.plugin_test_prof_init(options)
         | 
| @@ -40,6 +49,7 @@ module Minitest # :nodoc: | |
| 40 49 |  | 
| 41 50 | 
             
                reporter << TestProf::EventProfReporter.new(options[:io], options) if options[:event]
         | 
| 42 51 | 
             
                reporter << TestProf::FactoryDoctorReporter.new(options[:io], options) if options[:fdoc]
         | 
| 52 | 
            +
                reporter << TestProf::MemoryProfReporter.new(options[:io], options) if options[:mem_prof_mode]
         | 
| 43 53 |  | 
| 44 54 | 
             
                ::TestProf::MinitestSample.call if options[:sample]
         | 
| 45 55 | 
             
              end
         | 
| @@ -5,10 +5,12 @@ module TestProf | |
| 5 5 | 
             
                module Adapters
         | 
| 6 6 | 
             
                  # ActiveRecord adapter for `before_all`
         | 
| 7 7 | 
             
                  module ActiveRecord
         | 
| 8 | 
            +
                    POOL_ARGS = ((::ActiveRecord::VERSION::MAJOR > 6) ? [:writing] : []).freeze
         | 
| 9 | 
            +
             | 
| 8 10 | 
             
                    class << self
         | 
| 9 11 | 
             
                      def all_connections
         | 
| 10 12 | 
             
                        @all_connections ||= if ::ActiveRecord::Base.respond_to? :connects_to
         | 
| 11 | 
            -
                          ::ActiveRecord::Base.connection_handler.connection_pool_list.filter_map { |pool|
         | 
| 13 | 
            +
                          ::ActiveRecord::Base.connection_handler.connection_pool_list(*POOL_ARGS).filter_map { |pool|
         | 
| 12 14 | 
             
                            begin
         | 
| 13 15 | 
             
                              pool.connection
         | 
| 14 16 | 
             
                            rescue *pool_connection_errors => error
         | 
    
        data/lib/test_prof/before_all.rb
    CHANGED
    
    
| @@ -0,0 +1,167 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "fileutils"
         | 
| 4 | 
            +
            require "logger"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            require "test_prof/logging"
         | 
| 7 | 
            +
            require "test_prof/utils"
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            # Ruby applications tests profiling tools.
         | 
| 10 | 
            +
            #
         | 
| 11 | 
            +
            # Contains tools to analyze factories usage, integrate with Ruby profilers,
         | 
| 12 | 
            +
            # profile your examples using ActiveSupport notifications (if any) and
         | 
| 13 | 
            +
            # statically analyze your code with custom RuboCop cops.
         | 
| 14 | 
            +
            #
         | 
| 15 | 
            +
            # Example usage:
         | 
| 16 | 
            +
            #
         | 
| 17 | 
            +
            #   require 'test_prof'
         | 
| 18 | 
            +
            #
         | 
| 19 | 
            +
            #   # Activate a tool by providing environment variable, e.g.
         | 
| 20 | 
            +
            #   TEST_RUBY_PROF=1 rspec ...
         | 
| 21 | 
            +
            #
         | 
| 22 | 
            +
            #   # or manually in your code
         | 
| 23 | 
            +
            #   TestProf::RubyProf.run
         | 
| 24 | 
            +
            #
         | 
| 25 | 
            +
            # See other modules for more examples.
         | 
| 26 | 
            +
            module TestProf
         | 
| 27 | 
            +
              class << self
         | 
| 28 | 
            +
                include Logging
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                def config
         | 
| 31 | 
            +
                  @config ||= Configuration.new
         | 
| 32 | 
            +
                end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                def configure
         | 
| 35 | 
            +
                  yield config
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                # Returns true if we're inside RSpec
         | 
| 39 | 
            +
                def rspec?
         | 
| 40 | 
            +
                  defined?(RSpec::Core)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                # Returns true if we're inside Minitest
         | 
| 44 | 
            +
                def minitest?
         | 
| 45 | 
            +
                  defined?(Minitest)
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                # Returns true if Spring is used and not disabled
         | 
| 49 | 
            +
                def spring?
         | 
| 50 | 
            +
                  # See https://github.com/rails/spring/blob/577cf01f232bb6dbd0ade7df2df2ac209697e741/lib/spring/binstub.rb
         | 
| 51 | 
            +
                  disabled = ENV["DISABLE_SPRING"]
         | 
| 52 | 
            +
                  defined?(::Spring::Application) && (disabled.nil? || disabled.empty? || disabled == "0")
         | 
| 53 | 
            +
                end
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                # Returns the current process time
         | 
| 56 | 
            +
                def now
         | 
| 57 | 
            +
                  Process.clock_gettime(Process::CLOCK_MONOTONIC)
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
             | 
| 60 | 
            +
                # Require gem and shows a custom
         | 
| 61 | 
            +
                # message if it fails to load
         | 
| 62 | 
            +
                def require(gem_name, msg = nil)
         | 
| 63 | 
            +
                  Kernel.require gem_name
         | 
| 64 | 
            +
                  block_given? ? yield : true
         | 
| 65 | 
            +
                rescue LoadError
         | 
| 66 | 
            +
                  log(:error, msg) if msg
         | 
| 67 | 
            +
                  false
         | 
| 68 | 
            +
                end
         | 
| 69 | 
            +
             | 
| 70 | 
            +
                # Run block only if provided env var is present and
         | 
| 71 | 
            +
                # equal to the provided value (if any).
         | 
| 72 | 
            +
                # Contains workaround for applications using Spring.
         | 
| 73 | 
            +
                def activate(env_var, val = nil)
         | 
| 74 | 
            +
                  if spring?
         | 
| 75 | 
            +
                    notify_spring_detected
         | 
| 76 | 
            +
                    ::Spring.after_fork do
         | 
| 77 | 
            +
                      activate!(env_var, val) do
         | 
| 78 | 
            +
                        notify_spring_activate env_var
         | 
| 79 | 
            +
                        yield
         | 
| 80 | 
            +
                      end
         | 
| 81 | 
            +
                    end
         | 
| 82 | 
            +
                  else
         | 
| 83 | 
            +
                    activate!(env_var, val) { yield }
         | 
| 84 | 
            +
                  end
         | 
| 85 | 
            +
                end
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                # Return absolute path to asset
         | 
| 88 | 
            +
                def asset_path(filename)
         | 
| 89 | 
            +
                  ::File.expand_path(filename, ::File.join(::File.dirname(__FILE__), "..", "..", "assets"))
         | 
| 90 | 
            +
                end
         | 
| 91 | 
            +
             | 
| 92 | 
            +
                # Return a path to store artifact
         | 
| 93 | 
            +
                def artifact_path(filename)
         | 
| 94 | 
            +
                  create_artifact_dir
         | 
| 95 | 
            +
             | 
| 96 | 
            +
                  with_timestamps(
         | 
| 97 | 
            +
                    ::File.join(
         | 
| 98 | 
            +
                      config.output_dir,
         | 
| 99 | 
            +
                      with_report_suffix(
         | 
| 100 | 
            +
                        filename
         | 
| 101 | 
            +
                      )
         | 
| 102 | 
            +
                    )
         | 
| 103 | 
            +
                  )
         | 
| 104 | 
            +
                end
         | 
| 105 | 
            +
             | 
| 106 | 
            +
                def create_artifact_dir
         | 
| 107 | 
            +
                  FileUtils.mkdir_p(config.output_dir)[0]
         | 
| 108 | 
            +
                end
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                private
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                def activate!(env_var, val)
         | 
| 113 | 
            +
                  yield if ENV[env_var] && (val.nil? || val === ENV[env_var])
         | 
| 114 | 
            +
                end
         | 
| 115 | 
            +
             | 
| 116 | 
            +
                def with_timestamps(path)
         | 
| 117 | 
            +
                  return path unless config.timestamps?
         | 
| 118 | 
            +
                  timestamps = "-#{now.to_i}"
         | 
| 119 | 
            +
                  "#{path.sub(/\.\w+$/, "")}#{timestamps}#{::File.extname(path)}"
         | 
| 120 | 
            +
                end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                def with_report_suffix(path)
         | 
| 123 | 
            +
                  return path if config.report_suffix.nil?
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                  "#{path.sub(/\.\w+$/, "")}-#{config.report_suffix}#{::File.extname(path)}"
         | 
| 126 | 
            +
                end
         | 
| 127 | 
            +
             | 
| 128 | 
            +
                def notify_spring_detected
         | 
| 129 | 
            +
                  return if instance_variable_defined?(:@spring_notified)
         | 
| 130 | 
            +
                  log :info, "Spring detected"
         | 
| 131 | 
            +
                  @spring_notified = true
         | 
| 132 | 
            +
                end
         | 
| 133 | 
            +
             | 
| 134 | 
            +
                def notify_spring_activate(env_var)
         | 
| 135 | 
            +
                  log :info, "Activating #{env_var} with `Spring.after_fork`"
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
              end
         | 
| 138 | 
            +
             | 
| 139 | 
            +
              # TestProf configuration
         | 
| 140 | 
            +
              class Configuration
         | 
| 141 | 
            +
                attr_accessor :output, # IO to write logs
         | 
| 142 | 
            +
                  :color, # Whether to colorize output or not
         | 
| 143 | 
            +
                  :output_dir, # Directory to store artifacts
         | 
| 144 | 
            +
                  :timestamps, # Whether to use timestamped names for artifacts,
         | 
| 145 | 
            +
                  :report_suffix # Custom suffix for reports/artifacts
         | 
| 146 | 
            +
             | 
| 147 | 
            +
                def initialize
         | 
| 148 | 
            +
                  @output = $stdout
         | 
| 149 | 
            +
                  @color = true
         | 
| 150 | 
            +
                  @output_dir = "tmp/test_prof"
         | 
| 151 | 
            +
                  @timestamps = false
         | 
| 152 | 
            +
                  @report_suffix = ENV["TEST_PROF_REPORT"]
         | 
| 153 | 
            +
                end
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                def color?
         | 
| 156 | 
            +
                  color == true && output.is_a?(IO) && output.tty?
         | 
| 157 | 
            +
                end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                def timestamps?
         | 
| 160 | 
            +
                  timestamps == true
         | 
| 161 | 
            +
                end
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                def logger
         | 
| 164 | 
            +
                  @logger ||= Logger.new(output, formatter: Logging::Formatter.new)
         | 
| 165 | 
            +
                end
         | 
| 166 | 
            +
              end
         | 
| 167 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "minitest/base_reporter"
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Minitest
         | 
| 6 | 
            +
              module TestProf
         | 
| 7 | 
            +
                class MemoryProfReporter < BaseReporter # :nodoc:
         | 
| 8 | 
            +
                  attr_reader :tracker, :printer, :current_example
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  def initialize(io = $stdout, options = {})
         | 
| 11 | 
            +
                    super
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    configure_profiler(options)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                    @tracker = ::TestProf::MemoryProf.tracker
         | 
| 16 | 
            +
                    @printer = ::TestProf::MemoryProf.printer(tracker)
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                    @current_example = nil
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def prerecord(group, example)
         | 
| 22 | 
            +
                    set_current_example(group, example)
         | 
| 23 | 
            +
                    tracker.example_started(current_example)
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def record(example)
         | 
| 27 | 
            +
                    tracker.example_finished(current_example)
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def start
         | 
| 31 | 
            +
                    tracker.start
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def report
         | 
| 35 | 
            +
                    tracker.finish
         | 
| 36 | 
            +
                    printer.print
         | 
| 37 | 
            +
                  end
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                  private
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def set_current_example(group, example)
         | 
| 42 | 
            +
                    @current_example = {
         | 
| 43 | 
            +
                      name: example.gsub(/^test_(?:\d+_)?/, ""),
         | 
| 44 | 
            +
                      location: location_with_line_number(group, example)
         | 
| 45 | 
            +
                    }
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                  def configure_profiler(options)
         | 
| 49 | 
            +
                    ::TestProf::MemoryProf.configure do |config|
         | 
| 50 | 
            +
                      config.mode = options[:mem_prof_mode]
         | 
| 51 | 
            +
                      config.top_count = options[:mem_prof_top_count] if options[:mem_prof_top_count]
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
| @@ -0,0 +1,44 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TestProf
         | 
| 4 | 
            +
              module MemoryProf
         | 
| 5 | 
            +
                class Printer
         | 
| 6 | 
            +
                  module NumberToHuman
         | 
| 7 | 
            +
                    BASE = 1024
         | 
| 8 | 
            +
                    UNITS = %w[B KB MB GB TB PB EB ZB]
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                    class << self
         | 
| 11 | 
            +
                      def convert(number)
         | 
| 12 | 
            +
                        exponent = exponent(number)
         | 
| 13 | 
            +
                        human_size = number.to_f / (BASE**exponent)
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                        "#{round(human_size)}#{UNITS[exponent]}"
         | 
| 16 | 
            +
                      end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      private
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                      def exponent(number)
         | 
| 21 | 
            +
                        return 0 unless number.positive?
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                        max = UNITS.size - 1
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                        exponent = (Math.log(number) / Math.log(BASE)).to_i
         | 
| 26 | 
            +
                        (exponent > max) ? max : exponent
         | 
| 27 | 
            +
                      end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                      def round(number)
         | 
| 30 | 
            +
                        if integer?(number)
         | 
| 31 | 
            +
                          number.round
         | 
| 32 | 
            +
                        else
         | 
| 33 | 
            +
                          number.round(2)
         | 
| 34 | 
            +
                        end
         | 
| 35 | 
            +
                      end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      def integer?(number)
         | 
| 38 | 
            +
                        number.round == number
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
              end
         | 
| 44 | 
            +
            end
         | 
| @@ -0,0 +1,93 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "test_prof/memory_prof/printer/number_to_human"
         | 
| 4 | 
            +
            require "test_prof/ext/string_truncate"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module TestProf
         | 
| 7 | 
            +
              module MemoryProf
         | 
| 8 | 
            +
                class Printer
         | 
| 9 | 
            +
                  include Logging
         | 
| 10 | 
            +
                  using StringTruncate
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  def initialize(tracker)
         | 
| 13 | 
            +
                    @tracker = tracker
         | 
| 14 | 
            +
                  end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                  def print
         | 
| 17 | 
            +
                    messages = [
         | 
| 18 | 
            +
                      "MemoryProf results\n\n",
         | 
| 19 | 
            +
                      print_total,
         | 
| 20 | 
            +
                      print_block("groups", tracker.groups),
         | 
| 21 | 
            +
                      print_block("examples", tracker.examples)
         | 
| 22 | 
            +
                    ]
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    log :info, messages.join
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                  private
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  attr_reader :tracker
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def print_block(name, items)
         | 
| 32 | 
            +
                    return if items.empty?
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                    <<~GROUP
         | 
| 35 | 
            +
                      Top #{tracker.top_count} #{name} (by #{mode}):
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                      #{print_items(items)}
         | 
| 38 | 
            +
                    GROUP
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  def print_items(items)
         | 
| 42 | 
            +
                    messages =
         | 
| 43 | 
            +
                      items.map do |item|
         | 
| 44 | 
            +
                        <<~ITEM
         | 
| 45 | 
            +
                          #{item[:name].truncate(30)} (#{item[:location]}) – +#{memory_amount(item)} (#{memory_percentage(item)}%)
         | 
| 46 | 
            +
                        ITEM
         | 
| 47 | 
            +
                      end
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    messages.join
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def memory_percentage(item)
         | 
| 53 | 
            +
                    (100.0 * item[:memory] / tracker.total_memory).round(2)
         | 
| 54 | 
            +
                  end
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                  def number_to_human(value)
         | 
| 57 | 
            +
                    NumberToHuman.convert(value)
         | 
| 58 | 
            +
                  end
         | 
| 59 | 
            +
                end
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                class AllocPrinter < Printer
         | 
| 62 | 
            +
                  private
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                  def mode
         | 
| 65 | 
            +
                    "allocations"
         | 
| 66 | 
            +
                  end
         | 
| 67 | 
            +
             | 
| 68 | 
            +
                  def print_total
         | 
| 69 | 
            +
                    "Total allocations: #{tracker.total_memory}\n\n"
         | 
| 70 | 
            +
                  end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                  def memory_amount(item)
         | 
| 73 | 
            +
                    item[:memory]
         | 
| 74 | 
            +
                  end
         | 
| 75 | 
            +
                end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
                class RssPrinter < Printer
         | 
| 78 | 
            +
                  private
         | 
| 79 | 
            +
             | 
| 80 | 
            +
                  def mode
         | 
| 81 | 
            +
                    "RSS"
         | 
| 82 | 
            +
                  end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                  def print_total
         | 
| 85 | 
            +
                    "Final RSS: #{number_to_human(tracker.total_memory)}\n\n"
         | 
| 86 | 
            +
                  end
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  def memory_amount(item)
         | 
| 89 | 
            +
                    number_to_human(item[:memory])
         | 
| 90 | 
            +
                  end
         | 
| 91 | 
            +
                end
         | 
| 92 | 
            +
              end
         | 
| 93 | 
            +
            end
         | 
| @@ -0,0 +1,76 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module TestProf
         | 
| 4 | 
            +
              module MemoryProf
         | 
| 5 | 
            +
                class RSpecListener
         | 
| 6 | 
            +
                  NOTIFICATIONS = %i[
         | 
| 7 | 
            +
                    example_started
         | 
| 8 | 
            +
                    example_finished
         | 
| 9 | 
            +
                    example_group_started
         | 
| 10 | 
            +
                    example_group_finished
         | 
| 11 | 
            +
                  ].freeze
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                  attr_reader :tracker, :printer
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  def initialize
         | 
| 16 | 
            +
                    @tracker = MemoryProf.tracker
         | 
| 17 | 
            +
                    @printer = MemoryProf.printer(tracker)
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                    @tracker.start
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def example_started(notification)
         | 
| 23 | 
            +
                    tracker.example_started(example(notification))
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def example_finished(notification)
         | 
| 27 | 
            +
                    tracker.example_finished(example(notification))
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def example_group_started(notification)
         | 
| 31 | 
            +
                    tracker.group_started(group(notification))
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def example_group_finished(notification)
         | 
| 35 | 
            +
                    tracker.group_finished(group(notification))
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def report
         | 
| 39 | 
            +
                    tracker.finish
         | 
| 40 | 
            +
                    printer.print
         | 
| 41 | 
            +
                  end
         | 
| 42 | 
            +
             | 
| 43 | 
            +
                  private
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                  def example(notification)
         | 
| 46 | 
            +
                    {
         | 
| 47 | 
            +
                      name: notification.example.description,
         | 
| 48 | 
            +
                      location: notification.example.metadata[:location]
         | 
| 49 | 
            +
                    }
         | 
| 50 | 
            +
                  end
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                  def group(notification)
         | 
| 53 | 
            +
                    {
         | 
| 54 | 
            +
                      name: notification.group.description,
         | 
| 55 | 
            +
                      location: notification.group.metadata[:location]
         | 
| 56 | 
            +
                    }
         | 
| 57 | 
            +
                  end
         | 
| 58 | 
            +
                end
         | 
| 59 | 
            +
              end
         | 
| 60 | 
            +
            end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            TestProf.activate("TEST_MEM_PROF") do
         | 
| 63 | 
            +
              RSpec.configure do |config|
         | 
| 64 | 
            +
                listener = nil
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                config.before(:suite) do
         | 
| 67 | 
            +
                  listener = TestProf::MemoryProf::RSpecListener.new
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                  config.reporter.register_listener(
         | 
| 70 | 
            +
                    listener, *TestProf::MemoryProf::RSpecListener::NOTIFICATIONS
         | 
| 71 | 
            +
                  )
         | 
| 72 | 
            +
                end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                config.after(:suite) { listener&.report }
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
            end
         |