b 1.0.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.
- data/.gitignore +18 -0
- data/Gemfile +4 -0
- data/Guardfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +82 -0
- data/Rakefile +13 -0
- data/b.gemspec +29 -0
- data/lib/b.rb +128 -0
- data/lib/b/demo.rb +74 -0
- data/lib/b/output_plugins.rb +127 -0
- data/lib/b/version.rb +3 -0
- data/spec/b_spec.rb +244 -0
- data/spec/spec_helper.rb +6 -0
- metadata +188 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/Guardfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 moe@busyloop.net
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
# B::enchmark [](https://travis-ci.org/busyloop/b) [](https://gemnasium.com/busyloop/b)
|
2
|
+
|
3
|
+
A small, convenient benchmark-library.
|
4
|
+
|
5
|
+
## Features
|
6
|
+
|
7
|
+
* Sensible defaults and default-output
|
8
|
+
|
9
|
+
* Displays relative performance; "B was 1.4x slower than A"
|
10
|
+
|
11
|
+
* Returns benchmark-results as Array of Hashes (for easy integration with your unit-tests or CI)
|
12
|
+
|
13
|
+
* Output can be customized with a simple Plugin-API (ships with plugins for TSV and HTML)
|
14
|
+
|
15
|
+
* High precision (via [hitimes](https://github.com/copiousfreetime/hitimes))
|
16
|
+
|
17
|
+
|
18
|
+
## Installation
|
19
|
+
|
20
|
+
`gem install b`
|
21
|
+
|
22
|
+
## Example
|
23
|
+
|
24
|
+
```ruby
|
25
|
+
#!/usr/bin/env ruby
|
26
|
+
|
27
|
+
require 'b'
|
28
|
+
|
29
|
+
B.enchmark('Sleep "performance"', :rounds => 10, :compare => :mean) do
|
30
|
+
job '300ms' do
|
31
|
+
sleep 0.3
|
32
|
+
end
|
33
|
+
|
34
|
+
job '100ms' do
|
35
|
+
sleep 0.1
|
36
|
+
end
|
37
|
+
|
38
|
+
job '200ms' do
|
39
|
+
sleep 0.2
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
### Output:
|
45
|
+
|
46
|
+
```
|
47
|
+
-- Sleep "performance" ---------------------------------------------------------------
|
48
|
+
rounds r/s mean max min ± stddev x mean
|
49
|
+
100ms 10 9.99 100.13 100.17 100.11 0.02 1.00
|
50
|
+
200ms 10 5.00 200.14 200.17 200.11 0.02 2.00
|
51
|
+
300ms 10 3.33 300.13 300.18 300.11 0.02 3.00
|
52
|
+
```
|
53
|
+
|
54
|
+
## Getting started
|
55
|
+
|
56
|
+
For advanced usage beyond the above example please look at the included demos:
|
57
|
+
|
58
|
+
1. Run `ruby -r b/demo -e0`
|
59
|
+
2. [demo.rb](https://github.com/busyloop/b/blob/master/lib/b/demo.rb)
|
60
|
+
|
61
|
+
## License (MIT)
|
62
|
+
|
63
|
+
Copyright (c) 2013 moe@busyloop.net
|
64
|
+
|
65
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
66
|
+
a copy of this software and associated documentation files (the
|
67
|
+
"Software"), to deal in the Software without restriction, including
|
68
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
69
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
70
|
+
permit persons to whom the Software is furnished to do so, subject to
|
71
|
+
the following conditions:
|
72
|
+
|
73
|
+
The above copyright notice and this permission notice shall be
|
74
|
+
included in all copies or substantial portions of the Software.
|
75
|
+
|
76
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
77
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
78
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
79
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
80
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
81
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
82
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
require 'rspec/core/rake_task'
|
4
|
+
|
5
|
+
task :default => :test
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new("test:spec") do |t|
|
8
|
+
t.pattern = 'spec/**/*_spec.rb'
|
9
|
+
t.rspec_opts = '--fail-fast -b -c -f documentation --tag ~benchmark'
|
10
|
+
end
|
11
|
+
|
12
|
+
desc 'Run full test suite'
|
13
|
+
task :test => [ 'test:spec' ]
|
data/b.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'b/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = "b"
|
8
|
+
gem.version = B::VERSION
|
9
|
+
gem.authors = ["Moe"]
|
10
|
+
gem.email = ["moe@busyloop.net"]
|
11
|
+
gem.description = %q{A small, convenient benchmark-library.}
|
12
|
+
gem.summary = %q{A small, convenient benchmark-library.}
|
13
|
+
gem.homepage = "https://github.com/busyloop/b"
|
14
|
+
gem.has_rdoc = false
|
15
|
+
|
16
|
+
gem.files = `git ls-files`.split($/)
|
17
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
|
+
gem.require_paths = ["lib"]
|
20
|
+
|
21
|
+
gem.add_development_dependency 'simplecov'
|
22
|
+
gem.add_development_dependency 'rake'
|
23
|
+
gem.add_development_dependency 'rspec'
|
24
|
+
gem.add_development_dependency 'guard'
|
25
|
+
gem.add_development_dependency 'guard-rspec'
|
26
|
+
gem.add_development_dependency 'stdiotrap'
|
27
|
+
gem.add_dependency 'hitimes'
|
28
|
+
gem.add_dependency 'blockenspiel'
|
29
|
+
end
|
data/lib/b.rb
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
require "b/version"
|
2
|
+
require "b/output_plugins"
|
3
|
+
require "hitimes"
|
4
|
+
require "blockenspiel"
|
5
|
+
|
6
|
+
module B
|
7
|
+
class SanityViolation < RuntimeError; end
|
8
|
+
|
9
|
+
DEFAULT_ROUNDS = 50
|
10
|
+
|
11
|
+
def self.enchmark(id='Untitled Benchmark', opts={}, &block)
|
12
|
+
opts = {:output => ConsoleWriter.new}.merge(opts)
|
13
|
+
opts[:parent] = opts[:output]
|
14
|
+
Blockenspiel.invoke(block, Enchmark.new(id, opts)).run!
|
15
|
+
end
|
16
|
+
|
17
|
+
# B::Ase
|
18
|
+
class Ase
|
19
|
+
include Blockenspiel::DSL
|
20
|
+
attr_reader :opts
|
21
|
+
|
22
|
+
dsl_methods false
|
23
|
+
|
24
|
+
def run!
|
25
|
+
@children.map {|c| c.run! unless c.nil?} unless @children.nil?
|
26
|
+
post_run if self.respond_to? :post_run
|
27
|
+
@children.map(&:to_h)
|
28
|
+
end
|
29
|
+
|
30
|
+
def register(job)
|
31
|
+
@parent.register(job) if @parent.respond_to? :register
|
32
|
+
end
|
33
|
+
|
34
|
+
def start(job)
|
35
|
+
@parent.start(job) if @parent.respond_to? :start
|
36
|
+
end
|
37
|
+
|
38
|
+
def finish(job)
|
39
|
+
@parent.finish(job) if @parent.respond_to? :finish
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# B::Enchmark
|
44
|
+
class Enchmark < B::Ase
|
45
|
+
attr_reader :id
|
46
|
+
|
47
|
+
def job(id, opts={}, &block)
|
48
|
+
if opts[:rounds] and @compare
|
49
|
+
raise SanityViolation, "Can not override :rounds on job '#{id}' when comparing on :#{@compare}!"
|
50
|
+
end
|
51
|
+
if opts[:compare]
|
52
|
+
raise SanityViolation, "Can not override :compare in a job ('#{id}')"
|
53
|
+
end
|
54
|
+
opts = @opts.merge(opts)
|
55
|
+
(@children ||= []) << Job.new(self, id, opts, block)
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
dsl_methods false
|
60
|
+
def initialize(id, opts={})
|
61
|
+
@opts = {:rounds => DEFAULT_ROUNDS, :compare => nil}.merge(opts)
|
62
|
+
@parent = opts[:parent]
|
63
|
+
@id, @rounds, @compare = id, opts[:rounds], opts[:compare]
|
64
|
+
@buffer = []
|
65
|
+
end
|
66
|
+
|
67
|
+
def post_run
|
68
|
+
if @compare
|
69
|
+
@buffer.sort! do |a,b|
|
70
|
+
a.send(@compare) <=> b.send(@compare)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
@buffer.each_with_index do |job, i|
|
74
|
+
if 0 == i
|
75
|
+
job.x = 1.0
|
76
|
+
else
|
77
|
+
job.x = job.send(@compare) / @buffer[0].send(@compare)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
until @buffer.empty? do
|
81
|
+
@parent.start @buffer[0]
|
82
|
+
@parent.finish @buffer.shift
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def start(job)
|
87
|
+
@compare.nil? ? super : @buffer << job
|
88
|
+
end
|
89
|
+
|
90
|
+
def finish(job)
|
91
|
+
super if @compare.nil?
|
92
|
+
end
|
93
|
+
|
94
|
+
# B::Enchmark::Job
|
95
|
+
class Job < B::Ase
|
96
|
+
ATTRIBUTES = [:group, :id, :rounds, :rate, :mean, :max, :min, :stddev, :x, :compare]
|
97
|
+
attr_reader *ATTRIBUTES
|
98
|
+
attr_writer :x
|
99
|
+
|
100
|
+
dsl_methods false
|
101
|
+
def initialize(parent, id, opts, block)
|
102
|
+
@opts = parent.opts.merge(opts)
|
103
|
+
@parent, @block, @group, @id, @rounds, @compare = parent, block, parent.id, id, @opts[:rounds], @opts[:compare]
|
104
|
+
@parent.register(self)
|
105
|
+
end
|
106
|
+
|
107
|
+
def run!
|
108
|
+
tm = Hitimes::TimedMetric.new(nil)
|
109
|
+
@parent.start self
|
110
|
+
@opts[:rounds].times do
|
111
|
+
tm.start
|
112
|
+
@block.call
|
113
|
+
tm.stop
|
114
|
+
end
|
115
|
+
@rate, @mean, @max, @min, @stddev = tm.rate, tm.mean, tm.max, tm.min, tm.stddev
|
116
|
+
finish self
|
117
|
+
end
|
118
|
+
|
119
|
+
def to_h
|
120
|
+
ATTRIBUTES.reduce({}) { |m,e|
|
121
|
+
m[e] = instance_variable_get('@'+e.to_s)
|
122
|
+
m
|
123
|
+
}
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
data/lib/b/demo.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'b'
|
4
|
+
|
5
|
+
puts "Source-code is here: #{__FILE__}"
|
6
|
+
puts
|
7
|
+
|
8
|
+
# Basic sleep benchmark
|
9
|
+
results = B.enchmark("Nap time; output in milliseconds", :rounds => 3) do
|
10
|
+
job('sleep 0.1', :rounds => 10) { sleep 0.1 }
|
11
|
+
job('sleep 0.5') { sleep 0.5 }
|
12
|
+
job('sleep 1.0') { sleep 1.0 }
|
13
|
+
end
|
14
|
+
|
15
|
+
# Results are also returned as an Array of Hashes
|
16
|
+
#puts results
|
17
|
+
|
18
|
+
puts
|
19
|
+
|
20
|
+
# Same as above but display results in seconds instead of ms.
|
21
|
+
# HINT: You may also set :writer => nil if you want no output at all.
|
22
|
+
cw = B::ConsoleWriter.new({:multiply=>1, :round=>6})
|
23
|
+
result B.enchmark("Nap time; output in seconds", :output => cw, :rounds => 3) do
|
24
|
+
job('sleep 0.1', :rounds => 10) { sleep 0.1 }
|
25
|
+
job('sleep 0.5') { sleep 0.5 }
|
26
|
+
job('sleep 1.0') { sleep 1.0 }
|
27
|
+
end
|
28
|
+
|
29
|
+
#puts results
|
30
|
+
puts
|
31
|
+
|
32
|
+
# Fibonacci snippets taken from http://stackoverflow.com/questions/6418524/fibonacci-one-liner
|
33
|
+
B.enchmark("Compare fibonacci implementations", :rounds => 500, :compare => :mean) do
|
34
|
+
job 'fibonacci lambda A' do
|
35
|
+
f = lambda { |x| x < 2 ? x : f.call(x-1) + f.call(x-2) }
|
36
|
+
(0..16).each { |i| f.call(i) }
|
37
|
+
end
|
38
|
+
|
39
|
+
job 'fibonacci lambda B' do
|
40
|
+
f = lambda { |n| (0..n).inject([1,0]) { |(a,b), _| [b, a+b] }[0] }
|
41
|
+
(0..16).each { |i| f.call(i) }
|
42
|
+
end
|
43
|
+
|
44
|
+
job 'fibonacci hash' do
|
45
|
+
f = Hash.new{ |h,k| h[k] = k < 2 ? k : h[k-1] + h[k-2] }
|
46
|
+
(0..16).each { |i| f[i] }
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
puts
|
51
|
+
|
52
|
+
B.enchmark('Compare string-concat methods (50k per round)', :rounds => 10, :compare => :mean) do
|
53
|
+
job 'x = a << b << c' do
|
54
|
+
50_000.times do
|
55
|
+
a, b, c = 'a', 'b', 'c'
|
56
|
+
x = a << b << c
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
job 'x = "#{a}#{b}#{c}"' do
|
61
|
+
50_000.times do
|
62
|
+
a, b, c = 'a', 'b', 'c'
|
63
|
+
x = "#{a}#{b}#{c}"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
job 'x = a+b+c' do
|
68
|
+
50_000.times do
|
69
|
+
a, b, c = 'a', 'b', 'c'
|
70
|
+
x = a+b+c
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -0,0 +1,127 @@
|
|
1
|
+
module B
|
2
|
+
# print results in TSV format
|
3
|
+
class TsvWriter
|
4
|
+
COLUMNS = [:group, :id, :rounds, :rate, :mean, :max, :min, :stddev, :x]
|
5
|
+
def initialize(out=$stdout)
|
6
|
+
@out = out
|
7
|
+
printf COLUMNS.join("\t") + "\n"
|
8
|
+
end
|
9
|
+
|
10
|
+
def finish(job)
|
11
|
+
printf COLUMNS.map { |c| job.send(c) }.join("\t") + "\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
def printf(*a)
|
16
|
+
@out.printf *a
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# print results as a HTML table
|
21
|
+
class HtmlWriter
|
22
|
+
COLUMNS = [:group, :id, :rounds, :rate, :mean, :max, :min, :stddev, :x]
|
23
|
+
|
24
|
+
def register(job)
|
25
|
+
@todo = (@todo||0) +1
|
26
|
+
end
|
27
|
+
|
28
|
+
def initialize(out=$stdout)
|
29
|
+
@out = out
|
30
|
+
printf "<table>\n<tr>"
|
31
|
+
COLUMNS.map { |c| printf "<th>#{c}</th>" }
|
32
|
+
printf "</tr>\n"
|
33
|
+
end
|
34
|
+
|
35
|
+
def finish(job)
|
36
|
+
@done = (@done||0) +1
|
37
|
+
printf "<tr>"
|
38
|
+
COLUMNS.map { |c| printf "<td>#{job.send(c)}</td>" }
|
39
|
+
printf "</tr>\n"
|
40
|
+
|
41
|
+
printf "</table>\n" if @done == @todo
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def printf(*a)
|
46
|
+
@out.printf *a
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# print results in human friendly tabular format
|
51
|
+
#
|
52
|
+
# usage hints:
|
53
|
+
# * set :multiply=>1 if you want output in seconds instead of milliseconds
|
54
|
+
# * increase :column_width if you see indendation issues with wide values
|
55
|
+
# * increase :max_label_width if you want to see more of your labels
|
56
|
+
#
|
57
|
+
class ConsoleWriter
|
58
|
+
C_ID, C_LABEL, C_WIDTH, C_ROUND, C_MUL = *(0..4)
|
59
|
+
|
60
|
+
def initialize(opts={})
|
61
|
+
@opts = opts = { :out => $stderr, :multiply => 1000, :round => 2,
|
62
|
+
:column_width => 11, :max_label_width => 20 }.merge(opts)
|
63
|
+
|
64
|
+
@out = opts[:out]
|
65
|
+
@max_label_width = opts[:max_label_width]
|
66
|
+
|
67
|
+
@columns = [
|
68
|
+
# C_ID C_LABEL C_WIDTH C_ROUND C_MUL
|
69
|
+
[:id, '', -1, nil, nil],
|
70
|
+
[:rounds, 'rounds', -1, 0, 0],
|
71
|
+
[:rate, 'r/s', opts[:column_width], opts[:round], 1],
|
72
|
+
[:mean, 'mean', opts[:column_width], opts[:round], opts[:multiply]],
|
73
|
+
[:max, 'max', opts[:column_width], opts[:round], opts[:multiply]],
|
74
|
+
[:min, 'min', opts[:column_width], opts[:round], opts[:multiply]],
|
75
|
+
[:stddev, "\u00b1 stddev", opts[:column_width], opts[:round], opts[:multiply]]
|
76
|
+
]
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
def register(job)
|
81
|
+
# adjust width of first column (label) to fit longest label
|
82
|
+
@columns[0][C_WIDTH] = [@columns[0][C_WIDTH], [job.id.length+2,@max_label_width].min].max
|
83
|
+
# adjust width of second column (rounds) to fit widest value
|
84
|
+
@columns[1][C_WIDTH] = [@columns[1][C_WIDTH], @columns[1][C_LABEL].length, job.rounds.to_s.length].max
|
85
|
+
end
|
86
|
+
|
87
|
+
def start(job)
|
88
|
+
if @header_printed.nil?
|
89
|
+
@header_printed = true
|
90
|
+
|
91
|
+
# add :x column if this is a comparison
|
92
|
+
if job.compare
|
93
|
+
@columns << [:x, "x #{job.compare}", @opts[:column_width], @opts[:round], 1]
|
94
|
+
end
|
95
|
+
|
96
|
+
# print header
|
97
|
+
header = '-' * (@columns.transpose[C_WIDTH].reduce(&:+) + @columns.length - 1)
|
98
|
+
header[2..3+job.group.length] = " #{job.group} "
|
99
|
+
printf header + "\n"
|
100
|
+
@columns.each_with_index do |col, i|
|
101
|
+
printf col[C_LABEL].rjust(col[C_WIDTH]) + ' '
|
102
|
+
end
|
103
|
+
printf "\n"
|
104
|
+
end
|
105
|
+
# print job.label
|
106
|
+
printf "#{job.id[0..@columns[0][C_WIDTH]-1].ljust(@columns[0][C_WIDTH])} "
|
107
|
+
# print rounds
|
108
|
+
printf "%#{@columns[1][C_WIDTH]}d ", job.send(:rounds)
|
109
|
+
end
|
110
|
+
|
111
|
+
def finish(job)
|
112
|
+
@columns.each_with_index do |col, i|
|
113
|
+
next if 2 > i # first two columns were already printed in start()
|
114
|
+
value = job.send(col[C_ID]) * col[C_MUL]
|
115
|
+
|
116
|
+
printf "%#{col[C_WIDTH]}.#{col[C_ROUND]}f ", value
|
117
|
+
end
|
118
|
+
printf "\n"
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
def printf(*a)
|
123
|
+
@opts[:out].printf *a
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
data/lib/b/version.rb
ADDED
data/spec/b_spec.rb
ADDED
@@ -0,0 +1,244 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe B do
|
4
|
+
describe :enchmark do
|
5
|
+
before (:each) do
|
6
|
+
StdioTrap.trap!
|
7
|
+
end
|
8
|
+
|
9
|
+
after (:each) do
|
10
|
+
StdioTrap.release!
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'with opts={}' do
|
14
|
+
let(:test_opts) { {} }
|
15
|
+
describe :job do
|
16
|
+
it 'raises SanityViolation when trying to override :compare' do
|
17
|
+
lambda {
|
18
|
+
B.enchmark do
|
19
|
+
job('a', {:compare => :min}) {}
|
20
|
+
end
|
21
|
+
}.should raise_error B::SanityViolation
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'is called B::DEFAULT_ROUNDS times by default' do
|
25
|
+
a = mock(:a)
|
26
|
+
a.should_receive(:call).exactly(B::DEFAULT_ROUNDS).times
|
27
|
+
B.enchmark do
|
28
|
+
job('a') { a.call }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'can override :rounds' do
|
33
|
+
a, b, c, d = mock(:a), mock(:b), mock(:c), mock(:d)
|
34
|
+
a.should_receive(:call).exactly(B::DEFAULT_ROUNDS).times
|
35
|
+
b.should_receive(:call).exactly(5).times
|
36
|
+
c.should_receive(:call).exactly(B::DEFAULT_ROUNDS).times
|
37
|
+
d.should_receive(:call).exactly(10).times
|
38
|
+
B.enchmark do
|
39
|
+
job('a') { a.call }
|
40
|
+
job('b', :rounds => 5) { b.call }
|
41
|
+
job('c') { c.call }
|
42
|
+
job('d', :rounds => 10) { d.call }
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
describe "return value" do
|
48
|
+
it "is an Array of well-formed Hashes" do
|
49
|
+
results = B.enchmark do
|
50
|
+
job('a') {}
|
51
|
+
job('b') {}
|
52
|
+
end
|
53
|
+
results.length.should == 2
|
54
|
+
results.each do |h|
|
55
|
+
h.should be_a Hash
|
56
|
+
B::Enchmark::Job::ATTRIBUTES.each do |a|
|
57
|
+
h.should have_key a
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
it "retval[*][:x] is nil" do
|
63
|
+
results = B.enchmark do
|
64
|
+
job('a') {}
|
65
|
+
job('b') {}
|
66
|
+
end
|
67
|
+
results.each do |h|
|
68
|
+
B::Enchmark::Job::ATTRIBUTES.each do |a|
|
69
|
+
h[:x].should == nil
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
it "looks about correct (timing)" do
|
75
|
+
results = B.enchmark('', test_opts) do
|
76
|
+
job('a', :rounds => 2 ) { sleep 0.5 }
|
77
|
+
job('b', :rounds => 1 ) { sleep 1 }
|
78
|
+
end
|
79
|
+
results.length.should == 2
|
80
|
+
|
81
|
+
results[0][:rounds].should == 2
|
82
|
+
results[0][:mean].should be >= 0.5
|
83
|
+
results[0][:mean].should be <= 1.0
|
84
|
+
|
85
|
+
results[1][:rounds].should == 1
|
86
|
+
results[1][:mean].should be >= 1.0
|
87
|
+
results[1][:mean].should be <= 1.5
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "stdout" do
|
92
|
+
it "is empty" do
|
93
|
+
B.enchmark do
|
94
|
+
job('a') {}
|
95
|
+
end
|
96
|
+
StdioTrap.stdout.should == ''
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "stderr" do
|
101
|
+
it "looks like output from the default ConsoleWriter" do
|
102
|
+
B.enchmark do
|
103
|
+
job('a') {}
|
104
|
+
end
|
105
|
+
StdioTrap.stderr.should match /^-- Untitled Benchmark/
|
106
|
+
StdioTrap.stderr.should match /rounds/
|
107
|
+
StdioTrap.stderr.should match /stddev/
|
108
|
+
StdioTrap.stderr.lines.count.should == 3
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
describe 'with opts={:compare => :mean, :rounds => 1}' do
|
114
|
+
let(:test_opts) { {:compare => :mean, :rounds => 1} }
|
115
|
+
|
116
|
+
describe :job do
|
117
|
+
it 'raises SanityViolation when trying to override :compare' do
|
118
|
+
lambda {
|
119
|
+
B.enchmark('', test_opts) do
|
120
|
+
job('a', {:compare => :min}) {}
|
121
|
+
end
|
122
|
+
}.should raise_error B::SanityViolation
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'raises SanityViolation when trying to override :rounds' do
|
126
|
+
lambda {
|
127
|
+
B.enchmark('', test_opts) do
|
128
|
+
job('a', :rounds => 2) {}
|
129
|
+
end
|
130
|
+
}.should raise_error B::SanityViolation
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
describe "return value" do
|
135
|
+
it "is an Array of well-formed Hashes" do
|
136
|
+
results = B.enchmark('', test_opts) do
|
137
|
+
job('a') {}
|
138
|
+
job('b') {}
|
139
|
+
end
|
140
|
+
|
141
|
+
results.length.should == 2
|
142
|
+
results.each do |h|
|
143
|
+
h.should be_a Hash
|
144
|
+
B::Enchmark::Job::ATTRIBUTES.each do |a|
|
145
|
+
h.should have_key a
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
it "retval[*][:x] is not nil" do
|
151
|
+
results = B.enchmark('', test_opts) do
|
152
|
+
job('a') {}
|
153
|
+
job('b') {}
|
154
|
+
end
|
155
|
+
results.each do |h|
|
156
|
+
B::Enchmark::Job::ATTRIBUTES.each do |a|
|
157
|
+
h.should_not == nil
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it "is sorted on :mean (and :x)" do
|
163
|
+
results = B.enchmark('', test_opts) do
|
164
|
+
job('b') { sleep 0.3 }
|
165
|
+
job('a') { sleep 0.1 }
|
166
|
+
job('d') { sleep 0.9 }
|
167
|
+
job('c') { sleep 0.6 }
|
168
|
+
end
|
169
|
+
as_returned = results.collect {|e| [e[:mean], e[:id]]}
|
170
|
+
sorted = as_returned.sort_by {|e| e[0]}
|
171
|
+
sorted.collect {|e| e[1]}.should == ['a', 'b', 'c', 'd']
|
172
|
+
|
173
|
+
as_returned = results.collect {|e| [e[:x], e[:id]]}
|
174
|
+
sorted = as_returned.sort_by {|e| e[0]}
|
175
|
+
sorted.collect {|e| e[1]}.should == ['a', 'b', 'c', 'd']
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
describe 'with opts={:output => nil}' do
|
182
|
+
let(:test_opts) { { :output=> nil } }
|
183
|
+
|
184
|
+
it 'doesn\'t write to stdout nor stderr' do
|
185
|
+
B.enchmark('', test_opts) do
|
186
|
+
job('a') {}
|
187
|
+
end
|
188
|
+
StdioTrap.stdout.should == ''
|
189
|
+
StdioTrap.stderr.should == ''
|
190
|
+
end
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
describe 'TsvWriter' do
|
195
|
+
before (:each) do
|
196
|
+
StdioTrap.trap!
|
197
|
+
output = B::TsvWriter.new
|
198
|
+
B.enchmark('foobar', :output => output) do
|
199
|
+
job('a') {}
|
200
|
+
end
|
201
|
+
@stdio = StdioTrap.release!
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "stdout" do
|
205
|
+
it "looks like output from TsvWriter" do
|
206
|
+
@stdio[:stdout].should match /^group\tid\trounds\trate\tmean\tmax\tmin\tstddev\tx\n/
|
207
|
+
@stdio[:stdout].split("\n")[1].should match /^foobar\t/
|
208
|
+
@stdio[:stdout].lines.count.should == 2
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
describe "stderr" do
|
213
|
+
it "is empty" do
|
214
|
+
@stdio[:stderr].should == ''
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
describe 'HtmlWriter' do
|
220
|
+
before (:each) do
|
221
|
+
StdioTrap.trap!
|
222
|
+
output = B::HtmlWriter.new
|
223
|
+
B.enchmark('foobar', :output => output) do
|
224
|
+
job('a') {}
|
225
|
+
end
|
226
|
+
@stdio = StdioTrap.release!
|
227
|
+
end
|
228
|
+
|
229
|
+
describe "stdout" do
|
230
|
+
it "looks like output from TsvWriter" do
|
231
|
+
@stdio[:stdout].should match /^<table>\n<tr><th>/
|
232
|
+
@stdio[:stdout].split("\n")[2].should match /^<tr><td>foobar<\/td>/
|
233
|
+
@stdio[:stdout].lines.count.should == 4
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "stderr" do
|
238
|
+
it "is empty" do
|
239
|
+
@stdio[:stderr].should == ''
|
240
|
+
end
|
241
|
+
end
|
242
|
+
|
243
|
+
end
|
244
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,188 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: b
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Moe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2013-01-05 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: simplecov
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '0'
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rake
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: '0'
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: '0'
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: rspec
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: guard
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
- !ruby/object:Gem::Dependency
|
79
|
+
name: guard-rspec
|
80
|
+
requirement: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ! '>='
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: '0'
|
86
|
+
type: :development
|
87
|
+
prerelease: false
|
88
|
+
version_requirements: !ruby/object:Gem::Requirement
|
89
|
+
none: false
|
90
|
+
requirements:
|
91
|
+
- - ! '>='
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
- !ruby/object:Gem::Dependency
|
95
|
+
name: stdiotrap
|
96
|
+
requirement: !ruby/object:Gem::Requirement
|
97
|
+
none: false
|
98
|
+
requirements:
|
99
|
+
- - ! '>='
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
none: false
|
106
|
+
requirements:
|
107
|
+
- - ! '>='
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
- !ruby/object:Gem::Dependency
|
111
|
+
name: hitimes
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
113
|
+
none: false
|
114
|
+
requirements:
|
115
|
+
- - ! '>='
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :runtime
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
none: false
|
122
|
+
requirements:
|
123
|
+
- - ! '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: blockenspiel
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
none: false
|
130
|
+
requirements:
|
131
|
+
- - ! '>='
|
132
|
+
- !ruby/object:Gem::Version
|
133
|
+
version: '0'
|
134
|
+
type: :runtime
|
135
|
+
prerelease: false
|
136
|
+
version_requirements: !ruby/object:Gem::Requirement
|
137
|
+
none: false
|
138
|
+
requirements:
|
139
|
+
- - ! '>='
|
140
|
+
- !ruby/object:Gem::Version
|
141
|
+
version: '0'
|
142
|
+
description: A small, convenient benchmark-library.
|
143
|
+
email:
|
144
|
+
- moe@busyloop.net
|
145
|
+
executables: []
|
146
|
+
extensions: []
|
147
|
+
extra_rdoc_files: []
|
148
|
+
files:
|
149
|
+
- .gitignore
|
150
|
+
- Gemfile
|
151
|
+
- Guardfile
|
152
|
+
- LICENSE.txt
|
153
|
+
- README.md
|
154
|
+
- Rakefile
|
155
|
+
- b.gemspec
|
156
|
+
- lib/b.rb
|
157
|
+
- lib/b/demo.rb
|
158
|
+
- lib/b/output_plugins.rb
|
159
|
+
- lib/b/version.rb
|
160
|
+
- spec/b_spec.rb
|
161
|
+
- spec/spec_helper.rb
|
162
|
+
homepage: https://github.com/busyloop/b
|
163
|
+
licenses: []
|
164
|
+
post_install_message:
|
165
|
+
rdoc_options: []
|
166
|
+
require_paths:
|
167
|
+
- lib
|
168
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
169
|
+
none: false
|
170
|
+
requirements:
|
171
|
+
- - ! '>='
|
172
|
+
- !ruby/object:Gem::Version
|
173
|
+
version: '0'
|
174
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
175
|
+
none: false
|
176
|
+
requirements:
|
177
|
+
- - ! '>='
|
178
|
+
- !ruby/object:Gem::Version
|
179
|
+
version: '0'
|
180
|
+
requirements: []
|
181
|
+
rubyforge_project:
|
182
|
+
rubygems_version: 1.8.23
|
183
|
+
signing_key:
|
184
|
+
specification_version: 3
|
185
|
+
summary: A small, convenient benchmark-library.
|
186
|
+
test_files:
|
187
|
+
- spec/b_spec.rb
|
188
|
+
- spec/spec_helper.rb
|