benchmark-http 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b3ee38b73acca01fe8fa41779cf8ce54df543e70a383efb931ae535f7faf53f4
4
+ data.tar.gz: 7291fa79b4ed7e60aaecef5be5c7241ce4e6c6aeac5ea930b006ca4e677672d1
5
+ SHA512:
6
+ metadata.gz: 2a1abfcbfc272ff0ed63ddb5ba98fb05479d61fe8a69cdb417cf814334252bad7d4fb12919f0a4c5e42c081809a30594376a1e56e4d88299f2df9bf576df4239
7
+ data.tar.gz: 34d9b1f2c5d3208d42e57d503dbda173a3671777ae5f15bcd821ae849968004d19fef8131d9ea7b17bb5bbd52c042d7fab8bd851e2a2042d8131a40eee0f09a5
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,21 @@
1
+ language: ruby
2
+ sudo: required
3
+ dist: xenial
4
+ cache: bundler
5
+
6
+ before_script:
7
+ - gem update --system
8
+
9
+ matrix:
10
+ include:
11
+ - rvm: 2.3
12
+ - rvm: 2.4
13
+ - rvm: 2.5
14
+ - rvm: jruby-head
15
+ env: JRUBY_OPTS="--debug -X+O"
16
+ - rvm: ruby-head
17
+ - rvm: rbx-3
18
+ allow_failures:
19
+ - rvm: ruby-head
20
+ - rvm: jruby-head
21
+ - rvm: rbx-3
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in benchmark-http.gemspec
6
+ gemspec
@@ -0,0 +1,144 @@
1
+ # Benchmark::HTTP
2
+
3
+ An asynchronous HTTP benchmark tool built on top of [async], [async-io] and [async-http]. Useful for analysing server performance. Supports HTTP1 and HTTP2.
4
+
5
+ [![Build Status](https://secure.travis-ci.org/socketry/benchmark-http.svg)](http://travis-ci.org/socketry/benchmark-http)
6
+ [![Code Climate](https://codeclimate.com/github/socketry/benchmark-http.svg)](https://codeclimate.com/github/socketry/benchmark-http)
7
+ [![Coverage Status](https://coveralls.io/repos/socketry/benchmark-http/badge.svg)](https://coveralls.io/r/socketry/benchmark-http)
8
+
9
+ [async]: https://github.com/socketry/async
10
+ [async-io]: https://github.com/socketry/async-io
11
+ [async-http]: https://github.com/socketry/async-http
12
+
13
+ ## Installation
14
+
15
+ Install it yourself:
16
+
17
+ $ gem install benchmark-http
18
+
19
+ ## Usage
20
+
21
+ You can run `benchmark-http` is a top level tool for invoking specific benchmarks.
22
+
23
+ ### Concurrency
24
+
25
+ This benchmark determines the optimal level of concurrency (maximise throughput while keeping latency to a minimum).
26
+
27
+ ```shell
28
+ $ benchmark-http concurrency https://www.oriontransfer.co.nz/welcome/index
29
+ I am going to benchmark https://www.oriontransfer.co.nz/welcome/index...
30
+ I am running 1 asynchronous tasks that will each make sequential requests...
31
+ I made 273 requests in 52.4s. The per-request latency was 191.79ms. That's 5.214149911737622 asynchronous requests/second.
32
+ Variance: 997.437µs
33
+ Standard Deviation: 31.58ms
34
+ Standard Error: 0.0019114428646174592
35
+ I am running 2 asynchronous tasks that will each make sequential requests...
36
+ I made 177 requests in 16.8s. The per-request latency was 190.19ms. That's 10.51600772540387 asynchronous requests/second.
37
+ Variance: 632.076µs
38
+ Standard Deviation: 25.14ms
39
+ Standard Error: 0.001889722381767832
40
+ I am running 4 asynchronous tasks that will each make sequential requests...
41
+ I made 8 requests in 372.49ms. The per-request latency was 186.25ms. That's 21.476841588829895 asynchronous requests/second.
42
+ Variance: 0.048µs
43
+ Standard Deviation: 219.819µs
44
+ Standard Error: 7.771792696588776e-05
45
+ I am running 8 asynchronous tasks that will each make sequential requests...
46
+ I made 128 requests in 3.0s. The per-request latency was 188.10ms. That's 42.53004421587869 asynchronous requests/second.
47
+ Variance: 399.781µs
48
+ Standard Deviation: 19.99ms
49
+ Standard Error: 0.0017672840585127617
50
+ I am running 16 asynchronous tasks that will each make sequential requests...
51
+ I made 184 requests in 2.2s. The per-request latency was 188.46ms. That's 84.89938672881854 asynchronous requests/second.
52
+ Variance: 548.641µs
53
+ Standard Deviation: 23.42ms
54
+ Standard Error: 0.0017267724185582615
55
+ I am running 32 asynchronous tasks that will each make sequential requests...
56
+ I made 152 requests in 891.06ms. The per-request latency was 187.59ms. That's 170.58399627520865 asynchronous requests/second.
57
+ Variance: 335.694µs
58
+ Standard Deviation: 18.32ms
59
+ Standard Error: 0.00148610620533633
60
+ I am running 64 asynchronous tasks that will each make sequential requests...
61
+ I made 438 requests in 1.3s. The per-request latency was 191.68ms. That's 333.89790173541496 asynchronous requests/second.
62
+ Variance: 1.19ms
63
+ Standard Deviation: 34.51ms
64
+ Standard Error: 0.001648801656177374
65
+ I am running 128 asynchronous tasks that will each make sequential requests...
66
+ I made 360 requests in 533.83ms. The per-request latency was 189.81ms. That's 674.373540567776 asynchronous requests/second.
67
+ Variance: 555.212µs
68
+ Standard Deviation: 23.56ms
69
+ Standard Error: 0.0012418759009531876
70
+ I am running 256 asynchronous tasks that will each make sequential requests...
71
+ I made 512 requests in 463.03ms. The per-request latency was 231.51ms. That's 1105.762787139087 asynchronous requests/second.
72
+ Variance: 888.185µs
73
+ Standard Deviation: 29.80ms
74
+ Standard Error: 0.0013170938569343825
75
+ I am running 192 asynchronous tasks that will each make sequential requests...
76
+ I made 384 requests in 380.97ms. The per-request latency was 190.48ms. That's 1007.9615261872923 asynchronous requests/second.
77
+ Variance: 142.770µs
78
+ Standard Deviation: 11.95ms
79
+ Standard Error: 0.0006097518459132856
80
+ I am running 224 asynchronous tasks that will each make sequential requests...
81
+ I made 448 requests in 411.79ms. The per-request latency was 205.89ms. That's 1087.9398101463066 asynchronous requests/second.
82
+ Variance: 215.480µs
83
+ Standard Deviation: 14.68ms
84
+ Standard Error: 0.0006935294886115942
85
+ I am running 240 asynchronous tasks that will each make sequential requests...
86
+ I made 480 requests in 401.62ms. The per-request latency was 200.81ms. That's 1195.1473779597363 asynchronous requests/second.
87
+ Variance: 292.021µs
88
+ Standard Deviation: 17.09ms
89
+ Standard Error: 0.0007799848849992035
90
+ I am running 248 asynchronous tasks that will each make sequential requests...
91
+ I made 496 requests in 432.58ms. The per-request latency was 216.29ms. That's 1146.621534849607 asynchronous requests/second.
92
+ Variance: 446.681µs
93
+ Standard Deviation: 21.13ms
94
+ Standard Error: 0.0009489813840514241
95
+ I am running 252 asynchronous tasks that will each make sequential requests...
96
+ I made 504 requests in 417.86ms. The per-request latency was 208.93ms. That's 1206.1477638702509 asynchronous requests/second.
97
+ Variance: 222.939µs
98
+ Standard Deviation: 14.93ms
99
+ Standard Error: 0.0006650854376052381
100
+ I am running 254 asynchronous tasks that will each make sequential requests...
101
+ I made 508 requests in 419.67ms. The per-request latency was 209.83ms. That's 1210.4835614086478 asynchronous requests/second.
102
+ Variance: 177.005µs
103
+ Standard Deviation: 13.30ms
104
+ Standard Error: 0.0005902836562252991
105
+ I am running 255 asynchronous tasks that will each make sequential requests...
106
+ I made 510 requests in 434.38ms. The per-request latency was 217.19ms. That's 1174.0936493567908 asynchronous requests/second.
107
+ Variance: 457.592µs
108
+ Standard Deviation: 21.39ms
109
+ Standard Error: 0.000947227304291054
110
+ Your server can handle 255 concurrent requests.
111
+ At this level of concurrency, requests have ~1.13x higher latency.
112
+ ```
113
+
114
+ ## Contributing
115
+
116
+ 1. Fork it
117
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
118
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
119
+ 4. Push to the branch (`git push origin my-new-feature`)
120
+ 5. Create new Pull Request
121
+
122
+ ## License
123
+
124
+ Released under the MIT license.
125
+
126
+ Copyright, 2018, by [Samuel G. D. Williams](http://www.codeotaku.com/samuel-williams).
127
+
128
+ Permission is hereby granted, free of charge, to any person obtaining a copy
129
+ of this software and associated documentation files (the "Software"), to deal
130
+ in the Software without restriction, including without limitation the rights
131
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
132
+ copies of the Software, and to permit persons to whom the Software is
133
+ furnished to do so, subject to the following conditions:
134
+
135
+ The above copyright notice and this permission notice shall be included in
136
+ all copies or substantial portions of the Software.
137
+
138
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
139
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
140
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
141
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
142
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
143
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
144
+ THE SOFTWARE.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,23 @@
1
+ require_relative "lib/benchmark/http/version"
2
+
3
+ Gem::Specification.new do |spec|
4
+ spec.name = "benchmark-http"
5
+ spec.version = Benchmark::HTTP::VERSION
6
+ spec.authors = ["Samuel Williams"]
7
+ spec.email = ["samuel.williams@oriontransfer.co.nz"]
8
+
9
+ spec.summary = "An asynchronous benchmark toolbox for modern HTTP servers."
10
+ spec.homepage = "https://github.com/socketry/benchmark-http"
11
+
12
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
13
+ f.match(%r{^(test|spec|features)/})
14
+ end
15
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
16
+ spec.require_paths = ["lib"]
17
+
18
+ spec.add_dependency "samovar"
19
+
20
+ spec.add_development_dependency "bundler", "~> 1.16"
21
+ spec.add_development_dependency "rake", "~> 10.0"
22
+ spec.add_development_dependency "rspec", "~> 3.0"
23
+ end
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
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.
22
+
23
+ require_relative '../lib/benchmark/http'
24
+
25
+ begin
26
+ Benchmark::HTTP::Command.parse(ARGV).invoke
27
+ rescue Interrupt
28
+ end
@@ -0,0 +1,22 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative "http/version"
22
+ require_relative "http/command"
@@ -0,0 +1,72 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative 'command/concurrency'
22
+ require_relative 'version'
23
+ require 'samovar'
24
+
25
+ module Benchmark
26
+ module HTTP
27
+ module Command
28
+ def self.parse(*args)
29
+ Top.parse(*args)
30
+ end
31
+
32
+ class Top < Samovar::Command
33
+ self.description = "An asynchronous HTTP server benchmark."
34
+
35
+ options do
36
+ option '--verbose | --quiet', "Verbosity of output for debugging.", key: :logging
37
+ option '-h/--help', "Print out help information."
38
+ option '-v/--version', "Print out the application version."
39
+ end
40
+
41
+ nested '<command>',
42
+ 'concurrency' => Concurrency
43
+
44
+ def verbose?
45
+ @options[:logging] == :verbose
46
+ end
47
+
48
+ def quiet?
49
+ @options[:logging] == :quiet
50
+ end
51
+
52
+ def invoke(program_name: File.basename($0))
53
+ if verbose?
54
+ Async.logger.level = Logger::DEBUG
55
+ elsif quiet?
56
+ Async.logger.level = Logger::WARN
57
+ else
58
+ Async.logger.level = Logger::INFO
59
+ end
60
+
61
+ if @options[:version]
62
+ puts "benchmark-http v#{Falcon::VERSION}"
63
+ elsif @options[:help] or @command.nil?
64
+ print_usage(program_name)
65
+ else
66
+ @command.invoke(self)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,124 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ require_relative '../seconds'
22
+ require_relative '../statistics'
23
+
24
+ require 'async'
25
+ require 'async/http/client'
26
+ require 'async/http/url_endpoint'
27
+
28
+ require 'samovar'
29
+
30
+ module Benchmark
31
+ module HTTP
32
+ module Command
33
+ class Concurrency < Samovar::Command
34
+ self.description = "Determine the optimal level of concurrency."
35
+
36
+ options do
37
+ option '-t/--threshold <factor>', "The acceptable latency penalty when making concurrent requests", default: 1.2, type: Float
38
+ option '-c/--confidence <factor>', "The confidence required when computing latency (lower is less reliable but faster)", default: 0.99, type: Float
39
+ end
40
+
41
+ many :hosts, "One or more hosts to benchmark"
42
+
43
+ def confidence_factor
44
+ 1.0 - @options[:confidence]
45
+ end
46
+
47
+ def measure_performance(concurrency, endpoint, request_path)
48
+ puts "I am running #{concurrency} asynchronous tasks that will each make sequential requests..."
49
+
50
+ statistics = Statistics.new(concurrency)
51
+ task = Async::Task.current
52
+
53
+ concurrency.times.map do
54
+ task.async do
55
+ client = Async::HTTP::Client.new(endpoint, endpoint.protocol)
56
+
57
+ statistics.sample(confidence_factor) do
58
+ response = client.get(request_path)
59
+ end
60
+
61
+ client.close
62
+ end
63
+ end.each(&:wait)
64
+
65
+ puts "I made #{statistics.count} requests in #{Seconds[statistics.sequential_duration]}. The per-request latency was #{Seconds[statistics.latency]}. That's #{statistics.per_second} asynchronous requests/second."
66
+ puts "\t Variance: #{Seconds[statistics.variance]}"
67
+ puts "\tStandard Deviation: #{Seconds[statistics.standard_deviation]}"
68
+ puts "\t Standard Error: #{statistics.standard_error}"
69
+
70
+ return statistics
71
+ end
72
+
73
+ def run(url)
74
+ endpoint = Async::HTTP::URLEndpoint.parse(url)
75
+ request_path = endpoint.url.request_uri
76
+
77
+ puts "I am going to benchmark #{url}..."
78
+
79
+ Async::Reactor.run do |task|
80
+ statistics = []
81
+ minimum = 1
82
+
83
+ base = measure_performance(minimum, endpoint, request_path)
84
+ statistics << base
85
+
86
+ current = 2
87
+ maximum = nil
88
+
89
+ while statistics.last.concurrency < current
90
+ results = measure_performance(current, endpoint, request_path)
91
+
92
+ if base.similar?(results, @options[:threshold])
93
+ statistics << results
94
+
95
+ minimum = current
96
+
97
+ if maximum
98
+ current += (maximum - current) / 2
99
+ else
100
+ current *= 2
101
+ end
102
+ else
103
+ # current concurrency is too big, so we limit maximum to it.
104
+ maximum = current
105
+
106
+ current = (minimum + (maximum - minimum) / 2).floor
107
+ end
108
+ end
109
+
110
+ puts "Your server can handle #{statistics.last.concurrency} concurrent requests."
111
+
112
+ puts "At this level of concurrency, requests have ~#{(statistics.last.latency / statistics.first.latency).round(2)}x higher latency."
113
+ end
114
+ end
115
+
116
+ def invoke(parent)
117
+ @hosts.each do |host|
118
+ run(host).wait
119
+ end
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,52 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Benchmark
22
+ module HTTP
23
+ class Seconds
24
+ UNITS = ["s", "ms", "µs"]
25
+ SCALE = UNITS.count - 1
26
+
27
+ def self.[](value)
28
+ self.new(value)
29
+ end
30
+
31
+ def initialize(value)
32
+ @value = value
33
+ end
34
+
35
+ def scale
36
+ Math.log(@value) / Math.log(1000)
37
+ end
38
+
39
+ def to_s
40
+ scaled_value = @value
41
+ scale = 0
42
+
43
+ while scaled_value < 1 && scale < SCALE
44
+ scaled_value *= 1000
45
+ scale += 1
46
+ end
47
+
48
+ return sprintf("%0.#{scale+1}f%s", scaled_value, UNITS.fetch(scale))
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,118 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Benchmark
22
+ module HTTP
23
+ class Statistics
24
+ def initialize(concurrency)
25
+ @samples = []
26
+ @duration = 0
27
+
28
+ @concurrency = concurrency
29
+ end
30
+
31
+ attr :samples
32
+ attr :duration
33
+
34
+ attr :concurrency
35
+
36
+ def sequential_duration
37
+ @duration / @concurrency
38
+ end
39
+
40
+ def count
41
+ @samples.count
42
+ end
43
+
44
+ def per_second
45
+ @samples.count.to_f / sequential_duration.to_f
46
+ end
47
+
48
+ def latency
49
+ @duration.to_f / @samples.count.to_f
50
+ end
51
+
52
+ def similar?(other, difference = 2.0)
53
+ ratio = other.latency / self.latency
54
+
55
+ return ratio < difference
56
+ end
57
+
58
+ def average
59
+ if @samples.any?
60
+ @samples.sum / @samples.count
61
+ end
62
+ end
63
+
64
+ # Computes Population Variance, σ^2.
65
+ def variance
66
+ return nil if @samples.count < 2
67
+
68
+ average = self.average
69
+
70
+ return @samples.map{|n| (n - average)**2}.sum / @samples.count
71
+ end
72
+
73
+ # Population Standard Deviation, σ
74
+ def standard_deviation
75
+ if variance = self.variance
76
+ Math.sqrt(variance.abs)
77
+ end
78
+ end
79
+
80
+ def standard_error
81
+ if standard_deviation = self.standard_deviation
82
+ standard_deviation / Math.sqrt(@samples.count)
83
+ end
84
+ end
85
+
86
+ def measure
87
+ start_time = Time.now
88
+
89
+ result = yield
90
+
91
+ duration = Time.now - start_time
92
+ @samples << duration
93
+ @duration += duration
94
+
95
+ return result
96
+ end
97
+
98
+ def sample(confidence_factor, &block)
99
+ # warmup
100
+ yield
101
+
102
+ begin
103
+ measure(&block)
104
+ end until confident?(confidence_factor)
105
+ end
106
+
107
+ def print(out = STDOUT)
108
+ out.puts "#{@samples.count} samples. #{1.0 / self.average} per second. S/D: #{standard_deviation}."
109
+ end
110
+
111
+ private
112
+
113
+ def confident?(factor)
114
+ (@samples.count > @concurrency) && self.standard_error < (self.average * factor)
115
+ end
116
+ end
117
+ end
118
+ end
@@ -0,0 +1,25 @@
1
+ # Copyright, 2018, by Samuel G. D. Williams. <http://www.codeotaku.com>
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ module Benchmark
22
+ module HTTP
23
+ VERSION = "0.1.0"
24
+ end
25
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: benchmark-http
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Samuel Williams
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-03-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: samovar
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.16'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.16'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '3.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '3.0'
69
+ description:
70
+ email:
71
+ - samuel.williams@oriontransfer.co.nz
72
+ executables:
73
+ - benchmark-http
74
+ extensions: []
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".rspec"
79
+ - ".travis.yml"
80
+ - Gemfile
81
+ - README.md
82
+ - Rakefile
83
+ - benchmark-http.gemspec
84
+ - bin/benchmark-http
85
+ - lib/benchmark/http.rb
86
+ - lib/benchmark/http/command.rb
87
+ - lib/benchmark/http/command/concurrency.rb
88
+ - lib/benchmark/http/seconds.rb
89
+ - lib/benchmark/http/statistics.rb
90
+ - lib/benchmark/http/version.rb
91
+ homepage: https://github.com/socketry/benchmark-http
92
+ licenses: []
93
+ metadata: {}
94
+ post_install_message:
95
+ rdoc_options: []
96
+ require_paths:
97
+ - lib
98
+ required_ruby_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ requirements:
105
+ - - ">="
106
+ - !ruby/object:Gem::Version
107
+ version: '0'
108
+ requirements: []
109
+ rubyforge_project:
110
+ rubygems_version: 2.7.6
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: An asynchronous benchmark toolbox for modern HTTP servers.
114
+ test_files: []