drnbench 1.0.3 → 1.0.4

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 3b10736f18dbeaf958fe4b9bd29c0a6788b69f3c
4
- data.tar.gz: bc4363d42fd30a955a70f4d2241052486e121635
3
+ metadata.gz: 6f1ededc12c542fd3d9cdd9f908e3488aa4e4e22
4
+ data.tar.gz: 6c42a7d169c1880cc272e3d652c4cfabe532432b
5
5
  SHA512:
6
- metadata.gz: c7d68baea7789ca3a4a803fa7a4b5d2ff3c24252a7cf49c89d0f33a5eb598f9c0f064dc64c6eb35251abe6a0de4fe910d554e562a5151e18eb00d43973e3ea37
7
- data.tar.gz: 4dad01b34e627c89618827e67f2f070c6cdb229f208d65214361b76477b6d91b10f6be69bf62ed2695078356341714f64fd4007277e674a8f8f8f3a0f4d8f527
6
+ metadata.gz: c611caff29de7e18bee469d17501a6a24570fdb68a464c9d53c2a6e6bf42471d2406f69c52c70f43f340aa99f62f8f946b28fccd92a3625113870344ceba229a
7
+ data.tar.gz: edd62ed000afc56ea5feb077597859efdbd8026a5f37d75f6137fa0ae6c4c94d8acbcc1c0358ebedbaa91bdcfd9fe55a3b637433d0b77d6c15cda68b99324146
@@ -19,6 +19,7 @@
19
19
  require "drnbench"
20
20
  require "optparse"
21
21
  require "shellwords"
22
+ require "sigdump/setup"
22
23
 
23
24
  config = Drnbench::PublishSubscribe::Configuration.new
24
25
  option_parser = OptionParser.new do |parser|
@@ -18,6 +18,7 @@
18
18
 
19
19
  require "drnbench"
20
20
  require "optparse"
21
+ require "sigdump/setup"
21
22
 
22
23
  config = Drnbench::RequestResponse::Configuration.new
23
24
  option_parser = OptionParser.new do |parser|
@@ -39,6 +40,11 @@ option_parser = OptionParser.new do |parser|
39
40
  "(#{config.n_slow_requests})") do |n_slow_requests|
40
41
  config.n_slow_requests = n_slow_requests
41
42
  end
43
+ parser.on("--n-fast-requests=N", Integer,
44
+ "Number of fast requests to be reported.",
45
+ "(#{config.n_fast_requests})") do |n_fast_requests|
46
+ config.n_fast_requests = n_fast_requests
47
+ end
42
48
 
43
49
  parser.separator("")
44
50
  parser.separator("Progressive benchmark:")
@@ -1,6 +1,15 @@
1
1
  # News
2
2
 
3
- ## 1.0.3: 2014-10-29 (planned)
3
+ ## 1.0.4: 2014-11-29
4
+
5
+ * `drnbench-request-response`
6
+ * Not only top slow requests, but top fast requests are also reported.
7
+ It will help you to detect "strange good" results from invalid queries or something.
8
+ The number of reported fast requests can be customized via the new `--n-fast-requests` option.
9
+ * Virtual clients are working with multiple processes.
10
+ If there are multiple processors in your computer, drnbench uses them more effectively.
11
+
12
+ ## 1.0.3: 2014-10-07
4
13
 
5
14
  * `drnbench-request-response`
6
15
  * Accept multiple hosts as a comma separated list via the `--default-hosts` option.
@@ -51,6 +51,8 @@ Gem::Specification.new do |spec|
51
51
  spec.add_runtime_dependency("json")
52
52
  spec.add_runtime_dependency("droonga-client")
53
53
  spec.add_runtime_dependency("drntest")
54
+ spec.add_runtime_dependency("facter")
55
+ spec.add_runtime_dependency("sigdump")
54
56
 
55
57
  spec.add_development_dependency("bundler")
56
58
  spec.add_development_dependency("rake")
@@ -19,9 +19,9 @@ module Drnbench
19
19
  DEFAULT_COMMAND = "search"
20
20
  DEFAULT_METHOD = "POST"
21
21
 
22
- def initialize(params, config)
22
+ def initialize(params)
23
23
  super
24
- @command = params["command"] || DEFAULT_COMMAND
24
+ @command = params[:command] || DEFAULT_COMMAND
25
25
  end
26
26
 
27
27
  private
