benchmark-memory 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ea91842c5ad96cfe3dc3c101bf3d7179b6b1871f
4
+ data.tar.gz: 75947cf78b65b8286bceec66bf4ace28403cc5ac
5
+ SHA512:
6
+ metadata.gz: 23a9674497bab9bf07ecaaf07c0f51cdf880223a924de621806afa8f9f62fdca4cf0db59913b9f2372d77a8d849b73f297d106a0a80f9c9c0cd0c15878e5503b
7
+ data.tar.gz: 8eab918622dbb1e0dfbea807fa39c4404489510a90f3d37632dad8bbb6b0f8fad00fea34ce30a684f3f3b4ec74e4a6d00cadd30b742bf826a65631a5907f5c5d
@@ -0,0 +1,16 @@
1
+ # Change Log
2
+
3
+ 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.
4
+
5
+ [semver]: http://semver.org/spec/v2.0.0.html
6
+
7
+ ## [0.1.0] - 2016-05-18
8
+
9
+ ### Added
10
+
11
+ - Main `Benchmark.memory` method.
12
+ - Holding results between invocations for measuring implementations on different versions of Ruby or different versions of libraries.
13
+ - Quiet mode, with no command line output.
14
+
15
+ [0.1.0]: https://github.com/michaelherold/benchmark-memory/tree/v0.1.0
16
+ [unreleased]: https://github.com/michaelherold/benchmark-memory/compare/v0.1.0...HEAD
@@ -0,0 +1,50 @@
1
+ # Contributing
2
+
3
+ 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:
4
+
5
+ * Use alpha, beta, and pre-release versions.
6
+ * Report bugs.
7
+ * Suggest new features.
8
+ * Write or edit documentation.
9
+ * Write specifications.
10
+ * Write code (**no patch is too small**: fix typos, add comments, clean up inconsistent whitespace).
11
+ * Refactor code.
12
+ * Fix [issues][].
13
+ * Review patches.
14
+
15
+ [issues]: https://github.com/michaelherold/benchmark-memory/issues
16
+
17
+ ## Submitting an Issue
18
+
19
+ 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.
20
+
21
+ 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.
22
+
23
+ Ideally, a bug report should include a pull request with failing specs.
24
+
25
+ ## Submitting a Pull Request
26
+
27
+ 1. [Fork the repository](http://help.github.com/fork-a-repo/).
28
+ 2. [Create a topic branch](http://learn.github.com/p/branching.html).
29
+ 3. Add specs for your unimplemented feature or bug fix.
30
+ 4. Run `bundle exec rake spec`. If your specs pass, return to step 3.
31
+ 5. Implement your feature or bug fix.
32
+ 6. Run `bundle exec rake`. If your specs or any of the linters fail, return to step 5.
33
+ 7. Open `coverage/index.html`. If your changes are not completely covered by your tests, return to step 3.
34
+ 8. Add documentation for your feature or bug fix.
35
+ 9. Run `bundle exec inch`. If your changes are below a B in documentation, go back to step 8.
36
+ 10. Commit and push your changes.
37
+ 11. [Submit a pull request](http://help.github.com/send-pull-requests/).
38
+
39
+ ## Tools to help you succeed
40
+
41
+ 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.
42
+
43
+ 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`.
44
+
45
+ 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.
46
+
47
+ 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].
48
+
49
+ [guard]: http://guardgem.org
50
+ [rubygems]: https://rubygems.org
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 Michael Herold
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,183 @@
1
+ # benchmark-memory
2
+
3
+ [![Build Status](https://travis-ci.org/michaelherold/benchmark-memory.svg)][travis]
4
+ [![Code Climate](https://codeclimate.com/github/michaelherold/benchmark-memory/badges/gpa.svg)][codeclimate]
5
+ [![Inline docs](http://inch-ci.org/github/michaelherold/benchmark-memory.svg?branch=master)][inch]
6
+
7
+ [codeclimate]: https://codeclimate.com/github/michaelherold/benchmark-memory
8
+ [inch]: http://inch-ci.org/github/michaelherold/benchmark-memory
9
+ [travis]: https://travis-ci.org/michaelherold/benchmark-memory
10
+
11
+ 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.
12
+
13
+ [memory_profiler]: https://github.com/SamSaffron/memory_profiler
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ ```ruby
20
+ gem "benchmark-memory"
21
+ ```
22
+
23
+ And then execute:
24
+
25
+ $ bundle
26
+
27
+ Or install it yourself as:
28
+
29
+ $ gem install benchmark-memory
30
+
31
+ ## Usage
32
+
33
+ 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:
34
+
35
+ ```ruby
36
+ require "benchmark/memory"
37
+
38
+ # First method under test
39
+ def allocate_string
40
+ "this string was dynamically allocated"
41
+ end
42
+
43
+ # Second method under test
44
+ def give_frozen_string
45
+ "this string is frozen".freeze
46
+ end
47
+
48
+ Benchmark.memory do |x|
49
+ x.report("dynamic allocation") { allocate_string }
50
+ x.report("frozen string") { give_frozen_string }
51
+
52
+ x.compare!
53
+ end
54
+ ```
55
+
56
+ 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.
57
+
58
+ When you run this example, you see the difference between the two reports:
59
+
60
+ ```txt
61
+ Calculating -------------------------------------
62
+ dynamic allocation 40.000 memsize ( 0.000 retained)
63
+ 1.000 objects ( 0.000 retained)
64
+ 1.000 strings ( 0.000 retained)
65
+ frozen string 0.000 memsize ( 0.000 retained)
66
+ 0.000 objects ( 0.000 retained)
67
+ 0.000 strings ( 0.000 retained)
68
+
69
+ Comparison:
70
+ frozen string: 0 allocated
71
+ dynamic allocation: 40 allocated - Infx more
72
+ ```
73
+
74
+ 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 was created during the method definition.
75
+
76
+ [benchmark-ips]: https://github.com/evanphx/benchmark-ips
77
+
78
+ ## Options
79
+
80
+ There are several options available when running a memory benchmark.
81
+
82
+ ### Suppress all output (Quiet Mode)
83
+
84
+ ```ruby
85
+ Benchmark.memory(:quiet => true)
86
+ ```
87
+
88
+ 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.
89
+
90
+ ### Enable comparison
91
+
92
+ ```ruby
93
+ Benchmark.memory do |x|
94
+ x.compare!
95
+ end
96
+ ```
97
+
98
+ Calling `#compare!` on the job within the setup block of `Benchmark.memory` enables the output of the comparison section of the benchmark. Without it, this section is suppressed and you only get the raw numbers output during calculation.
99
+
100
+ ### Hold results between invocations
101
+
102
+ ```ruby
103
+ Benchmark.memory do |x|
104
+ x.hold!("benchmark_results.json")
105
+ end
106
+ ```
107
+
108
+ 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.
109
+
110
+ 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 be read in between invocations of your benchmark.
111
+
112
+ 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.
113
+
114
+ To test the two implementations, you could then write this benchmark:
115
+
116
+ ```ruby
117
+ require "benchmark/memory"
118
+
119
+ require "stats" # require your library file here
120
+ data = [] # set up the data that it will call here
121
+
122
+ Benchmark.memory do |x|
123
+ x.report("original") { Stats.monthly_recurring_revenue(data) }
124
+ x.report("optimized") { Stats.monthly_recurring_revenue(data) }
125
+
126
+ x.compare!
127
+ x.hold("bm_recurring_revenue.json")
128
+ end
129
+ ```
130
+
131
+ 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.
132
+
133
+ You could then run the following (assuming you saved your benchmark as `benchmark_mrr.rb`:
134
+
135
+ ```sh
136
+ $ git checkout master
137
+ $ ruby benchmark_mrr.rb
138
+ $ git checkout optimize-memory
139
+ $ ruby benchmark_mrr.rb
140
+ ```
141
+
142
+ The first invocation of `ruby benchmark_mrr.rb` runs the benchmark in the "original" entry using your code in your `master` Git branch. The second invocation runs the benchmark in the "optimized" entry using the code in your `optimize-memory` Git branch. These two results are collated and then compared to show you the difference between the two.
143
+
144
+ 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.
145
+
146
+ ## Supported Ruby Versions
147
+
148
+ This library aims to support and is [tested against][travis] the following Ruby versions:
149
+
150
+ * Ruby 2.1
151
+ * Ruby 2.2
152
+ * Ruby 2.3
153
+
154
+ If something doesn't work on one of these versions, it's a bug.
155
+
156
+ This library may inadvertently work (or seem to work) on other Ruby versions, however, support will only be provided for the versions listed above.
157
+
158
+ 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, support for that Ruby version may be dropped.
159
+
160
+ ## Versioning
161
+
162
+ This library aims to adhere to [Semantic Versioning 2.0.0][semver]. Violations of this scheme should be reported as bugs. Specifically, if a minor or patch version is released that breaks backward compatibility, that version should be immediately yanked and/or a new version should be immediately released that restores compatibility. Breaking changes to the public API will only be introduced 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:
163
+
164
+ spec.add_dependency "benchmark-memory", "~> 0.1"
165
+
166
+ [pessimistic]: http://guides.rubygems.org/patterns/#pessimistic-version-constraint
167
+ [semver]: http://semver.org/spec/v2.0.0.html
168
+
169
+ ## Acknowledgments
170
+
171
+ This library wouldn't be possible without two projects and the people behind them:
172
+
173
+ * Sam Saffron's [memory_profiler] does all of the measurement of the memory allocation and retention in the benchmarks.
174
+ * 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.
175
+
176
+ [benchmark-ips]: https://github.com/evanphx/benchmark-ips
177
+ [memory_profiler]: https://github.com/SamSaffron/memory_profiler
178
+
179
+ ## License
180
+
181
+ The gem is available as open source under the terms of the [MIT License][license].
182
+
183
+ [license]: http://opensource.org/licenses/MIT.
@@ -0,0 +1,29 @@
1
+ require "bundler"
2
+ Bundler.setup
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ require "inch/rake"
6
+ Inch::Rake::Suggest.new(:inch)
7
+
8
+ require "rspec/core/rake_task"
9
+ RSpec::Core::RakeTask.new(:spec)
10
+
11
+ require "rubocop/rake_task"
12
+ RuboCop::RakeTask.new(:rubocop)
13
+
14
+ require "yard/rake/yardoc_task"
15
+ YARD::Rake::YardocTask.new(:yard)
16
+
17
+ task :mutant do
18
+ command = [
19
+ "bundle exec mutant",
20
+ "--include lib",
21
+ "--require interactor-contracts",
22
+ "--use rspec",
23
+ "Interactor::Contracts*",
24
+ ].join(" ")
25
+
26
+ system command
27
+ end
28
+
29
+ task :default => [:spec, :rubocop, :yard, :inch]
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+
3
+ require File.expand_path("../lib/benchmark/memory/version", __FILE__)
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "benchmark-memory"
7
+ spec.version = Benchmark::Memory::VERSION
8
+ spec.authors = ["Michael Herold"]
9
+ spec.email = ["michael.j.herold@gmail.com"]
10
+
11
+ spec.summary = "Benchmark-style memory profiling for Ruby 2.1+"
12
+ spec.description = spec.summary
13
+ spec.homepage = "https://github.com/michaelherold/benchmark-memory"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = %w(CHANGELOG.md CONTRIBUTING.md LICENSE.md README.md Rakefile)
17
+ spec.files += %w(benchmark-memory.gemspec)
18
+ spec.files += Dir["lib/**/*.rb"]
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "memory_profiler", "~> 0.9"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.12"
24
+ end
@@ -0,0 +1,2 @@
1
+ # rubocop:disable Style/FileName
2
+ require "benchmark/memory"
@@ -0,0 +1,34 @@
1
+ require "benchmark/memory/errors"
2
+ require "benchmark/memory/job"
3
+ require "benchmark/memory/version"
4
+
5
+ # Performance benchmarking library
6
+ module Benchmark
7
+ # Benchmark memory usage in code to benchmark different approaches.
8
+ # @see https://github.com/michaelherold/benchmark-memory
9
+ module Memory
10
+ # Measure memory usage in report blocks.
11
+ #
12
+ # @param quiet [Boolean] A flag to toggle benchmark output.
13
+ #
14
+ # @return [Report]
15
+ def memory(quiet: false)
16
+ unless block_given?
17
+ fail(
18
+ ConfigurationError,
19
+ "You did not give a test block to your call to `Benchmark.memory`"
20
+ )
21
+ end
22
+
23
+ job = Job.new(:quiet => quiet)
24
+
25
+ yield job
26
+
27
+ job.run
28
+ job.run_comparison
29
+ job.full_report
30
+ end
31
+ end
32
+
33
+ extend Benchmark::Memory
34
+ end
@@ -0,0 +1,7 @@
1
+ module Benchmark
2
+ module Memory
3
+ Error = Class.new(StandardError)
4
+
5
+ ConfigurationError = Class.new(Error)
6
+ end
7
+ end
@@ -0,0 +1,110 @@
1
+ require "forwardable"
2
+ require "benchmark/memory/held_results/entry_serializer"
3
+
4
+ module Benchmark
5
+ module Memory
6
+ # Collate results that should be held until the next run.
7
+ class HeldResults
8
+ extend Forwardable
9
+
10
+ # Instantiate a new set of held results on a path.
11
+ #
12
+ # @param path [String, IO] The path to write held results to.
13
+ def initialize(path = nil)
14
+ @path = path
15
+ @results = {}
16
+ end
17
+
18
+ # @return [String, IO] The path to write held results to.
19
+ attr_accessor :path
20
+
21
+ # @return [Hash{String => Measurement}] Held results from previous runs.
22
+ attr_reader :results
23
+
24
+ # Allow Hash-like access to the results without asking for them.
25
+ def_delegator :@results, :[]
26
+
27
+ # Add a result to the held results.
28
+ #
29
+ # @param entry [Report::Entry] The entry to hold.
30
+ #
31
+ # @return [void]
32
+ def add_result(entry)
33
+ with_hold_file("a") do |file|
34
+ file.write EntrySerializer.new(entry)
35
+ file.write "\n"
36
+ end
37
+ end
38
+
39
+ # Check whether any results have been stored.
40
+ #
41
+ # @return [Boolean]
42
+ def any?
43
+ if @path.is_a?(String)
44
+ File.exist?(@path)
45
+ else
46
+ @path.size > 0 # rubocop:disable Style/ZeroLengthPredicate
47
+ end
48
+ end
49
+
50
+ # Clean up the results after all results have been collated.
51
+ #
52
+ # @return [void]
53
+ def cleanup
54
+ if @path.is_a?(String) && File.exist?(@path)
55
+ File.delete(@path)
56
+ end
57
+ end
58
+
59
+ # Check whether to hold results.
60
+ #
61
+ # @return [Boolean]
62
+ def holding?
63
+ !!@path
64
+ end
65
+
66
+ # Check whether an entry has been added to the results.
67
+ #
68
+ # @param entry [#label] The entry to check.
69
+ #
70
+ # @return [Boolean]
71
+ def include?(entry)
72
+ holding? && any? && results.key?(entry.label)
73
+ end
74
+
75
+ # Load results from the serialized output.
76
+ #
77
+ # @return [void]
78
+ def load
79
+ return unless holding? && any?
80
+
81
+ results = with_hold_file do |file|
82
+ file.map { |line| EntrySerializer.load(line) }
83
+ end
84
+ @results = Hash[results.map do |result|
85
+ [result.label, result.measurement]
86
+ end]
87
+ end
88
+
89
+ private
90
+
91
+ # Execute a block on the hold file.
92
+ #
93
+ # @param access_mode [String] The mode to use when opening the file.
94
+ # @param _block [Proc] The block to execute on each line of the file.
95
+ #
96
+ # @return [void]
97
+ def with_hold_file(access_mode = "r", &_block)
98
+ return unless @path
99
+
100
+ if @path.is_a?(String)
101
+ File.open(@path, access_mode) do |f|
102
+ yield f
103
+ end
104
+ else
105
+ yield @path
106
+ end
107
+ end
108
+ end
109
+ end
110
+ end