drnbench 1.0.3 → 1.0.4

Sign up to get free protection for your applications and to get access to all the features.
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