@@ -16,59 +16,68 @@
16
16
  require "thread"
17
17
  require "droonga/client"
18
18
  require "json"
19
+ require "drb"
19
20
 
20
21
  module Drnbench
21
22
  class HttpClient
22
- attr_reader :requests, :results, :wait
23
+ attr_reader :runner, :results, :wait
23
24
 
24
25
  SUPPORTED_HTTP_METHODS = ["GET", "POST"]
25
26
 
26
27
  @@count = 0
27
28
 
28
- def initialize(params, config)
29
- @requests = params[:requests]
30
- @result = params[:result]
31
- @config = config
29
+ def initialize(params)
30
+ @runner = params[:runner]
31
+ @config = params[:config]
32
32
  @count = 0
33
33
  @id = @@count
34
34
  @@count += 1
35
+ @thread = nil
35
36
  end
36
37
 
37
38
  def run
38
39
  @thread = Thread.new do
40
+ clients = {}
41
+ start_time = Time.now
39
42
  loop do
40
- request = @requests.pop
43
+ if @runner.empty?
44
+ puts "WORNING: requests queue becomes empty! (#{Time.now - start_time} sec)"
45
+ stop
46
+ break
47
+ end
48
+
49
+ request = @runner.pop_request
41
50
  request = fixup_request(request)
42
51
 
43
- client = Droonga::Client.new(:protocol => :http,
44
- :host => request["host"],
45
- :port => request["port"],
46
- :timeout => request["timeout"])
52
+ client_params = {
53
+ :protocol => :http,
54
+ :host => request["host"],
55
+ :port => request["port"],
56
+ :timeout => request["timeout"],
57
+ }
58
+ client = clients[client_params.to_s] ||= Droonga::Client.new(client_params)
59
+
47
60
  request["headers"] ||= {}
48
61
  request["headers"]["user-agent"] = "Ruby/#{RUBY_VERSION} Droonga::Benchmark::Runner::HttpClient"
49
62
  start_time = Time.now
50
- @last_request = request
51
- @last_start_time = start_time
52
63
  begin
53
64
  response = client.request(request)
54
- @result << {
65
+ @runner.push_result(
55
66
  :request => request,
56
67
  :status => response.code,
57
68
  :elapsed_time => Time.now - start_time,
58
69
  :client => @id,
59
70
  :index => @count,
60
- }
71
+ )
61
72
  rescue Timeout::Error
62
- @result << {
73
+ @runner.push_result(
63
74
  :request => request,
64
75
  :status => "0",
65
76
  :elapsed_time => Time.now - start_time,
66
77
  :client => @id,
67
78
  :index => @count,
68
- }
79
+ )
69
80
  end
70
- @last_request = nil
71
- @last_start_time = nil
72
81
  @count += 1
73
82
  sleep @config.wait
74
83
  end
@@ -77,18 +86,14 @@ module Drnbench
77
86
  end
78
87
 
79
88
  def stop
89
+ return unless @thread
90
+
80
91
  @thread.exit
92
+ @thread = nil
93
+ end
81
94
 
82
- if @last_request
83
- @result << {
84
- :request => @last_request,
85
- :status => "0",
86
- :elapsed_time => Time.now - @last_start_time,
87
- :client => @id,
88
- :index => @count,
89
- :last => true,
90
- }
91
- end
95
+ def running?
96
+ not @thread.nil?
92
97
  end
93
98
 
94
99
  private
@@ -19,7 +19,8 @@ module Drnbench
19
19
  module RequestResponse
20
20
  class Configuration
21
21
  attr_accessor :duration, :wait, :interval, :request_patterns_file
22
- attr_accessor :start_n_clients, :end_n_clients, :step, :n_requests, :n_slow_requests
22
+ attr_accessor :start_n_clients, :end_n_clients, :step, :n_requests
23
+ attr_accessor :n_slow_requests, :n_fast_requests
23
24
  attr_accessor :mode
24
25
  attr_reader :default_hosts
25
26
  attr_accessor :default_port, :default_path, :default_method, :default_timeout
@@ -38,6 +39,7 @@ module Drnbench
38
39
  @n_requests = 1000
39
40
  @mode = :http
40
41
  @n_slow_requests = 5
42
+ @n_fast_requests = 5
41
43
 
42
44
  @default_hosts = ["localhost"]
43
45
  @default_port = 80
