pax_global_header00006660000000000000000000000064141403740030014506gustar00rootroot0000000000000052 comment=f5fe5156793e2ac19f0d5cf870bc3315e6748957 benchmark-memory-0.2.0/000077500000000000000000000000001414037400300147455ustar00rootroot00000000000000benchmark-memory-0.2.0/.codeclimate.yml000066400000000000000000000003411414037400300200150ustar00rootroot00000000000000--- engines: duplication: enabled: true config: languages: - ruby reek: enabled: true rubocop: enabled: true ratings: paths: - Gemfile.lock - "**.rb" exclude_paths: - spec/**/* benchmark-memory-0.2.0/.github/000077500000000000000000000000001414037400300163055ustar00rootroot00000000000000benchmark-memory-0.2.0/.github/workflows/000077500000000000000000000000001414037400300203425ustar00rootroot00000000000000benchmark-memory-0.2.0/.github/workflows/ci.yaml000066400000000000000000000045671414037400300216350ustar00rootroot00000000000000name: CI on: - pull_request jobs: lint: name: Lint runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-ruby@v1 with: ruby-version: 2.7 - uses: actions/cache@v2 with: path: vendor/bundle key: ${{ runner.os }}-${{ matrix.ruby }}-${{ hashFiles('Gemfile') }} restore-keys: | ${{ runner.os }}-${{ matrix.ruby }}- - run: | bundle config --local path vendor/bundle bundle config --local with ci bundle config --local without "development test" bundle check || bundle install --jobs 4 --retry 3 - name: Lint code style run: bundle exec rubocop - name: Lint documentation run: bundle exec inch test: name: Test on ${{ matrix.ruby }} runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - 2.5 - 2.6 - 2.7 - 3.0 steps: - uses: actions/checkout@v2 - uses: actions/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} - uses: actions/cache@v2 with: path: vendor/bundle key: ${{ runner.os }}-${{ matrix.ruby }}-${{ hashFiles('benchmark-memory.gemspec', 'Gemfile') }} restore-keys: | ${{ runner.os }}-${{ matrix.ruby }}- - name: Ensure proper Bundler installed if: ${{ matrix.ruby == 2.5 || matrix.ruby == 2.6 }} run: gem install bundler:2.2.28 - name: Bundle install run: | bundle config --local path vendor/bundle bundle config --local with "test" bundle config --local without "development ci" bundle check || bundle install --jobs 4 --retry 3 - name: Test without reporting coverage run: bundle exec rspec if: ${{ matrix.ruby != '2.7' }} - name: Test and report coverage run: | curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter chmod +x ./cc-test-reporter ./cc-test-reporter before-build bundle exec rspec ./cc-test-reporter after-build -t simplecov --exit-code $? if: ${{ matrix.ruby == '2.7' }} env: CC_TEST_REPORTER_ID: 36f3969bdc1e5e821f38411648b9ba4217fa4518c9f58c30f0e9b0d1639b4f19 benchmark-memory-0.2.0/.gitignore000066400000000000000000000001111414037400300167260ustar00rootroot00000000000000/.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ benchmark-memory-0.2.0/.hound.yml000066400000000000000000000000621414037400300166610ustar00rootroot00000000000000ruby: enabled: true config_file: .rubocop.yml benchmark-memory-0.2.0/.rspec000066400000000000000000000000101414037400300160510ustar00rootroot00000000000000--color benchmark-memory-0.2.0/.rubocop.yml000066400000000000000000000003631414037400300172210ustar00rootroot00000000000000AllCops: NewCops: enable SuggestExtensions: false TargetRubyVersion: 2.5 Naming/FileName: Exclude: - lib/benchmark-memory.rb Metrics/BlockLength: Exclude: - spec/**/*.rb Metrics/MethodLength: Exclude: - spec/**/*.rb benchmark-memory-0.2.0/.yardopts000066400000000000000000000001621414037400300166120ustar00rootroot00000000000000--no-private --protected --markup markdown - CHANGELOG.md CODE_OF_CONDUCT.md CONTRIBUTING.md LICENSE.md README.md benchmark-memory-0.2.0/CHANGELOG.md000066400000000000000000000032701414037400300165600ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning 2.0.0][semver]. Any violations of this scheme are considered to be bugs. [semver]: http://semver.org/spec/v2.0.0.html ## [0.2.0](https://github.com/michaelherold/benchmark-memory/compare/v0.1.1...v0.2.0) ### Added - [#11](https://github.com/michaelherold/benchmark-memory/pull/11): Drop support for Ruby < 2.4 - [@dblock](https://github.com/dblock). - [#23](https://github.com/michaelherold/benchmark-memory/pull/23): Allow for sorting the comparison by different criteria - [@michaelherold](https://github.com/michaelherold). ### Updated - [#16](https://github.com/michaelherold/benchmark-memory/pull/16): Updated Rubocop to ~> 1 and Ruby to 2.5.0+ - [@AlexWayfer](https://github.com/AlexWayfer). - [#19](https://github.com/michaelherold/benchmark-memory/pull/19): Updated to `memory_profiler` 1.0 - [@AlexWayfer](https://github.com/AlexWayfer). ## [0.1.2](https://github.com/michaelherold/benchmark-memory/compare/v0.1.1...v0.1.2) - 2017-01-30 ### Fixed - [#4](https://github.com/michaelherold/benchmark-memory/pull/4): Fix a small bug when StringIO wasn't properly required - [@rzane](https://github.com/rzane). ## [0.1.1](https://github.com/michaelherold/benchmark-memory/compare/v0.1.0...v0.1.1) - 2016-06-10 ### Fixed - Printing comparisons for multiple entries. ## [0.1.0](https://github.com/michaelherold/benchmark-memory/tree/v0.1.0) - 2016-05-18 ### Added - Main `Benchmark.memory` method. - Holding results between invocations for measuring implementations on different versions of Ruby or different versions of libraries. - Quiet mode, with no command line output. benchmark-memory-0.2.0/CODE_OF_CONDUCT.md000066400000000000000000000045321414037400300175500ustar00rootroot00000000000000# Contributor Code of Conduct As contributors and maintainers of this project, and in the interest of fostering an open and welcoming community, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, ethnicity, age, religion, or nationality. Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery * Personal attacks * Trolling or insulting/derogatory comments * Public or private harassment * Publishing other's private information, such as physical or electronic addresses, without explicit permission * Other unethical or unprofessional conduct Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. By adopting this Code of Conduct, project maintainers commit themselves to fairly and consistently applying these principles to every aspect of managing this project. Project maintainers who do not follow or enforce the Code of Conduct may be permanently removed from the project team. This code of conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting a project maintainer at michael.j.herold@gmail.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. Maintainers are obligated to maintain confidentiality with regard to the reporter of an incident. This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.3.0, available at [http://contributor-covenant.org/version/1/3/0/][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/3/0/benchmark-memory-0.2.0/CONTRIBUTING.md000066400000000000000000000057761414037400300172150ustar00rootroot00000000000000# Contributing In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project. Here are some ways *you* can contribute: * Use alpha, beta, and pre-release versions. * Report bugs. * Suggest new features. * Write or edit documentation. * Write specifications. * Write code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace). * Refactor code. * Fix [issues][]. * Review patches. [issues]: https://github.com/michaelherold/benchmark-memory/issues ## Submitting an Issue We use the [GitHub issue tracker][issues] to track bugs and features. Before submitting a bug report or feature request, check to make sure it hasn't already been submitted. When submitting a bug report, please include a [Gist](https://gist.github.com) that includes a stack trace and any details that may be necessary to reproduce the bug, including your gem version, Ruby version, and operating system. Ideally, a bug report should include a pull request with failing specs. ## Submitting a Pull Request 1. [Fork the repository](http://help.github.com/fork-a-repo/). 2. [Create a topic branch](http://learn.github.com/p/branching.html). 3. Add specs for your unimplemented feature or bug fix. 4. Run `bundle exec rake spec`. If your specs pass, return to step 3. 5. Implement your feature or bug fix. 6. Run `bundle exec rake`. If your specs or any of the linters fail, return to step 5. 7. Open `coverage/index.html`. If your changes are not completely covered by your tests, return to step 3. 8. Add documentation for your feature or bug fix. 9. Run `bundle exec inch`. If your changes are below a B in documentation, go back to step 8. 10. Commit and push your changes. 11. [Submit a pull request](http://help.github.com/send-pull-requests/). ## Tools to help you succeed After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. When writing code, you can use the helper application [Guard][guard] to automatically run tests and coverage tools whenever you modify and save a file. This helps to eliminate the tedium of running tests manually and reduces the chance that you will accidentally forget to run the tests. To use Guard, run `bundle exec guard`. Before committing code, run `rake` to check that the code conforms to the style guidelines of the project, that all of the tests are green (if you're writing a feature; if you're only submitting a failing test, then it does not have to pass!), and that the changes are sufficiently documented. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org][rubygems]. [guard]: http://guardgem.org [rubygems]: https://rubygems.org benchmark-memory-0.2.0/Gemfile000066400000000000000000000006551414037400300162460ustar00rootroot00000000000000# frozen_string_literal: true source 'https://rubygems.org' gemspec group :development do gem 'guard' gem 'guard-bundler' gem 'guard-inch' gem 'guard-rspec', '~> 4.6' gem 'guard-rubocop' gem 'rake', '>= 12.3.3' gem 'yard', '~> 0.9.11' group :ci do gem 'inch' gem 'rubocop', '~> 1' end group :test do gem 'pry' end end group :test do gem 'rspec', '~> 3.4' gem 'simplecov', '> 0.20' end benchmark-memory-0.2.0/Gemfile.lock000066400000000000000000000054241414037400300171740ustar00rootroot00000000000000PATH remote: . specs: benchmark-memory (0.2.0) memory_profiler (~> 1) GEM remote: https://rubygems.org/ specs: ast (2.4.2) coderay (1.1.3) diff-lcs (1.4.4) docile (1.3.5) ffi (1.13.1) formatador (0.2.5) guard (2.16.2) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) lumberjack (>= 1.0.12, < 2.0) nenv (~> 0.1) notiffany (~> 0.0) pry (>= 0.9.12) shellany (~> 0.0) thor (>= 0.18.1) guard-bundler (3.0.0) bundler (>= 2.1, < 3) guard (~> 2.2) guard-compat (~> 1.1) guard-compat (1.2.1) guard-inch (0.2.0) guard (~> 2) inch (~> 0) guard-rspec (4.7.3) guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) guard-rubocop (1.4.0) guard (~> 2.0) rubocop (< 2.0) inch (0.8.0) pry sparkr (>= 0.2.0) term-ansicolor yard (~> 0.9.12) listen (3.3.1) rb-fsevent (~> 0.10, >= 0.10.3) rb-inotify (~> 0.9, >= 0.9.10) lumberjack (1.2.8) memory_profiler (1.0.0) method_source (1.0.0) nenv (0.3.0) notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) parallel (1.20.1) parser (3.0.0.0) ast (~> 2.4.1) pry (0.13.1) coderay (~> 1.1) method_source (~> 1.0) rainbow (3.0.0) rake (13.0.1) rb-fsevent (0.10.4) rb-inotify (0.10.1) ffi (~> 1.0) regexp_parser (2.1.1) rexml (3.2.5) rspec (3.10.0) rspec-core (~> 3.10.0) rspec-expectations (~> 3.10.0) rspec-mocks (~> 3.10.0) rspec-core (3.10.0) rspec-support (~> 3.10.0) rspec-expectations (3.10.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-mocks (3.10.0) diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.10.0) rspec-support (3.10.0) rubocop (1.11.0) parallel (~> 1.10) parser (>= 3.0.0.0) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 3.0) rubocop-ast (1.4.1) parser (>= 2.7.1.5) ruby-progressbar (1.11.0) shellany (0.0.1) simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) simplecov_json_formatter (~> 0.1) simplecov-html (0.12.3) simplecov_json_formatter (0.1.2) sparkr (0.4.1) sync (0.5.0) term-ansicolor (1.7.1) tins (~> 1.0) thor (1.0.1) tins (1.26.0) sync unicode-display_width (2.0.0) yard (0.9.25) PLATFORMS ruby DEPENDENCIES benchmark-memory! guard guard-bundler guard-inch guard-rspec (~> 4.6) guard-rubocop inch pry rake (>= 12.3.3) rspec (~> 3.4) rubocop (~> 1) simplecov (> 0.20) yard (~> 0.9.11) BUNDLED WITH 2.2.28 benchmark-memory-0.2.0/Guardfile000066400000000000000000000006651414037400300166010ustar00rootroot00000000000000# frozen_string_literal: true guard :bundler do watch('Gemfile') watch('interactor-contracts.gemspec') end guard :inch do watch(/.+\.rb/) end guard :rspec, cmd: 'bundle exec rspec' do watch('spec/spec_helper.rb') { 'spec' } watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" } end guard :rubocop do watch(/.+\.rb$/) watch(%r{(?:.+/)?\.rubocop\.yml$}) { |m| File.dirname(m[0]) } end benchmark-memory-0.2.0/LICENSE.md000066400000000000000000000020711414037400300163510ustar00rootroot00000000000000The MIT License (MIT) Copyright (c) 2016 Michael Herold 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. benchmark-memory-0.2.0/README.md000066400000000000000000000221051414037400300162240ustar00rootroot00000000000000# benchmark-memory [![CI](https://github.com/michaelherold/benchmark-memory/workflows/CI/badge.svg)][ci] [![Code Climate](https://codeclimate.com/github/michaelherold/benchmark-memory/badges/gpa.svg)][codeclimate] [![Inline docs](http://inch-ci.org/github/michaelherold/benchmark-memory.svg?branch=main)][inch] [ci]: https://github.com/michaelherold/benchmark-memory/actions [codeclimate]: https://codeclimate.com/github/michaelherold/benchmark-memory [inch]: http://inch-ci.org/github/michaelherold/benchmark-memory benchmark-memory is a tool that helps you to benchmark the memory usage of different pieces of code. It leverages the power of [memory_profiler] to give you a metric of the total amount of memory allocated and retained by a block, as well as the number of objects and strings allocated and retained. [memory_profiler]: https://github.com/SamSaffron/memory_profiler ## Installation Add this line to your application's Gemfile: ```ruby gem "benchmark-memory" ``` And then execute: $ bundle Or install it yourself as: $ gem install benchmark-memory ## Usage Following the examples of the built-in Benchmark and Evan Phoenix's [benchmark-ips], the most common way of using benchmark-memory is through the `Benchmark.memory` wrapper. An example might look like this: ```ruby require "benchmark/memory" # First method under test def allocate_string "this string was dynamically allocated" end # Second method under test def give_frozen_string "this string is frozen".freeze end Benchmark.memory do |x| x.report("dynamic allocation") { allocate_string } x.report("frozen string") { give_frozen_string } x.compare! end ``` This example tests two methods that are defined inline. Note that you don't have to define them inline; you can just as easily use a method that you require before the benchmark or anything else that you can place in a block. When you run this example, you see the difference between the two reports: ```txt Calculating ------------------------------------- dynamic allocation 40.000 memsize ( 0.000 retained) 1.000 objects ( 0.000 retained) 1.000 strings ( 0.000 retained) frozen string 0.000 memsize ( 0.000 retained) 0.000 objects ( 0.000 retained) 0.000 strings ( 0.000 retained) Comparison: frozen string: 0 allocated dynamic allocation: 40 allocated - Infx more ``` Reading this output shows that the "dynamic allocation" example allocates one string that is not retained outside the scope of the block. The "frozen string" example, however, does not allocate anything because it reuses the frozen string that we created during the method definition. [benchmark-ips]: https://github.com/evanphx/benchmark-ips ## Options There are several options available when running a memory benchmark. ### Suppress all output (Quiet Mode) ```ruby Benchmark.memory(:quiet => true) ``` Passing a `:quiet` flag to the `Benchmark.memory` method suppresses the output of the benchmark. You might find this useful if you want to run a benchmark as part of your test suite, where outputting to `STDOUT` would be disruptive. ### Enable comparison ```ruby Benchmark.memory do |x| x.compare! end ``` Calling `#compare!` on the job within the setup block of `Benchmark.memory` enables the output of the comparison section of the benchmark. Without it, the benchmark suppresses this section and you only get the raw numbers output during calculation. By default, this compares the reports by the amount of allocated memory. You can configure the comparison along two axes. The first axis is the metric, which is one of: `:memory`, `:objects`, or `:strings`. The second is the value, which is either `:allocated` or `:retained`. Depending on what you're trying to benchmark, different configurations make sense. For example: ``` ruby Benchmark.memory do |bench| bench.compare! memory: :allocated # or, equivalently: # bench.compare! end ``` The purpose of the default configuration is benchmarking the total amount of memory an algorithm might use. If you're trying to improve a memory-intensive task, this is the mode you want. An alternative comparison might look like: ``` ruby Benchmark.memory do |bench| bench.compare! memory: :retained end ``` When you're looking for a memory leak, this configuration can help you because it compares your reports by the amount of memory that the garbage collector does not collect after the benchmark. ### Hold results between invocations ```ruby Benchmark.memory do |x| x.hold!("benchmark_results.json") end ``` Often when you want to benchmark something, you compare two implementations of the same method. This is cumbersome because you have to keep two implementations side-by-side and call them in the same manner. Alternatively, you may want to compare how a method performs on two different versions of Ruby. To make both of these scenarios easier, you can enable "holding" on the benchmark. By calling `#hold!` on the benchmark, you enable the benchmark to write to the given file to store its results in a file that can the benchmark reads in between invocations of your benchmark. For example, imagine that you have a library that exposes a method called `Statistics.calculate_monthly_recurring_revenue` that you want to optimize for memory usage because it keeps causing your worker server to run out of memory. You make some changes to the method and commit them to an `optimize-memory` branch in Git. To test the two implementations, you could then write this benchmark: ```ruby require "benchmark/memory" require "stats" # require your library file here data = [] # set up the data that it will call here Benchmark.memory do |x| x.report("original") { Stats.monthly_recurring_revenue(data) } x.report("optimized") { Stats.monthly_recurring_revenue(data) } x.compare! x.hold("bm_recurring_revenue.json") end ``` Note that the method calls are the same for both tests and that we have enabled result holding in the "bm_recurring_revenue.json" file. You could then run the following (assuming you saved your benchmark as `benchmark_mrr.rb`: ```sh $ git checkout main $ ruby benchmark_mrr.rb $ git checkout optimize-memory $ ruby benchmark_mrr.rb ``` The first invocation of `ruby benchmark_mrr.rb` runs the benchmark in the "original" entry using your code in your `main` Git branch. The second invocation runs the benchmark in the "optimized" entry using the code in your `optimize-memory` Git branch. It then collates and compares the two results to show you the difference between the two. When enabling holding, the benchmark writes to the file passed into the `#hold!` method. After you run all of the entries in the benchmark, the benchmark automatically cleans up its log by deleting the file. ## Supported Ruby Versions This library aims to support and is [tested against][ci] the following Ruby versions: * Ruby 2.5 * Ruby 2.6 * Ruby 2.7 * Ruby 3.0 If something doesn't work on one of these versions, it's a bug. This library may inadvertently work (or seem to work) on other Ruby versions, however, we will only give support for the versions listed above. If you would like this library to support another Ruby version or implementation, you may volunteer to be a maintainer. Being a maintainer entails making sure all tests run and pass on that implementation. When something breaks on your implementation, you will be responsible for providing patches in a timely fashion. If critical issues for a particular implementation exist at the time of a major release, we may drop support for that Ruby version. ## Versioning This library aims to adhere to [Semantic Versioning 2.0.0][semver]. Report violations of this scheme as bugs. Specifically, if we release a minor or patch version that breaks backward compatibility, that version should be immediately yanked and/or a new version should be immediately released that restores compatibility. We will only introduce breaking changes to the public API with new major versions. As a result of this policy, you can (and should) specify a dependency on this gem using the [Pessimistic Version Constraint][pessimistic] with two digits of precision. For example: spec.add_dependency "benchmark-memory", "~> 0.1" [pessimistic]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint [semver]: http://semver.org/spec/v2.0.0.html ## Acknowledgments This library wouldn't be possible without two projects and the people behind them: * Sam Saffron's [memory_profiler] does all of the measurement of the memory allocation and retention in the benchmarks. * I based much of the code around Evan Phoenix's [benchmark-ips] project, since it has a clean base from which to work and a logical organization. I also wanted to go for feature- and DSL-parity with it because I really like the way it works. [benchmark-ips]: https://github.com/evanphx/benchmark-ips [memory_profiler]: https://github.com/SamSaffron/memory_profiler ## License The gem is available as open source under the terms of the [MIT License][license]. [license]: http://opensource.org/licenses/MIT. benchmark-memory-0.2.0/Rakefile000066400000000000000000000005771414037400300164230ustar00rootroot00000000000000# frozen_string_literal: true require 'bundler' Bundler.setup Bundler::GemHelper.install_tasks require 'inch/rake' Inch::Rake::Suggest.new(:inch) require 'rspec/core/rake_task' RSpec::Core::RakeTask.new(:spec) require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) require 'yard/rake/yardoc_task' YARD::Rake::YardocTask.new(:yard) task default: %i[spec rubocop yard inch] benchmark-memory-0.2.0/benchmark-memory.gemspec000066400000000000000000000014441414037400300215550ustar00rootroot00000000000000# frozen_string_literal: true require File.expand_path('lib/benchmark/memory/version', __dir__) Gem::Specification.new do |spec| spec.name = 'benchmark-memory' spec.version = Benchmark::Memory::VERSION spec.authors = ['Michael Herold'] spec.email = ['michael.j.herold@gmail.com'] spec.summary = 'Benchmark-style memory profiling' spec.description = spec.summary spec.homepage = 'https://github.com/michaelherold/benchmark-memory' spec.license = 'MIT' spec.files = %w[CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile] spec.files += %w[benchmark-memory.gemspec] spec.files += Dir['lib/**/*.rb'] spec.require_paths = ['lib'] spec.required_ruby_version = '>= 2.5.0' spec.add_dependency 'memory_profiler', '~> 1' end benchmark-memory-0.2.0/bin/000077500000000000000000000000001414037400300155155ustar00rootroot00000000000000benchmark-memory-0.2.0/bin/console000077500000000000000000000005631414037400300171110ustar00rootroot00000000000000#!/usr/bin/env ruby # frozen_string_literal: true require 'bundler/setup' require 'benchmark/memory' # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require 'irb' IRB.start benchmark-memory-0.2.0/bin/setup000077500000000000000000000002031414037400300165760ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here benchmark-memory-0.2.0/lib/000077500000000000000000000000001414037400300155135ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark-memory.rb000066400000000000000000000000721414037400300212770ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory' benchmark-memory-0.2.0/lib/benchmark/000077500000000000000000000000001414037400300174455ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory.rb000066400000000000000000000013111414037400300212760ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/errors' require 'benchmark/memory/job' require 'benchmark/memory/version' # Performance benchmarking library module Benchmark # Benchmark memory usage in code to benchmark different approaches. # @see https://github.com/michaelherold/benchmark-memory module Memory # Measure memory usage in report blocks. # # @param quiet [Boolean] A flag to toggle benchmark output. # # @return [Report] def memory(quiet: false) raise ConfigurationError unless block_given? job = Job.new(quiet: quiet) yield job job.run job.run_comparison job.full_report end end extend Benchmark::Memory end benchmark-memory-0.2.0/lib/benchmark/memory/000077500000000000000000000000001414037400300207555ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory/errors.rb000066400000000000000000000004141414037400300226150ustar00rootroot00000000000000# frozen_string_literal: true module Benchmark module Memory Error = Class.new(StandardError) ConfigurationError = Class.new(Error) do def message 'You did not give a test block to your call to `Benchmark.memory`' end end end end benchmark-memory-0.2.0/lib/benchmark/memory/held_results.rb000066400000000000000000000052061414037400300240020ustar00rootroot00000000000000# frozen_string_literal: true require 'forwardable' require 'benchmark/memory/held_results/entry_serializer' module Benchmark module Memory # Collate results that should be held until the next run. class HeldResults extend Forwardable # Instantiate a new set of held results on a path. # # @param path [String, IO] The path to write held results to. def initialize(path = nil) @path = path @results = {} end # @return [String, IO] The path to write held results to. attr_accessor :path # @return [Hash{String => Measurement}] Held results from previous runs. attr_reader :results # Allow Hash-like access to the results without asking for them. def_delegator :@results, :[] # Add a result to the held results. # # @param entry [Report::Entry] The entry to hold. # # @return [void] def add_result(entry) with_hold_file('a') do |file| file.write EntrySerializer.new(entry) file.write "\n" end end # Check whether any results have been stored. # # @return [Boolean] def any? if @path.is_a?(String) File.exist?(@path) else @path.size.positive? end end # Clean up the results after all results have been collated. # # @return [void] def cleanup File.delete(@path) if @path.is_a?(String) && File.exist?(@path) end # Check whether to hold results. # # @return [Boolean] def holding? !!@path end # Check whether an entry has been added to the results. # # @param entry [#label] The entry to check. # # @return [Boolean] def include?(entry) holding? && any? && results.key?(entry.label) end # Load results from the serialized output. # # @return [void] def load return unless holding? && any? results = with_hold_file do |file| file.map { |line| EntrySerializer.load(line) } end @results = results.map { |result| [result.label, result.measurement] }.to_h end private # Execute a block on the hold file. # # @param access_mode [String] The mode to use when opening the file. # @param _block [Proc] The block to execute on each line of the file. # # @return [void] def with_hold_file(access_mode = 'r', &block) return unless @path if @path.is_a?(String) File.open(@path, access_mode, &block) else yield @path end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/held_results/000077500000000000000000000000001414037400300234525ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory/held_results/entry_serializer.rb000066400000000000000000000016731414037400300274000ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/held_results/serializer' require 'benchmark/memory/held_results/measurement_serializer' require 'benchmark/memory/report/entry' module Benchmark module Memory class HeldResults # Serialize entrys for holding between runs. class EntrySerializer < Serializer # Convert a JSON hash into an Entry. # # @param hash [Hash] A JSON document hash. # # @return [Report::Entry] def load(hash) @object = Report::Entry.new( hash['item'], MeasurementSerializer.load(hash['measurement']) ) self end # Convert the entry to a Hash. # # @return [Hash] The entry as a Hash. def to_h { item: object.label, measurement: MeasurementSerializer.new(object.measurement).to_h } end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/held_results/measurement_serializer.rb000066400000000000000000000021751414037400300305620ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/held_results/serializer' require 'benchmark/memory/held_results/metric_serializer' require 'benchmark/memory/measurement' module Benchmark module Memory class HeldResults # Serialize measurements for holding between runs. class MeasurementSerializer < Serializer # Convert a JSON hash into a Measurement. # # @param hash [Hash] A JSON document hash. # # @return [Measurement] def load(hash) @object = Measurement.new( memory: MetricSerializer.load(hash['memory']), objects: MetricSerializer.load(hash['objects']), strings: MetricSerializer.load(hash['strings']) ) self end # Convert the measurement to a Hash. # # @return [Hash] The measurement as a Hash. def to_h { memory: MetricSerializer.new(object.memory).to_h, objects: MetricSerializer.new(object.objects).to_h, strings: MetricSerializer.new(object.strings).to_h } end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/held_results/metric_serializer.rb000066400000000000000000000016361414037400300275210ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/held_results/serializer' require 'benchmark/memory/measurement/metric' module Benchmark module Memory class HeldResults # Serialize metrics for holding between runs. class MetricSerializer < Serializer # Convert a JSON hash into a Metric. # # @param hash [Hash] A JSON document hash. # # @return [Measurement::Metric] # def load(hash) @object = Measurement::Metric.new( hash['type'], hash['allocated'], hash['retained'] ) self end # Convert the metric to a Hash. # # @return [Hash] The metric as a Hash. def to_h { allocated: object.allocated, retained: object.retained, type: object.type } end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/held_results/serializer.rb000066400000000000000000000033641414037400300261560ustar00rootroot00000000000000# frozen_string_literal: true require 'json' module Benchmark module Memory class HeldResults # Serialize objects for holding between runs. class Serializer # Load an object from a JSON document. # # @param json [String] A JSON document as a string. # # @return [Object] The object converted from the JSON document. def self.load(json) json = JSON.parse(json) if json.is_a?(String) new.load(json).object end # Instantiate a new serializer. # # @param object [Object] The object to serialize. def initialize(object = nil) @object = object end # @return [Object] The object to serialize. attr_reader :object # Convert a JSON document into an object. # # @param _hash [Hash] A JSON document hash. # # @return [Object] # @raise [NotImplementedError] # If the inheriting subclass didn't implement. def load(_hash) raise( NotImplementedError, 'You must implement a concrete version in a subclass' ) end # Convert the object to a Hash. # # @return [Hash] The object as a Hash. # @raise [NotImplementedError] # If the inheriting subclass didn't implement. def to_h raise( NotImplementedError, 'You must implement a concrete version in a subclass' ) end # Convert the object to a JSON document. # # @return [String] The object as a JSON document. def to_json(*args) JSON.generate(to_h, *args) end alias to_s to_json end end end end benchmark-memory-0.2.0/lib/benchmark/memory/helpers.rb000066400000000000000000000017041414037400300227460ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/human_readable_unit' module Benchmark module Memory # Helper methods for formatting output. module Helpers # Right-justifies to a length of 20 or adds a line of padding when longer. # # @param label [#to_s] The label to justify. # # @return [String] The justified label. def rjust(label) label = label.to_s if label.size > 20 "#{label}\n#{' ' * 20}" else label.rjust(20) end end # Scale a value into human-understandable terms. # # @param value [Integer, Float] The value to scale. # # @return [String] The scaled value. def scale(value) value = HumanReadableUnit.new(value) format("%10.3f#{value.unit}", value.to_f / (1000**value.scale)) end module_function :scale # rubocop:disable Style/AccessModifierDeclarations end end end benchmark-memory-0.2.0/lib/benchmark/memory/human_readable_unit.rb000066400000000000000000000013531414037400300252720ustar00rootroot00000000000000# frozen_string_literal: true require 'delegate' module Benchmark module Memory # Transforms raw numbers into a human-readable scale and suffix class HumanReadableUnit < SimpleDelegator # @return [Integer] the exponential scale of the value def scale scale = Math.log10(__getobj__) scale = 0 if scale.infinite? scale = (scale / 3).to_i if scale <= 5 scale else 0 end end # @return [String] the single-character unit for the value def unit case scale when 1 then 'k' when 2 then 'M' when 3 then 'B' when 4 then 'T' when 5 then 'Q' else ' ' end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job.rb000066400000000000000000000073021414037400300220560ustar00rootroot00000000000000# frozen_string_literal: true require 'forwardable' require 'benchmark/memory/job/task' require 'benchmark/memory/job/io_output' require 'benchmark/memory/job/null_output' require 'benchmark/memory/held_results' require 'benchmark/memory/report' require 'benchmark/memory/report/comparator' module Benchmark module Memory # Encapsulate the memory measurements of reports. class Job extend Forwardable # Instantiate a job for containing memory performance reports. # # @param output [#puts] The output to use for showing the job results. # @param quiet [Boolean] A flag for stopping output. # # @return [Job] def initialize(output: $stdout, quiet: false) @full_report = Report.new @held_results = HeldResults.new @quiet = quiet @output = quiet? ? NullOutput.new : IOOutput.new(output) @tasks = [] end # @return [Report] the full report of all measurements in the job. attr_reader :full_report # @return [Array] the measurement tasks to run. attr_reader :tasks def_delegator :@held_results, :holding? # Check whether the job should do a comparison. # # @return [Boolean] def compare? !!@comparator end # Enable output of a comparison of the different tasks. # # @return [void] def compare!(**spec) @comparator = full_report.comparator = Report::Comparator.from_spec(spec.to_h) end # Enable holding results to compare between separate runs. # # @param held_path [String, IO] The location to save the held results. # # @return [void] def hold!(held_path) @held_results.path = held_path end # Add a measurement entry to the job to measure the specified block. # # @param label [String] The label for the measured code. # @param block [Proc] Code the measure. # # @raise [ArgumentError] if no code block is specified. def report(label = '', &block) raise ArgumentError, 'You did not specify a block for the item' unless block_given? tasks.push Task.new(label, block) end # Run the job and outputs its full report. # # @return [Report] def run @output.put_header @held_results.load tasks.each do |task| held = run_task(task) if held @output.put_hold_notice break end end full_report end # Run a task. # # @param task [Task] # # @return [Boolean] A flag indicating whether to hold or not. def run_task(task) if @held_results.include?(task) run_with_held_results(task) else run_without_held_results(task) end end # Run a comparison of the entries and puts it on the output. # # @return [void] def run_comparison return unless compare? && full_report.comparable? @output.put_comparison(full_report.comparison) end # Check whether the job is set to quiet. # # @return [Boolean] def quiet? @quiet end private def run_with_held_results(task) measurement = @held_results[task.label] full_report.add_entry(task, measurement) false end def run_without_held_results(task) measurement = task.call entry = full_report.add_entry(task, measurement) @output.put_entry(entry) if task == tasks.last @held_results.cleanup false else @held_results.add_result(entry) @held_results.holding? end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job/000077500000000000000000000000001414037400300215275ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory/job/io_output.rb000066400000000000000000000024601414037400300241050ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/job/io_output/comparison_formatter' require 'benchmark/memory/job/io_output/entry_formatter' module Benchmark module Memory class Job # Output the results of jobs into an IO. class IOOutput # Instantiate a new output that writes to an IO. # # @param io [#puts] The IO to write on. def initialize(io) @io = io end # Put the entry onto the output. # # @return [void] def put_entry(entry) @io.puts EntryFormatter.new(entry) end # Put the comparison onto the output. # # @return [void] def put_comparison(comparison) @io.puts @io.puts 'Comparison:' @io.puts ComparisonFormatter.new(comparison) end # Put the header onto the output. # # @return [void] def put_header @io.puts 'Calculating -------------------------------------' end # Put a notice that the execution is holding for another run. # # @return [void] def put_hold_notice @io.puts @io.puts 'Pausing here -- run Ruby again to ' \ 'measure the next benchmark...' end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job/io_output/000077500000000000000000000000001414037400300235565ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory/job/io_output/comparison_formatter.rb000066400000000000000000000036701414037400300303460ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/helpers' require 'benchmark/memory/job/io_output/metric_formatter' module Benchmark module Memory class Job class IOOutput # Format a comparison for use with the IOOutput. class ComparisonFormatter include Helpers # Instantiate a formatter to output an comparison into an IO. # # @param comparison [Report::Comparison] The comparison to format. def initialize(comparison) @comparison = comparison end # @return [Report::Comparison] The comparison to format. attr_reader :comparison # Format comparison to a string to put on the output. # # @return [String] def to_s return '' unless comparison.possible? output = StringIO.new best, *rest = comparison.entries rest = Array(rest) add_best_summary(best, output) rest.each do |entry| add_comparison(entry, best, output) end output.string end private def add_best_summary(best, output) output << summary_message("%20s: %10i %s\n", best) end def add_comparison(entry, best, output) output << summary_message('%20s: %10i %s - ', entry) output << comparison_between(entry, best) output << "\n" end def comparison_between(entry, best) ratio = entry.compared_metric(comparison).to_f / best.compared_metric(comparison) if ratio.abs > 1 format('%.2fx more', ratio: ratio) else 'same' end end def summary_message(message, entry) format(message, entry.label, entry.compared_metric(comparison), comparison.value) end end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job/io_output/entry_formatter.rb000066400000000000000000000021211414037400300273230ustar00rootroot00000000000000# frozen_string_literal: true require 'stringio' require 'benchmark/memory/helpers' require 'benchmark/memory/job/io_output/metric_formatter' module Benchmark module Memory class Job class IOOutput # Format entries for use with the IOOutput. class EntryFormatter include Helpers # Instantiate a formatter to output an entry into an IO. # # @param entry [Entry] The entry to format. def initialize(entry) @entry = entry end # @return [Entry] The entry to format. attr_reader :entry # Format entry to a string to put on the output. # # @return [String] def to_s output = StringIO.new output << rjust(entry.label) first, *rest = *entry.measurement output << "#{MetricFormatter.new(first)}\n" rest.each do |metric| output << "#{' ' * 20}#{MetricFormatter.new(metric)}\n" end output.string end end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job/io_output/metric_formatter.rb000066400000000000000000000024431414037400300274540ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/helpers' module Benchmark module Memory class Job class IOOutput # Format metrics for use with the IOOutput. class MetricFormatter include Helpers # Instantiate a formatter to output a metric into an IO. # # @param metric [Measurement::Metric] The metric to format. def initialize(metric) @metric = metric end # @return [Metric] The metric to format. attr_reader :metric # Format metric to a string to put on the output. # # @return [String] def to_s [allocated_message, retained_message].join(' ') end private # @return [String] the formated string for allocated memory def allocated_message format( '%s %s', allocated: scale(metric.allocated), type: metric.type ) end # @return [String] the formated string for retained memory def retained_message format( '(%s retained)', retained: scale(metric.retained) ) end end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job/null_output.rb000066400000000000000000000007431414037400300244520ustar00rootroot00000000000000# frozen_string_literal: true module Benchmark module Memory class Job # Swallow all output from a job. class NullOutput # Swallow entry output. # # @return [void] def put_entry(entry); end # Swallow comparison output. # # @return [void] def put_comparison(comparison); end # Swallow header output. # # @return [void] def put_header; end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/job/task.rb000066400000000000000000000025351414037400300230230ustar00rootroot00000000000000# frozen_string_literal: true require 'memory_profiler' require 'benchmark/memory/measurement' module Benchmark module Memory class Job # Hold a labelled job for later measurement. class Task # Instantiate a job task for later measurement. # # @param label [#to_s] The label for the benchmark. # @param action [#call] The code to be measured. # # @raise [ArgumentError] if the action does not respond to `#call`. def initialize(label, action) unless action.respond_to?(:call) raise( ArgumentError, "Invalid action (#{@action.inspect} does not respond to call)" ) end @label = label @action = action end # @return [#call] The code to be measured. attr_reader :action # @return [#to_s] The label for the benchmark. attr_reader :label # Call the action and report on its memory usage. # # @return [Measurement] the memory usage measurement of the code. def call result = while_measuring_memory_usage { action.call } Measurement.from_result(result) end private def while_measuring_memory_usage(&block) MemoryProfiler.report({}, &block) end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/measurement.rb000066400000000000000000000031471414037400300236340ustar00rootroot00000000000000# frozen_string_literal: true require 'forwardable' require 'benchmark/memory/measurement/metric_extractor' module Benchmark module Memory # Encapsulate the combined metrics of an action. class Measurement include Enumerable extend Forwardable # Create a Measurement from a MemoryProfiler::Results object. # # @param result [MemoryProfiler::Results] # The results of a MemoryProfiler report. def self.from_result(result) memory = MetricExtractor.extract_memory(result) objects = MetricExtractor.extract_objects(result) strings = MetricExtractor.extract_strings(result) new(memory: memory, objects: objects, strings: strings) end # Instantiate a Measurement of memory usage. # # @param memory [Metric] The memory usage of an action. # @param objects [Metric] The object allocations of an action. # @param strings [Metric] The string allocations of an action. def initialize(memory:, objects:, strings:) @memory = memory @objects = objects @strings = strings @metrics = [@memory, @objects, @strings] end # @return [Metric] The memory allocation metric. attr_reader :memory # @return [Array] The metrics for the measurement. attr_reader :metrics # @return [Metric] The object allocation metric. attr_reader :objects # @return [Metric] The string allocation metric. attr_reader :strings # Enumerate through the metrics when enumerating a measurement. def_delegator :metrics, :each end end end benchmark-memory-0.2.0/lib/benchmark/memory/measurement/000077500000000000000000000000001414037400300233025ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory/measurement/metric.rb000066400000000000000000000017221414037400300251140ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/helpers' module Benchmark module Memory class Measurement # Describe the ratio of allocated vs. retained memory in a measurement. class Metric # Instantiate a Metric of allocated vs. retained memory. # # @param type [Symbol] The type of memory allocated in the metric. # @param allocated [Integer] The amount allocated in the metric. # @param retained [Integer] The amount retained in the metric. def initialize(type, allocated, retained) @type = type @allocated = allocated @retained = retained end # @return [Integer] The amount allocated in the metric. attr_reader :allocated # @return [Integer] The amount retained in the metric. attr_reader :retained # @return [Symbol] The type of memory allocated in the metric. attr_reader :type end end end end benchmark-memory-0.2.0/lib/benchmark/memory/measurement/metric_extractor.rb000066400000000000000000000024441414037400300272110ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/measurement/metric' module Benchmark module Memory # Extracts metrics from a memory profiler result class MetricExtractor # Extracts the memory-specific metrics from a profiler result # # @param result [MemoryProfiler::Results] # @return [Benchmark::Memory::Measurement::Metric] def self.extract_memory(result) Measurement::Metric.new( :memsize, result.total_allocated_memsize, result.total_retained_memsize ) end # Extracts the object-specific metrics from a profiler result # # @param result [MemoryProfiler::Results] # @return [Benchmark::Memory::Measurement::Metric] def self.extract_objects(result) Measurement::Metric.new( :objects, result.total_allocated, result.total_retained ) end # Extracts the string-specific metrics from a profiler result # # @param result [MemoryProfiler::Results] # @return [Benchmark::Memory::Measurement::Metric] def self.extract_strings(result) Measurement::Metric.new( :strings, result.strings_allocated.size, result.strings_retained.size ) end end end end benchmark-memory-0.2.0/lib/benchmark/memory/report.rb000066400000000000000000000024551414037400300226230ustar00rootroot00000000000000# frozen_string_literal: true require 'benchmark/memory/report/comparison' require 'benchmark/memory/report/entry' module Benchmark module Memory # Hold the results of a set of benchmarks. class Report # Instantiate a report to hold entries of tasks and measurements. # # @return [Report] def initialize @entries = [] @comparator = Comparator.new end # @return [Comparator] The {Comparator} to use when creating the {Comparison}. attr_accessor :comparator # @return [Array] The entries in the report. attr_reader :entries # Add an entry to the report. # # @param task [Job::Task] The task to report about. # @param measurement [Measurement] The measurements from the task. # # @return [Entry] the newly created entry. def add_entry(task, measurement) entry = Entry.new(task.label, measurement) entries.push(entry) entry end # Return true if the report is comparable. # # @return [Boolean] def comparable? comparison.possible? end # Compare the entries within a report. # # @return [Comparison] def comparison @comparison ||= Comparison.new(entries, comparator) end end end end benchmark-memory-0.2.0/lib/benchmark/memory/report/000077500000000000000000000000001414037400300222705ustar00rootroot00000000000000benchmark-memory-0.2.0/lib/benchmark/memory/report/comparator.rb000066400000000000000000000040271414037400300247670ustar00rootroot00000000000000# frozen_string_literal: true module Benchmark module Memory class Report # Compares two {Entry} for the purposes of sorting and outputting a {Comparison}. class Comparator # @private METRICS = %i[memory objects strings].freeze # @private VALUES = %i[allocated retained].freeze # Instantiates a {Comparator} from a spec given by {Job#compare!} # # @param spec [Hash] The specification given for the {Comparator} # @return [Comparator] def self.from_spec(spec) raise ArgumentError, 'Only send a single metric and value, in the form memory: :allocated' if spec.length > 1 metric, value = *spec.first metric ||= :memory value ||= :allocated new(metric: metric, value: value) end # Instantiate a new comparator # # @param metric [Symbol] (see #metric) # @param value [Symbol] (see #value) def initialize(metric: :memory, value: :allocated) raise ArgumentError, "Invalid metric: #{metric.inspect}" unless METRICS.include? metric raise ArgumentError, "Invalid value: #{value.inspect}" unless VALUES.include? value @metric = metric @value = value end # @return [Symbol] The metric to compare, one of `:memory`, `:objects`, or `:strings` attr_reader :metric # @return [Symbol] The value to compare, one of `:allocated` or `:retained` attr_reader :value # Checks whether a {Comparator} equals another # # @param other [Benchmark::Memory::Comparator] The comparator to check against # # @return [Boolean] def ==(other) metric == other.metric && value == other.value end # Converts the {Comparator} to a Proc for passing to a block # # @return [Proc] def to_proc proc { |entry| entry.measurement.public_send(metric).public_send(value) } end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/report/comparison.rb000066400000000000000000000022351414037400300247710ustar00rootroot00000000000000# frozen_string_literal: true module Benchmark module Memory class Report # Compare entries against each other. class Comparison extend Forwardable # Instantiate a new comparison. # # @param entries [Array] The entries to compare. # @param comparator [Comparator] The comparator to use when generating. def initialize(entries, comparator) @entries = entries.sort_by(&comparator) @comparator = comparator end # @return [Comparator] The {Comparator} to use when generating the {Comparison}. attr_reader :comparator # @return [Array] The entries to compare. attr_reader :entries # @!method metric # @return [Symbol] The metric to compare, one of `:memory`, `:objects`, or `:strings` # @!method value # @return [Symbol] The value to compare, one of `:allocated` or `:retained` def_delegators :@comparator, :metric, :value # Check if the comparison is possible # # @return [Boolean] def possible? entries.size > 1 end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/report/entry.rb000066400000000000000000000017011414037400300237550ustar00rootroot00000000000000# frozen_string_literal: true require 'forwardable' module Benchmark module Memory class Report # An entry in a report about a benchmark. class Entry # Instantiate a new entry. # # @param label [#to_s] The entry label. # @param measurement [Measurement] The measurements for the entry. def initialize(label, measurement) @label = label @measurement = measurement end # @return [#to_s] The entry label. attr_reader :label # @return [Measurement] The measurements for the entry. attr_reader :measurement # Get the total amount of memory allocated in the entry. # # @param comparison [Comparison] The {Comparison} to compare. # @return [Integer] def compared_metric(comparison) measurement.public_send(comparison.metric).public_send(comparison.value) end end end end end benchmark-memory-0.2.0/lib/benchmark/memory/version.rb000066400000000000000000000001401414037400300227620ustar00rootroot00000000000000# frozen_string_literal: true module Benchmark module Memory VERSION = '0.2.0' end end benchmark-memory-0.2.0/spec/000077500000000000000000000000001414037400300156775ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/000077500000000000000000000000001414037400300176315ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/000077500000000000000000000000001414037400300211415ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/held_results/000077500000000000000000000000001414037400300236365ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/held_results/entry_serializer_spec.rb000066400000000000000000000033661414037400300305770ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::HeldResults::EntrySerializer do describe '.load' do it 'converts JSON documents into entries' do document = '{"item":"my super cool test",' \ '"measurement":' \ '{"memory":' \ '{"allocated":3078619,"retained":1539309,"type":"memsize"},' \ '"objects":{"allocated":2936123,"retained":0,"type":"objects"},' \ '"strings":{"allocated":100,"retained":99,"type":"strings"}}}' entry = described_class.load(document) result = described_class.new(entry).to_s expect(result).to eq(document) end end describe '#to_s' do it 'converts the entry into a JSON document' do result = described_class.new(create_entry).to_s expected_result = '{"item":"my super cool test",' \ '"measurement":' \ '{"memory":' \ '{"allocated":3078619,"retained":1539309,"type":"memsize"},' \ '"objects":{"allocated":2936123,"retained":0,"type":"objects"},' \ '"strings":{"allocated":100,"retained":99,"type":"strings"}}}' expect(result).to eq(expected_result) end end def create_entry Benchmark::Memory::Report::Entry.new( 'my super cool test', create_measurement ) end def create_measurement memsize = Benchmark::Memory::Measurement::Metric.new( :memsize, 3_078_619, 1_539_309 ) objects = Benchmark::Memory::Measurement::Metric.new( :objects, 2_936_123, 0 ) strings = Benchmark::Memory::Measurement::Metric.new( :strings, 100, 99 ) Benchmark::Memory::Measurement.new( strings: strings, objects: objects, memory: memsize ) end end benchmark-memory-0.2.0/spec/benchmark/memory/helpers_spec.rb000066400000000000000000000013461414037400300241460ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Helpers do describe '#scale' do it 'scales values into human terms' do scale = ->(value) { Benchmark::Memory::Helpers.scale(value) } expect(scale.call(1)).to eq(' 1.000 ') expect(scale.call(123)).to eq(' 123.000 ') expect(scale.call(1_234)).to eq(' 1.234k') expect(scale.call(1_234_567)).to eq(' 1.235M') expect(scale.call(1_234_567_890)).to eq(' 1.235B') expect(scale.call(1_234_567_890_123)).to eq(' 1.235T') expect(scale.call(1_234_567_890_123_456)).to eq(' 1.235Q') end end end benchmark-memory-0.2.0/spec/benchmark/memory/job/000077500000000000000000000000001414037400300217135ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/job/io_output/000077500000000000000000000000001414037400300237425ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/job/io_output/comparison_formatter_spec.rb000066400000000000000000000041731414037400300315430ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Job::IOOutput::ComparisonFormatter do describe '#to_s' do it 'is blank when the comparison is not possible' do comp = comparison([]) formatter = described_class.new(comp) expect(formatter.to_s).to be_empty end it 'outputs a comparison of the entries' do entries = [create_high_entry, create_medium_entry, create_low_entry] comp = comparison(entries) formatter = described_class.new(comp) output = formatter.to_s expect(output).not_to be_empty expect(output.split("\n").size).to eq(entries.length) end it "gives a multiplier when entries aren't equal" do comp = comparison([create_high_entry, create_low_entry]) formatter = described_class.new(comp) expect(formatter.to_s).to match(/4.00x/) end it "says 'same' when entries are equal" do comp = comparison([create_low_entry, create_low_entry]) formatter = described_class.new(comp) expect(formatter.to_s).to match(/same/) end end def comparison(entries) Benchmark::Memory::Report::Comparison.new(entries, Benchmark::Memory::Report::Comparator.new) end def create_high_entry Benchmark::Memory::Report::Entry.new( 'high', create_measurement(10_000, 5_000) ) end alias_method :create_entry, :create_high_entry def create_low_entry Benchmark::Memory::Report::Entry.new( 'low', create_measurement(2_500, 1_250) ) end def create_medium_entry Benchmark::Memory::Report::Entry.new( 'medium', create_measurement(5_000, 2_500) ) end def create_measurement(allocated, retained) memsize = Benchmark::Memory::Measurement::Metric.new( :memsize, allocated, retained ) objects = Benchmark::Memory::Measurement::Metric.new( :objects, 2_936_123, 0 ) strings = Benchmark::Memory::Measurement::Metric.new( :strings, 100, 99 ) Benchmark::Memory::Measurement.new( strings: strings, objects: objects, memory: memsize ) end end benchmark-memory-0.2.0/spec/benchmark/memory/job/io_output_spec.rb000066400000000000000000000016611414037400300253050ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Job::IOOutput do describe '#put_entry' do it 'outputs onto the passed IO' do entry = create_entry io = StringIO.new output = described_class.new(io) output.put_entry(entry) expect(io.string).not_to be_empty end end def create_entry Benchmark::Memory::Report::Entry.new( 'my super cool test', create_measurement ) end def create_measurement memsize = Benchmark::Memory::Measurement::Metric.new( :memsize, 3_078_619, 1_539_309 ) objects = Benchmark::Memory::Measurement::Metric.new( :objects, 2_936_123, 0 ) strings = Benchmark::Memory::Measurement::Metric.new( :strings, 100, 99 ) Benchmark::Memory::Measurement.new( strings: strings, objects: objects, memory: memsize ) end end benchmark-memory-0.2.0/spec/benchmark/memory/job/task_spec.rb000066400000000000000000000010531414037400300242130ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Job::Task do describe '.new' do it 'raises an ArgumentError when the action is not a callable' do expect do described_class.new('label', nil) end.to raise_error(ArgumentError) end end describe '#call' do it 'returns a measurement of the memory usage in the action' do action = -> {} entry = described_class.new('empty proc', action) expect(entry.call).to be_a Benchmark::Memory::Measurement end end end benchmark-memory-0.2.0/spec/benchmark/memory/job_spec.rb000066400000000000000000000056301414037400300232560ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' # rubocop:disable Lint/EmptyBlock RSpec.describe Benchmark::Memory::Job do describe '#report' do it 'raises an ArgumentError when no block is specified' do job = create_job expect { job.report('riddle me this') }.to raise_error(ArgumentError) end it 'adds a task to the list of tasks in the job' do job = create_job expect { job.report('riddle me that') {} }.to( change(job.tasks, :count).by(1) ) end end describe '#run' do it 'adds an entry to the report for each task' do job = create_job job.report('with you') {} job.report('my brown eyed girl') {} expect { job.run }.to change(job.full_report.entries, :count).by(2) end context 'holding' do it 'only executes one task for each call to run' do hold_buffer = StringIO.new job, output = create_job_and_output job.report('with you') {} job.report('my brown eyed girl') {} job.hold!(hold_buffer) expect { job.run }.to change(job.full_report.entries, :count).by(1) output_string = output.string expect(output_string).to match(/with you/) expect(output_string).not_to match(/brown eyed girl/) expect(output_string).to match(/Pausing/) # Reset for the second run hold_buffer.rewind job, output = create_job_and_output job.report('with you') {} job.report('my brown eyed girl') {} job.hold!(hold_buffer) expect { job.run }.to change(job.full_report.entries, :count).by(2) output_string = output.string expect(output_string).not_to match(/with you/) expect(output_string).to match(/brown eyed girl/) expect(output_string).not_to match(/Pausing/) end end end describe '#run_comparison' do it 'does not run if there are no entries' do job, output = create_job_and_output job.run job.run_comparison expect(output.string).not_to match(/Comparison/) end it 'runs when there are entries and the job is configured to compare' do job, output = create_job_and_output job.report('with you') {} job.report('my brown eyed girl') {} job.compare! job.run job.run_comparison expect(output.string).to match(/Comparison/) end end describe '#quiet?' do it 'prevents any output from being written' do job, output = create_job_and_output(quiet: true) job.report('with you') {} job.report('my brown eyed girl') {} job.compare! job.run job.run_comparison expect(output.string).to be_empty end end def create_job_and_output(quiet: false) output = StringIO.new job = Benchmark::Memory::Job.new(output: output, quiet: quiet) [job, output] end def create_job create_job_and_output.first end end # rubocop:enable Lint/EmptyBlock benchmark-memory-0.2.0/spec/benchmark/memory/measurement/000077500000000000000000000000001414037400300234665ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/measurement/metric_spec.rb000066400000000000000000000006661414037400300263200ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Measurement::Metric do describe '#to_s' do it 'is of consistent length' do examples = [0, 100, 1_000, 1_000_000, 1_000_000_000, 1_000_000_000_000] lengths = examples.map do |i| metric = described_class.new(:fake, i * 2, i) metric.to_s.length end expect(lengths.min).to eq(lengths.max) end end end benchmark-memory-0.2.0/spec/benchmark/memory/report/000077500000000000000000000000001414037400300224545ustar00rootroot00000000000000benchmark-memory-0.2.0/spec/benchmark/memory/report/comparator_spec.rb000066400000000000000000000023631414037400300261660ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Report::Comparator do describe '.from_spec' do it 'defaults to memory: :allocated when given nothing' do subject = described_class.from_spec({}) expect(subject).to eq described_class.new(metric: :memory, value: :allocated) end it 'accepts all different types of metrics' do memory = described_class.from_spec({ memory: :allocated }) expect(memory.metric).to eq :memory objects = described_class.from_spec({ objects: :allocated }) expect(objects.metric).to eq :objects strings = described_class.from_spec({ strings: :allocated }) expect(strings.metric).to eq :strings end it 'raises an error if given an invalid metric' do expect { described_class.from_spec({ unknown: :allocated }) } .to raise_error ArgumentError end it 'raises an error if given an invalid value' do expect { described_class.from_spec({ memory: :unknown }) } .to raise_error ArgumentError end it 'raises an error if given more than one metric' do expect { described_class.from_spec({ memory: :allocated, objects: :retained }) } .to raise_error ArgumentError end end end benchmark-memory-0.2.0/spec/benchmark/memory/report/comparison_spec.rb000066400000000000000000000024171414037400300261710ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Report::Comparison do describe '#entries' do it 'is sorted from smallest allocation to largest' do high_entry = create_high_entry low_entry = create_low_entry comparator = Benchmark::Memory::Report::Comparator.from_spec({}) comparison = described_class.new([high_entry, low_entry], comparator) expect(comparison.entries).to eq([low_entry, high_entry]) end end def create_high_entry Benchmark::Memory::Report::Entry.new( 'high', create_measurement(10_000, 5_000) ) end alias_method :create_entry, :create_high_entry def create_low_entry Benchmark::Memory::Report::Entry.new( 'low', create_measurement(2_500, 1_250) ) end def create_measurement(allocated, retained) memsize = Benchmark::Memory::Measurement::Metric.new( :memsize, allocated, retained ) objects = Benchmark::Memory::Measurement::Metric.new( :objects, 2_936_123, 0 ) strings = Benchmark::Memory::Measurement::Metric.new( :strings, 100, 99 ) Benchmark::Memory::Measurement.new( strings: strings, objects: objects, memory: memsize ) end end benchmark-memory-0.2.0/spec/benchmark/memory/report_spec.rb000066400000000000000000000014771414037400300240240ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory::Report do it 'is initialized with a blank list of entries' do report = described_class.new expect(report.entries).to be_empty end describe '#add_entry' do it 'adds an entry to the list of entries' do report = described_class.new task = Benchmark::Memory::Job::Task.new('do nothing', -> {}) measurement = create_measurement expect { report.add_entry(task, measurement) }.to( change(report.entries, :count).by(1) ) end end def create_measurement Benchmark::Memory::Measurement.new( memory: create_metric, objects: create_metric, strings: create_metric ) end def create_metric Benchmark::Memory::Measurement::Metric.new(:fake, 0, 0) end end benchmark-memory-0.2.0/spec/benchmark/memory_spec.rb000066400000000000000000000014261414037400300225030ustar00rootroot00000000000000# frozen_string_literal: true require 'spec_helper' RSpec.describe Benchmark::Memory do it 'has a version number' do expect(Benchmark::Memory::VERSION).not_to be nil end it 'exposes .memory on Benchmark' do expect(Benchmark).to respond_to(:memory) end describe '.memory' do around(:each) do |spec| old_stdout = $stdout $stdout = StringIO.new spec.run $stdout = old_stdout end it 'raises an error when not given a block' do expect { Benchmark.memory }.to raise_error( Benchmark::Memory::ConfigurationError ) end it 'returns a report' do # rubocop:disable Lint/EmptyBlock expect(Benchmark.memory {}).to be_a(Benchmark::Memory::Report) # rubocop:enable Lint/EmptyBlock end end end benchmark-memory-0.2.0/spec/spec_helper.rb000066400000000000000000000013641414037400300205210ustar00rootroot00000000000000# frozen_string_literal: true if ENV['COVERAGE'] || ENV['CI'] require 'simplecov' SimpleCov.start do add_filter '/spec/' end end require 'benchmark/memory' require 'pry' require 'rspec' RSpec.configure do |config| config.expect_with :rspec do |expectations| expectations.syntax = :expect expectations.include_chain_clauses_in_custom_matcher_descriptions = true end config.mock_with :rspec do |mocks| mocks.verify_partial_doubles = true end config.filter_run :focus config.run_all_when_everything_filtered = true config.disable_monkey_patching! config.default_formatter = 'doc' if config.files_to_run.one? config.profile_examples = 10 if ENV['PROFILE'] config.order = :random Kernel.srand config.seed end