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.
- checksums.yaml +7 -0
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.travis.yml +21 -0
- data/Gemfile +6 -0
- data/README.md +144 -0
- data/Rakefile +6 -0
- data/benchmark-http.gemspec +23 -0
- data/bin/benchmark-http +28 -0
- data/lib/benchmark/http.rb +22 -0
- data/lib/benchmark/http/command.rb +72 -0
- data/lib/benchmark/http/command/concurrency.rb +124 -0
- data/lib/benchmark/http/seconds.rb +52 -0
- data/lib/benchmark/http/statistics.rb +118 -0
- data/lib/benchmark/http/version.rb +25 -0
- metadata +114 -0
checksums.yaml
ADDED
|
@@ -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
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
|
@@ -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
data/README.md
ADDED
|
@@ -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
|
+
[](http://travis-ci.org/socketry/benchmark-http)
|
|
6
|
+
[](https://codeclimate.com/github/socketry/benchmark-http)
|
|
7
|
+
[](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.
|
data/Rakefile
ADDED
|
@@ -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
|
data/bin/benchmark-http
ADDED
|
@@ -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: []
|