@@ -66,9 +66,17 @@ module Drnbench
66
66
  groups.each do |group|
67
67
  n_requests = @config.n_requests * @config.end_n_clients * group.frequency
68
68
  base_patterns = group.patterns.shuffle
69
- n_requests.round.times do |count|
69
+ n_requests.ceil.times do |count|
70
70
  pattern = base_patterns[count % base_patterns.size]
71
- requests << pattern.to_request
71
+ if @config.default_hosts.size > 1
72
+ @config.default_hosts.each do |host|
73
+ request = pattern.to_request
74
+ request["host"] ||= host
75
+ requests << request
76
+ end
77
+ else
78
+ requests << pattern.to_request
79
+ end
72
80
  end
73
81
  end
74
82
  requests
@@ -172,11 +180,6 @@ module Drnbench
172
180
  end
173
181
 
174
182
  def to_request
175
- @populated ||= populate
176
- end
177
-
178
- private
179
- def populate
180
183
  if @source.is_a?(String)
181
184
  request = { "path" => @source }
182
185
  else
@@ -16,7 +16,7 @@
16
16
  module Drnbench
17
17
  module RequestResponse
18
18
  class Result
19
- attr_reader :n_clients, :statuses, :n_slow_requests
19
+ attr_reader :n_clients, :statuses, :n_slow_requests, :n_fast_requests
20
20
  attr_accessor :duration
21
21
 
22
22
  class << self
@@ -36,6 +36,7 @@ module Drnbench
36
36
  @n_clients = params[:n_clients]
37
37
  @duration = params[:duration]
38
38
  @n_slow_requests = params[:n_slow_requests] || 5
39
+ @n_fast_requests = params[:n_fast_requests] || 5
39
40
 
40
41
  @results = []
41
42
  @total_elapsed_time = 0.0
@@ -44,6 +45,10 @@ module Drnbench
44
45
  end
45
46
 
46
47
  def <<(result)
48
+ push(result)
49
+ end
50
+
51
+ def push(result)
47
52
  clear_cached_statistics
48
53
 
49
54
  @results << result
@@ -85,21 +90,31 @@ module Drnbench
85
90
 
86
91
  def top_slow_requests
87
92
  slow_requests[0..@n_slow_requests-1].collect do |result|
88
- request = result[:request]
89
- status = result[:status].to_i
90
- if status.zero?
91
- status = "#{status}(aborted)"
92
- end
93
- index = result[:index]
94
- index = "#{index}(last)" if result[:last]
95
- [
96
- "#{result[:elapsed_time]} sec:",
97
- request["method"],
98
- status,
99
- "<#{result[:client]}>#{index}",
100
- "http://#{request["host"]}:#{request["port"]}#{request["path"]}",
101
- ].join(" ")
93
+ format_result_for_request_line(result)
94
+ end
95
+ end
96
+
97
+ def top_fast_requests
98
+ fast_requests[0..@n_fast_requests-1].collect do |result|
99
+ format_result_for_request_line(result)
100
+ end
101
+ end
102
+
103
+ def format_result_for_request_line(result)
104
+ request = result[:request]
105
+ status = result[:status].to_i
106
+ if status.zero?
107
+ status = "#{status}(aborted)"
102
108
  end
109
+ index = result[:index]
110
+ index = "#{index}(last)" if result[:last]
111
+ [
112
+ "#{result[:elapsed_time]} sec:",
113
+ request["method"],
114
+ status,
115
+ "<#{result[:client]}>#{index}",
116
+ "http://#{request["host"]}:#{request["port"]}#{request["path"]}",
117
+ ].join(" ")
103
118
  end
104
119
 
105
120
  def slow_requests
@@ -108,6 +123,12 @@ module Drnbench
108
123
  end
109
124
  end
110
125
 
126
+ def fast_requests
127
+ @results.sort do |a, b|
128
+ a[:elapsed_time] <=> b[:elapsed_time]
129
+ end
130
+ end
131
+
111
132
  def to_s
112
133
  "Total requests: #{total_n_requests} " +
113
134
  "(#{queries_per_second} queries per second)\n" +
@@ -119,6 +140,11 @@ module Drnbench
119
140
  " min: #{min_elapsed_time} sec\n" +
120
141
  " max: #{max_elapsed_time} sec\n" +
121
142
  " average: #{average_elapsed_time} sec\n" +
