benchmark-http 0.2.0 → 0.3.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 +4 -4
- data/.travis.yml +1 -1
- data/Gemfile +4 -3
- data/README.md +79 -0
- data/benchmark-http.gemspec +4 -1
- data/lib/benchmark/http/command.rb +4 -1
- data/lib/benchmark/http/command/spider.rb +150 -0
- data/lib/benchmark/http/links_filter.rb +47 -0
- data/lib/benchmark/http/statistics.rb +91 -18
- data/lib/benchmark/http/version.rb +1 -1
- metadata +34 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: de65883bfb494a1d1da97429666bd4a590361632cc008d1292e11003a00a8acc
|
4
|
+
data.tar.gz: 2b0a8c9f5d9d59799203e6030669a576c4802d0d95c1c1dd4dcf23bba7a875ce
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: '039a873b982321ada9fcfd8cb3f848e46df8786ee2590a075a68580ad4aa5b3bcee638eb47f28a24e88faabf1349215193a02b65d25f377abe3ad091a36f594f'
|
7
|
+
data.tar.gz: 584cea3731b38082c4a1fb078d883c7eb8d61edc5b626d0ef5dc0ead41e9fe5338edbcfc6535f6b9a66e9187580fbcd25b73d68336e76502a3c5f8bb9a6b6e72
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -20,6 +20,85 @@ Install it yourself:
|
|
20
20
|
|
21
21
|
You can run `benchmark-http` is a top level tool for invoking specific benchmarks.
|
22
22
|
|
23
|
+
### Spider
|
24
|
+
|
25
|
+
This benchmark spiders a website and generates some statistics on general access time.
|
26
|
+
|
27
|
+
```shell
|
28
|
+
$ benchmark-http spider https://www.oriontransfer.co.nz/welcome/index
|
29
|
+
HEAD https://www.oriontransfer.co.nz/welcome/index -> HTTP/2.0 404 (unspecified bytes)
|
30
|
+
GET https://www.oriontransfer.co.nz/welcome/index (depth = 10)
|
31
|
+
GET https://www.oriontransfer.co.nz/welcome/index -> HTTP/2.0 404 (2263 bytes)
|
32
|
+
HEAD https://www.oriontransfer.co.nz/products/index -> HTTP/2.0 200 (unspecified bytes)
|
33
|
+
GET https://www.oriontransfer.co.nz/products/index (depth = 9)
|
34
|
+
HEAD https://www.oriontransfer.co.nz/services/index -> HTTP/2.0 200 (unspecified bytes)
|
35
|
+
GET https://www.oriontransfer.co.nz/services/index (depth = 9)
|
36
|
+
HEAD https://www.oriontransfer.co.nz/support/index -> HTTP/2.0 200 (unspecified bytes)
|
37
|
+
GET https://www.oriontransfer.co.nz/support/index (depth = 9)
|
38
|
+
HEAD https://www.oriontransfer.co.nz/support/contact-us -> HTTP/2.0 307 (unspecified bytes)
|
39
|
+
Following redirect to https://www.oriontransfer.co.nz/support/contact-us/index...
|
40
|
+
HEAD https://www.oriontransfer.co.nz/support/terms-of-service -> HTTP/2.0 200 (unspecified bytes)
|
41
|
+
GET https://www.oriontransfer.co.nz/support/terms-of-service (depth = 9)
|
42
|
+
GET https://www.oriontransfer.co.nz/products/index -> HTTP/2.0 200 (3469 bytes)
|
43
|
+
GET https://www.oriontransfer.co.nz/services/index -> HTTP/2.0 200 (2488 bytes)
|
44
|
+
GET https://www.oriontransfer.co.nz/support/index -> HTTP/2.0 200 (2246 bytes)
|
45
|
+
HEAD https://www.oriontransfer.co.nz/support/contact-us/index -> HTTP/2.0 200 (unspecified bytes)
|
46
|
+
GET https://www.oriontransfer.co.nz/support/contact-us/index (depth = 8)
|
47
|
+
GET https://www.oriontransfer.co.nz/support/terms-of-service -> HTTP/2.0 200 (8466 bytes)
|
48
|
+
HEAD https://www.oriontransfer.co.nz/products/library-inspector/index -> HTTP/2.0 200 (unspecified bytes)
|
49
|
+
GET https://www.oriontransfer.co.nz/products/library-inspector/index (depth = 8)
|
50
|
+
HEAD https://www.oriontransfer.co.nz/products/truth-tables/index -> HTTP/2.0 200 (unspecified bytes)
|
51
|
+
GET https://www.oriontransfer.co.nz/products/truth-tables/index (depth = 8)
|
52
|
+
HEAD https://www.oriontransfer.co.nz/products/fingerprint/index -> HTTP/2.0 200 (unspecified bytes)
|
53
|
+
GET https://www.oriontransfer.co.nz/products/fingerprint/index (depth = 8)
|
54
|
+
HEAD https://www.oriontransfer.co.nz/services/internet-services -> HTTP/2.0 200 (unspecified bytes)
|
55
|
+
GET https://www.oriontransfer.co.nz/services/internet-services (depth = 8)
|
56
|
+
HEAD https://www.oriontransfer.co.nz/services/software-development -> HTTP/2.0 200 (unspecified bytes)
|
57
|
+
GET https://www.oriontransfer.co.nz/services/software-development (depth = 8)
|
58
|
+
HEAD https://www.oriontransfer.co.nz/services/systems-administration -> HTTP/2.0 200 (unspecified bytes)
|
59
|
+
GET https://www.oriontransfer.co.nz/services/systems-administration (depth = 8)
|
60
|
+
HEAD https://www.oriontransfer.co.nz/services/website-development -> HTTP/2.0 200 (unspecified bytes)
|
61
|
+
GET https://www.oriontransfer.co.nz/services/website-development (depth = 8)
|
62
|
+
HEAD https://www.oriontransfer.co.nz/support/contact-us/ -> HTTP/2.0 307 (unspecified bytes)
|
63
|
+
Following redirect to https://www.oriontransfer.co.nz/support/contact-us/index...
|
64
|
+
GET https://www.oriontransfer.co.nz/support/contact-us/index -> HTTP/2.0 200 (3094 bytes)
|
65
|
+
GET https://www.oriontransfer.co.nz/products/library-inspector/index -> HTTP/2.0 200 (5592 bytes)
|
66
|
+
GET https://www.oriontransfer.co.nz/products/truth-tables/index -> HTTP/2.0 200 (4160 bytes)
|
67
|
+
GET https://www.oriontransfer.co.nz/products/fingerprint/index -> HTTP/2.0 200 (4414 bytes)
|
68
|
+
GET https://www.oriontransfer.co.nz/services/internet-services -> HTTP/2.0 200 (3362 bytes)
|
69
|
+
GET https://www.oriontransfer.co.nz/services/software-development -> HTTP/2.0 200 (3521 bytes)
|
70
|
+
GET https://www.oriontransfer.co.nz/services/systems-administration -> HTTP/2.0 200 (2979 bytes)
|
71
|
+
GET https://www.oriontransfer.co.nz/services/website-development -> HTTP/2.0 200 (3943 bytes)
|
72
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/library-inspector/_screenshots/large/Library%20Inspector%20(Libraries).png -> HTTP/2.0 200 (unspecified bytes)
|
73
|
+
Unsupported content type: image/png
|
74
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/library-inspector/_screenshots/large/Library%20Inspector%20(Libraries%20Disassembly).png -> HTTP/2.0 200 (unspecified bytes)
|
75
|
+
Unsupported content type: image/png
|
76
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/library-inspector/_screenshots/large/Library%20Inspector%20(Libraries%20QuickLook).png -> HTTP/2.0 200 (unspecified bytes)
|
77
|
+
Unsupported content type: image/png
|
78
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/library-inspector/_screenshots/large/Library%20Inspector%20(App).png -> HTTP/2.0 200 (unspecified bytes)
|
79
|
+
Unsupported content type: image/png
|
80
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/library-inspector/_screenshots/large/Library%20Inspector%20(App%20Headers).png -> HTTP/2.0 200 (unspecified bytes)
|
81
|
+
Unsupported content type: image/png
|
82
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/truth-tables/_screenshots/large/Reformat%20Expression.png -> HTTP/2.0 200 (unspecified bytes)
|
83
|
+
Unsupported content type: image/png
|
84
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/truth-tables/_screenshots/large/Large%20Tables.png -> HTTP/2.0 200 (unspecified bytes)
|
85
|
+
Unsupported content type: image/png
|
86
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/truth-tables/_screenshots/large/Tutor.png -> HTTP/2.0 200 (unspecified bytes)
|
87
|
+
Unsupported content type: image/png
|
88
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/truth-tables/_screenshots/large/Informative%20Text.png -> HTTP/2.0 200 (unspecified bytes)
|
89
|
+
Unsupported content type: image/png
|
90
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/fingerprint/_screenshots/large/Fingerprint%20(3).png -> HTTP/2.0 200 (unspecified bytes)
|
91
|
+
Unsupported content type: image/png
|
92
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/fingerprint/_screenshots/large/Fingerprint%20(2).png -> HTTP/2.0 200 (unspecified bytes)
|
93
|
+
Unsupported content type: image/png
|
94
|
+
HEAD https://www.oriontransfer.co.nz/_gallery/products/fingerprint/_screenshots/large/Fingerprint%20(1).png -> HTTP/2.0 200 (unspecified bytes)
|
95
|
+
Unsupported content type: image/png
|
96
|
+
HEAD https://www.oriontransfer.co.nz/services/training -> HTTP/2.0 200 (unspecified bytes)
|
97
|
+
GET https://www.oriontransfer.co.nz/services/training (depth = 7)
|
98
|
+
GET https://www.oriontransfer.co.nz/services/training -> HTTP/2.0 200 (2994 bytes)
|
99
|
+
14 samples: 13x 200; 1x 404. 15.124294695167547 requests per second. S/D: 35.69ms.
|
100
|
+
```
|
101
|
+
|
23
102
|
### Concurrency
|
24
103
|
|
25
104
|
This benchmark determines the optimal level of concurrency (maximise throughput while keeping latency to a minimum).
|
data/benchmark-http.gemspec
CHANGED
@@ -16,7 +16,10 @@ Gem::Specification.new do |spec|
|
|
16
16
|
spec.require_paths = ["lib"]
|
17
17
|
|
18
18
|
spec.add_dependency("async-io", "~> 1.5")
|
19
|
-
spec.add_dependency("async-http", "~> 0.
|
19
|
+
spec.add_dependency("async-http", "~> 0.27.0")
|
20
|
+
spec.add_dependency("async-await")
|
21
|
+
|
22
|
+
spec.add_dependency("trenni-sanitize")
|
20
23
|
|
21
24
|
spec.add_dependency('samovar', "~> 1.3")
|
22
25
|
|
@@ -19,6 +19,8 @@
|
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
21
|
require_relative 'command/concurrency'
|
22
|
+
require_relative 'command/spider'
|
23
|
+
|
22
24
|
require_relative 'version'
|
23
25
|
require 'samovar'
|
24
26
|
|
@@ -39,7 +41,8 @@ module Benchmark
|
|
39
41
|
end
|
40
42
|
|
41
43
|
nested '<command>',
|
42
|
-
'concurrency' => Concurrency
|
44
|
+
'concurrency' => Concurrency,
|
45
|
+
'spider' => Spider
|
43
46
|
|
44
47
|
def verbose?
|
45
48
|
@options[:logging] == :verbose
|
@@ -0,0 +1,150 @@
|
|
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
|
+
require_relative '../links_filter'
|
24
|
+
|
25
|
+
require 'async'
|
26
|
+
require 'async/http/client'
|
27
|
+
require 'async/http/url_endpoint'
|
28
|
+
require 'async/await'
|
29
|
+
|
30
|
+
require 'samovar'
|
31
|
+
require 'uri'
|
32
|
+
|
33
|
+
module Benchmark
|
34
|
+
module HTTP
|
35
|
+
module Command
|
36
|
+
class Spider < Samovar::Command
|
37
|
+
include Async::Await
|
38
|
+
|
39
|
+
self.description = "Spider a website and report on performance."
|
40
|
+
|
41
|
+
options do
|
42
|
+
option '-d/--depth <count>', "The number of nested URLs to traverse.", default: 10, type: Integer
|
43
|
+
option '-h/--headers', "Print out the response headers", default: false
|
44
|
+
end
|
45
|
+
|
46
|
+
many :urls, "One or more hosts to benchmark"
|
47
|
+
|
48
|
+
def log(method, url, response)
|
49
|
+
puts "#{method} #{url} -> #{response.version} #{response.status} (#{response.body&.length || 'unspecified'} bytes)"
|
50
|
+
|
51
|
+
response.headers.each do |key, value|
|
52
|
+
puts "\t#{key}: #{value}"
|
53
|
+
end if @options[:headers]
|
54
|
+
end
|
55
|
+
|
56
|
+
def extract_links(url, response)
|
57
|
+
base = url
|
58
|
+
|
59
|
+
body = response.read
|
60
|
+
|
61
|
+
begin
|
62
|
+
filter = LinksFilter.parse(body)
|
63
|
+
rescue
|
64
|
+
Async.logger.error($!)
|
65
|
+
return []
|
66
|
+
end
|
67
|
+
|
68
|
+
if filter.base
|
69
|
+
base = base + filter.base
|
70
|
+
end
|
71
|
+
|
72
|
+
filter.links.collect do |href|
|
73
|
+
begin
|
74
|
+
full_url = base + href
|
75
|
+
|
76
|
+
if full_url.host == url.host && full_url.kind_of?(URI::HTTP)
|
77
|
+
yield full_url
|
78
|
+
end
|
79
|
+
rescue ArgumentError, URI::InvalidURIError
|
80
|
+
puts "Could not fetch #{href}, relative to #{base}."
|
81
|
+
end
|
82
|
+
end.compact
|
83
|
+
end
|
84
|
+
|
85
|
+
async def fetch(statistics, client, url, depth = @options[:depth], fetched = Set.new)
|
86
|
+
return if fetched.include?(url) or depth == 0
|
87
|
+
|
88
|
+
fetched << url
|
89
|
+
|
90
|
+
request_uri = url.request_uri
|
91
|
+
|
92
|
+
response = timeout(10) do
|
93
|
+
client.head(request_uri)
|
94
|
+
end
|
95
|
+
|
96
|
+
log("HEAD", url, response)
|
97
|
+
|
98
|
+
if response.redirection?
|
99
|
+
location = url + response.headers['location']
|
100
|
+
if location.host == url.host
|
101
|
+
puts "Following redirect to #{location}..."
|
102
|
+
return fetch(statistics, client, location, depth-1, fetched).wait
|
103
|
+
else
|
104
|
+
puts "Ignoring redirect to #{location}."
|
105
|
+
return
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
content_type = response.headers['content-type']
|
110
|
+
unless content_type&.start_with? 'text/html'
|
111
|
+
puts "Unsupported content type: #{content_type}"
|
112
|
+
return
|
113
|
+
end
|
114
|
+
|
115
|
+
response = timeout(20) do
|
116
|
+
statistics.measure do
|
117
|
+
client.get(request_uri)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
log("GET", url, response)
|
122
|
+
|
123
|
+
extract_links(url, response) do |href|
|
124
|
+
fetch(statistics, client, href, depth - 1, fetched)
|
125
|
+
end.each(&:wait)
|
126
|
+
rescue Async::TimeoutError
|
127
|
+
Async.logger.error("Timeout while fetching #{url}")
|
128
|
+
rescue StandardError
|
129
|
+
Async.logger.error($!)
|
130
|
+
end
|
131
|
+
|
132
|
+
async def invoke(parent)
|
133
|
+
statistics = Statistics.new
|
134
|
+
|
135
|
+
@urls.each do |url|
|
136
|
+
endpoint = Async::HTTP::URLEndpoint.parse(url)
|
137
|
+
|
138
|
+
Async::HTTP::Client.open(endpoint, endpoint.protocol, connection_limit: 4) do |client|
|
139
|
+
fetch(statistics, client, endpoint.url).wait
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
statistics.print
|
144
|
+
|
145
|
+
return statistics
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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 'trenni/sanitize'
|
22
|
+
|
23
|
+
module Benchmark
|
24
|
+
module HTTP
|
25
|
+
class LinksFilter < Trenni::Sanitize::Filter
|
26
|
+
def initialize(*)
|
27
|
+
super
|
28
|
+
|
29
|
+
@base = nil
|
30
|
+
@links = []
|
31
|
+
end
|
32
|
+
|
33
|
+
attr :base
|
34
|
+
attr :links
|
35
|
+
|
36
|
+
def filter(node)
|
37
|
+
if node.name == 'base'
|
38
|
+
@base = node['href']
|
39
|
+
elsif node.name == 'a'
|
40
|
+
@links << node['href']
|
41
|
+
end
|
42
|
+
|
43
|
+
node.skip!(TAG)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -18,23 +18,38 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'async/clock'
|
22
|
+
|
21
23
|
module Benchmark
|
22
24
|
module HTTP
|
23
|
-
class
|
24
|
-
def initialize(concurrency)
|
25
|
+
class Stopwatch
|
26
|
+
def initialize(concurrency = 0)
|
25
27
|
@samples = []
|
26
|
-
|
28
|
+
|
29
|
+
@total_time = 0
|
30
|
+
|
31
|
+
# The number of currently executing measurements:
|
32
|
+
@count = 0
|
27
33
|
|
28
34
|
@concurrency = concurrency
|
35
|
+
@start_time = nil
|
29
36
|
end
|
30
37
|
|
38
|
+
# The individual samples' durations.
|
31
39
|
attr :samples
|
32
|
-
attr :duration
|
33
40
|
|
41
|
+
# The sequential time of all samples.
|
42
|
+
attr :total_time
|
43
|
+
|
44
|
+
# The maximum number of executing measurements at any one time.
|
34
45
|
attr :concurrency
|
35
46
|
|
47
|
+
def duration
|
48
|
+
@samples.sum
|
49
|
+
end
|
50
|
+
|
36
51
|
def sequential_duration
|
37
|
-
|
52
|
+
duration / @concurrency
|
38
53
|
end
|
39
54
|
|
40
55
|
def count
|
@@ -42,11 +57,11 @@ module Benchmark
|
|
42
57
|
end
|
43
58
|
|
44
59
|
def per_second
|
45
|
-
@samples.count.to_f /
|
60
|
+
@samples.count.to_f / total_time.to_f
|
46
61
|
end
|
47
62
|
|
48
63
|
def latency
|
49
|
-
|
64
|
+
duration.to_f / count.to_f
|
50
65
|
end
|
51
66
|
|
52
67
|
def similar?(other, difference = 2.0)
|
@@ -61,13 +76,17 @@ module Benchmark
|
|
61
76
|
end
|
62
77
|
end
|
63
78
|
|
79
|
+
def valid?
|
80
|
+
@samples.count > 1
|
81
|
+
end
|
82
|
+
|
64
83
|
# Computes Population Variance, σ^2.
|
65
84
|
def variance
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
85
|
+
if valid?
|
86
|
+
average = self.average
|
87
|
+
|
88
|
+
return @samples.map{|n| (n - average)**2}.sum / @samples.count
|
89
|
+
end
|
71
90
|
end
|
72
91
|
|
73
92
|
# Population Standard Deviation, σ
|
@@ -83,16 +102,37 @@ module Benchmark
|
|
83
102
|
end
|
84
103
|
end
|
85
104
|
|
105
|
+
def add(duration, result = nil)
|
106
|
+
@samples << duration
|
107
|
+
end
|
108
|
+
|
86
109
|
def measure
|
87
|
-
|
110
|
+
@count += 1
|
111
|
+
|
112
|
+
if @count > @concurrency
|
113
|
+
@concurrency = @count
|
114
|
+
end
|
115
|
+
|
116
|
+
start_time = Async::Clock.now
|
117
|
+
|
118
|
+
unless @start_time
|
119
|
+
@start_time = start_time
|
120
|
+
end
|
88
121
|
|
89
122
|
result = yield
|
90
123
|
|
91
|
-
|
92
|
-
|
93
|
-
|
124
|
+
end_time = Async::Clock.now
|
125
|
+
|
126
|
+
self.add(end_time - start_time, result)
|
94
127
|
|
95
128
|
return result
|
129
|
+
ensure
|
130
|
+
@count -= 1
|
131
|
+
|
132
|
+
if @count == 0
|
133
|
+
@total_time += end_time - @start_time
|
134
|
+
@start_time = nil
|
135
|
+
end
|
96
136
|
end
|
97
137
|
|
98
138
|
def sample(confidence_factor, &block)
|
@@ -105,13 +145,46 @@ module Benchmark
|
|
105
145
|
end
|
106
146
|
|
107
147
|
def print(out = STDOUT)
|
108
|
-
|
148
|
+
if self.valid?
|
149
|
+
out.puts "#{@samples.count} samples. #{per_second} requests per second. S/D: #{Seconds[standard_deviation]}."
|
150
|
+
else
|
151
|
+
out.puts "Not enough samples."
|
152
|
+
end
|
109
153
|
end
|
110
154
|
|
111
155
|
private
|
112
156
|
|
113
157
|
def confident?(factor)
|
114
|
-
|
158
|
+
if @samples.count > @concurrency
|
159
|
+
return self.standard_error < (self.average * factor)
|
160
|
+
end
|
161
|
+
|
162
|
+
return false
|
163
|
+
end
|
164
|
+
end
|
165
|
+
|
166
|
+
class Statistics < Stopwatch
|
167
|
+
def initialize(*)
|
168
|
+
super
|
169
|
+
|
170
|
+
# The count of the status codes seen in the responses:
|
171
|
+
@responses = Hash.new{|h,k| 0}
|
172
|
+
end
|
173
|
+
|
174
|
+
def add(duration, result)
|
175
|
+
super
|
176
|
+
|
177
|
+
@responses[result.status] += 1
|
178
|
+
end
|
179
|
+
|
180
|
+
def print(out = STDOUT)
|
181
|
+
if valid?
|
182
|
+
counts = @responses.sort.collect{|status, count| "#{count}x #{status}"}.join("; ")
|
183
|
+
|
184
|
+
out.puts "#{@samples.count} samples: #{counts}. #{per_second.round(2)} requests per second. S/D: #{Seconds[standard_deviation]}."
|
185
|
+
else
|
186
|
+
out.puts "Not enough samples."
|
187
|
+
end
|
115
188
|
end
|
116
189
|
end
|
117
190
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: benchmark-http
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Samuel Williams
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-07-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: async-io
|
@@ -30,14 +30,42 @@ dependencies:
|
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
33
|
+
version: 0.27.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: 0.27.0
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: async-await
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: trenni-sanitize
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
41
69
|
- !ruby/object:Gem::Dependency
|
42
70
|
name: samovar
|
43
71
|
requirement: !ruby/object:Gem::Requirement
|
@@ -113,6 +141,8 @@ files:
|
|
113
141
|
- lib/benchmark/http.rb
|
114
142
|
- lib/benchmark/http/command.rb
|
115
143
|
- lib/benchmark/http/command/concurrency.rb
|
144
|
+
- lib/benchmark/http/command/spider.rb
|
145
|
+
- lib/benchmark/http/links_filter.rb
|
116
146
|
- lib/benchmark/http/seconds.rb
|
117
147
|
- lib/benchmark/http/statistics.rb
|
118
148
|
- lib/benchmark/http/version.rb
|