b 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![Build Status](https://travis-ci.org/busyloop/b.png?branch=master)](https://travis-ci.org/busyloop/b) [![Dependency Status](https://gemnasium.com/busyloop/b.png)](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
|