143
+ "Top #{@n_fast_requests} fast requests:\n" +
144
+ " [time: method status <client>index url]\n" +
145
+ top_fast_requests.collect do |request|
146
+ " #{request}"
147
+ end.join("\n") + "\n" +
122
148
  "Top #{@n_slow_requests} slow requests:\n" +
123
149
  " [time: method status <client>index url]\n" +
124
150
  top_slow_requests.collect do |request|
@@ -13,6 +13,7 @@
13
13
  # You should have received a copy of the GNU General Public License
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
+ require "facter"
16
17
  require "drnbench/client/http"
17
18
  require "drnbench/client/http-droonga"
18
19
  require "drnbench/request-response/result"
@@ -23,6 +24,10 @@ module Drnbench
23
24
  class Runner
24
25
  attr_reader :n_clients, :result
25
26
 
27
+ MESSAGE_EXIT = "exit"
28
+ MESSAGE_START = "start"
29
+ MESSAGE_COMPLETE = "complete"
30
+
26
31
  def initialize(n_clients, config)
27
32
  n_clients = 1 if n_clients.zero?
28
33
  @n_clients = n_clients
@@ -37,50 +42,185 @@ module Drnbench
37
42
  @result
38
43
  end
39
44
 
45
+ def pop_request
46
+ @requests.pop
47
+ end
48
+
49
+ def push_result(result)
50
+ @result << result
51
+ end
52
+
53
+ def empty?
54
+ @requests.empty?
55
+ end
56
+
40
57
  private
41
58
  def process_requests
59
+ @result = Result.new(:n_clients => @n_clients,
60
+ :duration => @config.duration,
61
+ :n_fast_requests => @config.n_fast_requests,
62
+ :n_slow_requests => @config.n_slow_requests)
63
+
64
+ setup_child_processes
65
+ initiate_child_processes
66
+ wait_for_given_duration
67
+ kill_child_processes
68
+
69
+ @result
70
+ end
71
+
72
+ def setup_child_processes
73
+ @child_processes = []
74
+ @total_n_clients = 0
75
+ n_processes.times.each do |index|
76
+ setup_child_process
77
+ end
78
+ end
79
+
80
+ def setup_child_process
81
+ n_clients = n_clients_per_process
82
+ if @total_n_clients + n_clients > @n_clients
83
+ n_clients = @n_clients - @total_n_clients
84
+ end
85
+ return if n_clients <= 0
86
+
87
+ # Prepare request queue for child process at first
88
+ # to reduce needless inter-process communications (IPC) while running!
42
89
  requests_queue = Queue.new
43
- @requests.each do |request|
90
+ @requests.slice!(0..n_requests_per_process).each do |request|
44
91
  requests_queue.push(request)
45
92
  end
46
93
 
47
- @result = Result.new(:n_clients => @n_clients,
48
- :duration => @config.duration,
49
- :n_slow_requests => @config.n_slow_requests)
94
+ parent_read, child_write = IO.pipe
95
+ child_read, parent_write = IO.pipe
96
+
97
+ pid = fork do
98
+
99
+ parent_write.close
100
+ parent_read.close
101
+ druby_uri = child_read.gets.chomp
102
+ @parent = DRbObject.new_with_uri(druby_uri)
103
+
104
+ @requests = requests_queue
105
+ @result = []
106
+
107
+ # Because continuous benchmark increases objects,
108
+ # GC painflly slows down the process.
109
+ GC.start
110
+ GC.disable
50
111
 
51
- client_params = {
52
- :requests => requests_queue,
53
- :result => @result,
112
+ clients = setup_clients(n_clients)
113
+
114
+ loop do
115
+ message = child_read.gets
116
+ if message and message.chomp == MESSAGE_EXIT
117
+ clients.each(&:stop)
118
+ # We also should reduce IPC for results.
119
+ @result.each do |result|
120
+ @parent.push_result(result)
121
+ end
122
+ child_write.puts(MESSAGE_COMPLETE)
123
+ child_write.close
124
+ exit!
125
+ end
126
+ sleep(3)
127
+ end
128
+ end
129
+ @child_processes << {
130
+ :pid => pid,
131
+ :input => parent_read,
132
+ :output => parent_write,
54
133
  }
55
- @clients = @n_clients.times.collect do |index|
56
- client = nil
134
+
135
+ requests_queue = nil
136
+
137
+ child_read.close
138
+ child_write.close
139
+ end
140
+
141
+ def setup_clients(count)
142
+ count.times.collect do |index|
57
143
  case @config.mode
58
144
  when :http
59
- client = HttpClient.new(client_params, @config)
145
+ client = HttpClient.new(:runner => self,
146
+ :config => @config)
60
147
  when :http_droonga
61
- client = HttpDroongaClient.new(client_params, @config)
148
+ client = HttpDroongaClient.new(:runner => self,
149
+ :config => @config)
62
150
  else
63
151
  raise ArgumentError.new("Unknown mode: #{@config.mode}")
64
152
  end
65
153
  client.run
66
154
  client
67
155
  end
156
+ end
157
+
158
+ def initiate_child_processes
159
+ DRb.start_service("druby://localhost:0", self)
160
+ @child_processes.each do |child|
161
+ child[:output].puts(DRb.uri)
162
+ end
163
+ end
68
164
 
165
+ ONE_MINUTE_IN_SECONDS = 60
166
+ ONE_HOUR_IN_SECONDS = ONE_MINUTE_IN_SECONDS * 60
167
+
168
+ def wait_for_given_duration
69
169
  start_time = Time.now
70
- while Time.now - start_time < @config.duration
170
+ last_message = ""
171
+ loop do
71
172
  sleep 1
72
- if requests_queue.empty?
73
- puts "WORNING: requests queue becomes empty! (#{Time.now - start_time} sec)"
74
- @result.duration = Time.now - start_time
75
- break
76
- end
173
+ elapsed_time = (Time.now - start_time).to_i
174
+ break if elapsed_time >= @config.duration
175
+
176
+ remaining_seconds = @config.duration - elapsed_time
177
+ remaining_hours = (remaining_seconds / ONE_HOUR_IN_SECONDS).floor
178
+ remaining_seconds -= remaining_hours * ONE_HOUR_IN_SECONDS
179
+ remaining_minutes = (remaining_seconds / ONE_MINUTE_IN_SECONDS).floor
180
+ remaining_seconds -= remaining_minutes * ONE_MINUTE_IN_SECONDS
181
+ remaining_time = sprintf("%02i:%02i:%02i", remaining_hours, remaining_minutes, remaining_seconds)
182
+ next_message = "#{remaining_time} remaining..."
183
+ printf("%s", "#{" " * last_message.size}\r")
184
+ printf("%s", "#{next_message}\r")
185
+ last_message = next_message
77
186
  end
187
+ end
188
+
189
+ def kill_child_processes
190
+ puts "Collecting results..."
78
191
 
79
- @clients.each do |client|
80
- client.stop
192
+ @child_processes.each do |child|
193
+ child[:output].puts(MESSAGE_EXIT)
81
194
  end
82
195
 
83
- @result
196
+ loop do
197
+ @child_processes = @child_processes.reject do |child|
198
+ message = child[:input].gets
199
+ if message and message.chomp == MESSAGE_COMPLETE
200
+ Process.detach(child[:pid])
201
+ true
202
+ else
203
+ false
204
+ end
205
+ end
206
+ break if @child_processes.empty?
207
+ end
208
+ end
209
+
210
+ def n_processes
211
+ [[@n_clients, 1].max, max_n_processes].min
212
+ end
213
+
214
+ def max_n_processes
215
+ Facter["processorcount"].value.to_i
216
+ end
217
+
218
+ def n_clients_per_process
219
+ (@n_clients.to_f / n_processes).ceil
220
+ end
221
+
222
+ def n_requests_per_process
223
+ (@requests.size.to_f / n_processes).round
84
224
  end
85
225
  end
86
226
  end
@@ -14,5 +14,5 @@
14
14
  # along with this program. If not, see <http://www.gnu.org/licenses/>.
15
15
 
16
16
  module Drnbench
17
- VERSION = "1.0.3"
17
+ VERSION = "1.0.4"
18
18
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: drnbench
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.0.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - YUKI Hiroshi
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2014-10-07 00:00:00.000000000 Z
12
+ date: 2014-11-28 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: json
@@ -53,6 +53,34 @@ dependencies:
53
53
  - - '>='
54
54
  - !ruby/object:Gem::Version
55
55
  version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: facter
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :runtime
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: sigdump
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
56
84
  - !ruby/object:Gem::Dependency
57
85
  name: bundler
58
86
  requirement: !ruby/object:Gem::Requirement