railsbench 0.9.2 → 0.9.8

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.
Files changed (51) hide show
  1. data/CHANGELOG +1808 -451
  2. data/GCPATCH +73 -0
  3. data/INSTALL +5 -0
  4. data/Manifest.txt +23 -13
  5. data/PROBLEMS +0 -0
  6. data/README +23 -7
  7. data/Rakefile +1 -2
  8. data/bin/railsbench +7 -1
  9. data/config/benchmarking.rb +0 -0
  10. data/config/benchmarks.rb +3 -2
  11. data/config/benchmarks.yml +0 -0
  12. data/images/empty.png +0 -0
  13. data/images/minus.png +0 -0
  14. data/images/plus.png +0 -0
  15. data/install.rb +1 -1
  16. data/latest_changes.txt +18 -0
  17. data/lib/benchmark.rb +0 -0
  18. data/lib/railsbench/benchmark.rb +576 -0
  19. data/lib/railsbench/benchmark_specs.rb +63 -63
  20. data/lib/railsbench/gc_info.rb +38 -3
  21. data/lib/railsbench/perf_info.rb +1 -1
  22. data/lib/railsbench/perf_utils.rb +202 -179
  23. data/lib/railsbench/railsbenchmark.rb +213 -55
  24. data/lib/railsbench/version.rb +9 -9
  25. data/lib/railsbench/write_headers_only.rb +15 -15
  26. data/postinstall.rb +0 -0
  27. data/ruby185gc.patch +56 -29
  28. data/ruby186gc.patch +564 -0
  29. data/ruby19gc.patch +2425 -0
  30. data/script/convert_raw_data_files +49 -49
  31. data/script/generate_benchmarks +14 -4
  32. data/script/perf_bench +12 -8
  33. data/script/perf_comp +1 -1
  34. data/script/perf_comp_gc +9 -1
  35. data/script/perf_diff +2 -2
  36. data/script/perf_diff_gc +2 -2
  37. data/script/perf_html +1 -1
  38. data/script/perf_plot +192 -75
  39. data/script/perf_plot_gc +213 -74
  40. data/script/perf_prof +29 -10
  41. data/script/perf_run +2 -2
  42. data/script/perf_run_gc +2 -2
  43. data/script/perf_table +2 -2
  44. data/script/perf_tex +1 -1
  45. data/script/perf_times +6 -6
  46. data/script/perf_times_gc +14 -2
  47. data/script/run_urls +16 -10
  48. data/setup.rb +0 -0
  49. data/test/railsbench_test.rb +0 -0
  50. data/test/test_helper.rb +2 -0
  51. metadata +77 -55
@@ -6,7 +6,7 @@ class RailsBenchmark
6
6
  attr_accessor :http_host, :remote_addr, :server_port
7
7
  attr_accessor :relative_url_root
8
8
  attr_accessor :perform_caching, :cache_template_loading
9
- attr_accessor :session_data
9
+ attr_accessor :session_data, :session_key, :cookie_data
10
10
 
11
11
  def error_exit(msg)
12
12
  STDERR.puts msg
@@ -18,7 +18,13 @@ class RailsBenchmark
18
18
  end
19
19
 
20
20
  def relative_url_root=(value)
21
- ActionController::AbstractRequest.relative_url_root = value
21
+ if ActionController::Base.respond_to?(:relative_url_root=)
22
+ # rails 2.3
23
+ ActionController::Base.relative_url_root = value
24
+ else
25
+ # earlier railses
26
+ ActionController::AbstractRequest.relative_url_root = value
27
+ end
22
28
  @relative_url_root = value
23
29
  end
24
30
 
@@ -35,24 +41,53 @@ class RailsBenchmark
35
41
  @server_port = options[:server_port] || '80'
36
42
 
37
43
  @session_data = options[:session_data] || {}
44
+ @session_key = options[:session_key] || '_session_id'
38
45
 
39
46
  ENV['RAILS_ENV'] = 'benchmarking'
40
47
 
41
- require ENV['RAILS_ROOT'] + "/config/environment"
42
- require 'dispatcher' # make edge rails happy
48
+ begin
49
+ require ENV['RAILS_ROOT'] + "/config/environment"
50
+ require 'dispatcher' # make edge rails happy
51
+
52
+ if Rails::VERSION::STRING >= "2.3"
53
+ @rack_middleware = true
54
+ require 'cgi/session'
55
+ CGI.class_eval <<-"end_eval"
56
+ def env_table
57
+ @env_table ||= ENV.to_hash
58
+ end
59
+ end_eval
60
+ else
61
+ @rack_middleware = false
62
+ end
43
63
 
44
- # we don't want local error template output, which crashes anyway
64
+ rescue => e
65
+ $stderr.puts "failed to load application environment"
66
+ e.backtrace.each{|line| $stderr.puts line}
67
+ $stderr.puts "benchmarking aborted"
68
+ exit(-1)
69
+ end
70
+
71
+ # we don't want local error template output, which crashes anyway, when run under railsbench
45
72
  ActionController::Rescue.class_eval "def local_request?; false; end"
46
73
 
47
- # make sure an error code gets returned for 1.1.6
74
+ # print backtrace and exit if action execution raises an exception
48
75
  ActionController::Rescue.class_eval <<-"end_eval"
49
76
  def rescue_action_in_public(exception)
50
- case exception
51
- when ActionController::RoutingError, ActionController::UnknownAction
52
- render_text(IO.read(File.join(RAILS_ROOT, 'public', '404.html')), "404 Not Found")
53
- else
54
- render_text(IO.read(File.join(RAILS_ROOT, 'public', '500.html')), "500 Internal Error")
55
- end
77
+ $stderr.puts "benchmarking aborted due to application error: " + exception.message
78
+ exception.backtrace.each{|line| $stderr.puts line}
79
+ $stderr.print "clearing database connections ..."
80
+ ActiveRecord::Base.send :clear_all_cached_connections! if ActiveRecord::Base.respond_to?(:clear_all_cached_connections)
81
+ ActiveRecord::Base.clear_all_connections! if ActiveRecord::Base.respond_to?(:clear_all_connections)
82
+ $stderr.puts
83
+ exit!(-1)
84
+ end
85
+ end_eval
86
+
87
+ # override rails ActiveRecord::Base#inspect to make profiles more readable
88
+ ActiveRecord::Base.class_eval <<-"end_eval"
89
+ def self.inspect
90
+ super
56
91
  end
57
92
  end_eval
58
93
 
@@ -68,16 +103,30 @@ class RailsBenchmark
68
103
  exit
69
104
  end
70
105
 
71
- log_level = options[:log]
72
- log_level = Logger::DEBUG if ARGV.include?('-log')
73
- ARGV.each{|arg| arg =~ /-log=([a-zA-Z]*)/ && (log_level = eval("Logger::#{$1.upcase}")) }
106
+ logger_module = Logger
107
+ if defined?(Log4r) && RAILS_DEFAULT_LOGGER.is_a?(Log4r::Logger)
108
+ logger_module = Logger
109
+ end
110
+ default_log_level = logger_module.const_get("ERROR")
111
+ log_level = options[:log] || default_log_level
112
+ ARGV.each do |arg|
113
+ case arg
114
+ when '-log'
115
+ log_level = default_log_level
116
+ when '-log=(nil|none)'
117
+ log_level = nil
118
+ when /-log=([a-zA-Z]*)/
119
+ log_level = logger_module.const_get($1.upcase) rescue default_log_level
120
+ end
121
+ end
74
122
 
75
123
  if log_level
76
124
  RAILS_DEFAULT_LOGGER.level = log_level
77
- #ActiveRecord::Base.logger.level = log_level
78
- #ActionController::Base.logger.level = log_level
79
- #ActionMailer::Base.logger = level = log_level if defined?(ActionMailer)
125
+ ActiveRecord::Base.logger.level = log_level
126
+ ActionController::Base.logger.level = log_level
127
+ ActionMailer::Base.logger = level = log_level if defined?(ActionMailer)
80
128
  else
129
+ RAILS_DEFAULT_LOGGER.level = logger_module.const_get "FATAL"
81
130
  ActiveRecord::Base.logger = nil
82
131
  ActionController::Base.logger = nil
83
132
  ActionMailer::Base.logger = nil if defined?(ActionMailer)
@@ -90,10 +139,12 @@ class RailsBenchmark
90
139
  ActionController::Base.perform_caching = true if ARGV.include?('-cache')
91
140
  end
92
141
 
93
- if options.has_key?(:cache_template_loading)
94
- ActionView::Base.cache_template_loading = options[:cache_template_loading]
95
- else
96
- ActionView::Base.cache_template_loading = true
142
+ if ActionView::Base.respond_to?(:cache_template_loading)
143
+ if options.has_key?(:cache_template_loading)
144
+ ActionView::Base.cache_template_loading = options[:cache_template_loading]
145
+ else
146
+ ActionView::Base.cache_template_loading = true
147
+ end
97
148
  end
98
149
 
99
150
  self.relative_url_root = options[:relative_url_root] || ''
@@ -107,25 +158,58 @@ class RailsBenchmark
107
158
  end
108
159
 
109
160
  def establish_test_session
110
- session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.stringify_keys
111
- session_options = session_options.merge('new_session' => true)
112
- @session = CGI::Session.new(Hash.new, session_options)
113
- @session_data.each{ |k,v| @session[k] = v }
114
- @session.update
115
- @session_id = @session.session_id
161
+ if @rack_middleware
162
+ session_options = ActionController::Base.session_options
163
+ @session_id = ActiveSupport::SecureRandom.hex(16)
164
+ do_not_do_much = lambda do |env|
165
+ env["rack.session"] = @session_data
166
+ env["rack.session.options"] = {:id => @session_id}
167
+ [200, {}, ""]
168
+ end
169
+ @session_store = ActionController::Base.session_store.new(do_not_do_much, session_options)
170
+ @session_store.call({})
171
+ else
172
+ session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.stringify_keys
173
+ session_options = session_options.merge('new_session' => true)
174
+ @session = CGI::Session.new(Hash.new, session_options)
175
+ @session_data.each{ |k,v| @session[k] = v }
176
+ @session.update
177
+ @session_id = @session.session_id
178
+ end
116
179
  end
117
180
 
118
181
  def update_test_session_data(session_data)
119
- dbman = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
120
- old_session_data = dbman.new(@session).restore
121
- new_session_data = old_session_data.merge(session_data || {})
122
- new_session_data.each{ |k,v| @session[k] = v }
123
- @session.update
182
+ if @rack_middleware
183
+ session_options = ActionController::Base.session_options
184
+ merge_url_specific_session_data = lambda do |env|
185
+ old_session_data = env["rack.session"]
186
+ # $stderr.puts "data in old session: #{old_session_data.inspect}"
187
+ new_session_data = old_session_data.merge(session_data || {})
188
+ # $stderr.puts "data in new session: #{new_session_data.inspect}"
189
+ env["rack.session"] = new_session_data
190
+ [200, {}, ""]
191
+ end
192
+ @session_store.instance_eval { @app = merge_url_specific_session_data }
193
+ env = {}
194
+ env["HTTP_COOKIE"] = cookie
195
+ # debugger
196
+ @session_store.call(env)
197
+ else
198
+ dbman = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager]
199
+ old_session_data = dbman.new(@session).restore
200
+ # $stderr.puts old_session_data.inspect
201
+ new_session_data = old_session_data.merge(session_data || {})
202
+ new_session_data.each{ |k,v| @session[k] = v }
203
+ @session.update
204
+ end
124
205
  end
125
206
 
126
207
  def delete_test_session
127
- @session.delete
128
- @session = nil
208
+ # no way to delete a session by going through the session adpater in rails 2.3
209
+ if @session
210
+ @session.delete
211
+ @session = nil
212
+ end
129
213
  end
130
214
 
131
215
  # can be redefined in subclasses to clean out test sessions
@@ -133,6 +217,7 @@ class RailsBenchmark
133
217
  end
134
218
 
135
219
  def setup_test_urls(name)
220
+ @benchmark = name
136
221
  @urls = BenchmarkSpec.load(name)
137
222
  end
138
223
 
@@ -143,22 +228,34 @@ class RailsBenchmark
143
228
  end
144
229
 
145
230
  def setup_request_env(entry)
231
+ # $stderr.puts entry.inspect
146
232
  ENV['REQUEST_URI'] = @relative_url_root + entry.uri
147
- ENV['RAW_POST_DATA'] = nil
148
- ENV['QUERY_STRING'] = nil
233
+ ENV.delete 'RAW_POST_DATA'
234
+ ENV.delete 'QUERY_STRING'
149
235
  case ENV['REQUEST_METHOD'] = (entry.method || 'get').upcase
150
236
  when 'GET'
151
- query_data = escape_data(entry.query_string || '')
237
+ query_data = entry.query_string || ''
238
+ query_data = escape_data(query_data) unless entry.raw_data
152
239
  ENV['QUERY_STRING'] = query_data
153
240
  when 'POST'
154
- query_data = escape_data(entry.post_data || '')
241
+ query_data = entry.post_data || ''
242
+ query_data = escape_data(query_data) unless entry.raw_data
155
243
  ENV['RAW_POST_DATA'] = query_data
156
244
  end
157
245
  ENV['CONTENT_LENGTH'] = query_data.length.to_s
158
- ENV['HTTP_COOKIE'] = entry.new_session ? '' : "_session_id=#{@session_id}"
246
+ ENV['HTTP_COOKIE'] = entry.new_session ? '' : cookie
247
+ ENV['HTTP_X_REQUESTED_WITH'] = 'XMLHttpRequest' if entry.xhr
248
+ # $stderr.puts entry.session_data.inspect
159
249
  update_test_session_data(entry.session_data) unless entry.new_session
160
250
  end
161
251
 
252
+ def before_dispatch_hook(entry)
253
+ end
254
+
255
+ def cookie
256
+ "#{@session_key}=#{@session_id}#{cookie_data}"
257
+ end
258
+
162
259
  def escape_data(str)
163
260
  str.split('&').map{|e| e.split('=').map{|e| CGI::escape e}.join('=')}.join('&')
164
261
  end
@@ -169,7 +266,7 @@ class RailsBenchmark
169
266
  @urls.each do |entry|
170
267
  error_exit "No uri given for benchmark entry: #{entry.inspect}" unless entry.uri
171
268
  setup_request_env(entry)
172
- Dispatcher.dispatch
269
+ Dispatcher.dispatch(CGI.new)
173
270
  end
174
271
  end
175
272
 
@@ -183,7 +280,7 @@ class RailsBenchmark
183
280
  svl = SvlRubyPV.new
184
281
  elsif ARGV.include?('-svlMV')
185
282
  require 'svlRubyMV'
186
- svl = SvlRubyMV.new
283
+ svl = SvlRubyMV
187
284
  end
188
285
  rescue LoadError
189
286
  # SVL dll not available, do nothing
@@ -194,12 +291,20 @@ class RailsBenchmark
194
291
  ARGV.each{|arg| ruby_prof=$1 if arg =~ /-ruby_prof=([^ ]*)/ }
195
292
  begin
196
293
  if ruby_prof
197
- # redirect stderr
294
+ # redirect stderr (TODO: I can't remember why we don't do this later)
198
295
  if benchmark_file = ENV['RAILS_BENCHMARK_FILE']
199
296
  $stderr = File.open(benchmark_file, "w")
200
297
  end
201
298
  require 'ruby-prof'
202
- RubyProf.clock_mode = RubyProf::WALL_TIME
299
+ measure_mode = "WALL_TIME"
300
+ ARGV.each{|arg| measure_mode=$1.upcase if arg =~ /-measure_mode=([^ ]*)/ }
301
+ if %w(PROCESS_TIME WALL_TIME CPU_TIME ALLOCATIONS MEMORY).include?(measure_mode)
302
+ RubyProf.measure_mode = RubyProf.const_get measure_mode
303
+ else
304
+ $stderr = STDERR
305
+ $stderr.puts "unsupported ruby_prof measure mode: #{measure_mode}"
306
+ exit(-1)
307
+ end
203
308
  RubyProf.start
204
309
  end
205
310
  rescue LoadError
@@ -230,14 +335,61 @@ class RailsBenchmark
230
335
  GC.log "number of requests processed: #{@urls.size * iterations}"
231
336
  end
232
337
 
338
+ # try to detect Ruby interpreter memory leaks (OS X)
339
+ if ARGV.include?('-leaks')
340
+ leaks_log = "#{ENV['RAILS_PERF_DATA']}/leaks.log"
341
+ leaks_command = "leaks -nocontext #{$$} >#{leaks_log}"
342
+ ENV.delete 'MallocStackLogging'
343
+ # $stderr.puts "executing '#{leaks_command}'"
344
+ raise "could not execute leaks command" unless system(leaks_command)
345
+ mallocs, leaks = *`head -n 2 #{leaks_log}`.split("\n").map{|l| l.gsub(/Process #{$$}: /, '')}
346
+ if mem_leaks = (leaks =~ /(\d+) leaks for (\d+) total leaked bytes/)
347
+ $stderr.puts "\n!!!!! memory leaks detected !!!!! (#{leaks_log})"
348
+ $stderr.puts "=" * leaks.length
349
+ end
350
+ if gc_stats
351
+ GC.log mallocs
352
+ GC.log leaks
353
+ end
354
+ $stderr.puts mallocs, leaks
355
+ $stderr.puts "=" * leaks.length if mem_leaks
356
+ end
357
+
233
358
  # stop data collection if necessary
234
359
  svl.stopDataCollection if svl
235
360
 
236
361
  if defined? RubyProf
362
+ GC.disable #ruby-pof 0.7.x crash workaround
237
363
  result = RubyProf.stop
238
- # Print a flat profile to text
239
- printer = RubyProf::GraphHtmlPrinter.new(result)
240
- printer.print($stderr, ruby_prof.to_f)
364
+ GC.enable #ruby-pof 0.7.x crash workaround
365
+ min_percent = ruby_prof.split('/')[0].to_f rescue 0.1
366
+ threshold = ruby_prof.split('/')[1].to_f rescue 1.0
367
+ profile_type = nil
368
+ ARGV.each{|arg| profile_type=$1 if arg =~ /-profile_type=([^ ]*)/ }
369
+ profile_type ||= 'stack'
370
+ printer =
371
+ case profile_type
372
+ when 'stack' then RubyProf::CallStackPrinter
373
+ when 'grind' then RubyProf::CallTreePrinter
374
+ when 'flat' then RubyProf::FlatPrinter
375
+ when 'graph' then RubyProf::GraphHtmlPrinter
376
+ when 'multi' then RubyProf::MultiPrinter
377
+ else raise "unknown profile type: #{profile_type}"
378
+ end.new(result)
379
+ if profile_type == 'multi'
380
+ raise "you must specify a benchmark file when using multi printer" unless $stderr.is_a?(File)
381
+ $stderr.close
382
+ $stderr = STDERR
383
+ file_name = ENV['RAILS_BENCHMARK_FILE']
384
+ profile_name = File.basename(file_name).sub('.html','').sub(".#{profile_type}",'')
385
+ printer.print(:path => File.dirname(file_name),
386
+ :profile => profile_name,
387
+ :min_percent => min_percent, :threshold => threshold,
388
+ :title => "call tree/graph for benchmark #{@benchmark}")
389
+ else
390
+ printer.print($stderr, :min_percent => min_percent, :threshold => threshold,
391
+ :title => "call tree for benchmark #{@benchmark}")
392
+ end
241
393
  end
242
394
 
243
395
  delete_test_session
@@ -273,7 +425,8 @@ class RailsBenchmark
273
425
  GC.enable; GC.start; GC.disable
274
426
  request_count = 0
275
427
  n.times do
276
- Dispatcher.dispatch
428
+ before_dispatch_hook(entry)
429
+ Dispatcher.dispatch(CGI.new)
277
430
  if (request_count += 1) == gc_frequency
278
431
  GC.enable; GC.start; GC.disable
279
432
  request_count = 0
@@ -286,7 +439,8 @@ class RailsBenchmark
286
439
  urls.each do |entry|
287
440
  setup_request_env(entry)
288
441
  n.times do
289
- Dispatcher.dispatch
442
+ before_dispatch_hook(entry)
443
+ Dispatcher.dispatch(CGI.new)
290
444
  end
291
445
  end
292
446
  end
@@ -302,7 +456,8 @@ class RailsBenchmark
302
456
  GC.enable; GC.start; GC.disable
303
457
  GC.enable_stats if gc_stats
304
458
  n.times do
305
- Dispatcher.dispatch
459
+ before_dispatch_hook(entry)
460
+ Dispatcher.dispatch(CGI.new)
306
461
  if (request_count += 1) == gc_freq
307
462
  GC.enable; GC.start; GC.disable
308
463
  request_count = 0
@@ -327,7 +482,8 @@ class RailsBenchmark
327
482
  GC.enable_stats if gc_stats
328
483
  test.report(entry.name) do
329
484
  n.times do
330
- Dispatcher.dispatch
485
+ before_dispatch_hook(entry)
486
+ Dispatcher.dispatch(CGI.new)
331
487
  end
332
488
  end
333
489
  end
@@ -348,7 +504,8 @@ class RailsBenchmark
348
504
  n.times do
349
505
  urls.each do |entry|
350
506
  setup_request_env(entry)
351
- Dispatcher.dispatch
507
+ before_dispatch_hook(entry)
508
+ Dispatcher.dispatch(CGI.new)
352
509
  end
353
510
  end
354
511
  end
@@ -370,7 +527,8 @@ class RailsBenchmark
370
527
  n.times do
371
528
  urls.each do |entry|
372
529
  setup_request_env(entry)
373
- Dispatcher.dispatch
530
+ before_dispatch_hook(entry)
531
+ Dispatcher.dispatch(CGI.new)
374
532
  if (request_count += 1) == gc_frequency
375
533
  GC.enable; GC.start; GC.disable
376
534
  request_count = 0
@@ -391,7 +549,7 @@ class RailsBenchmarkWithActiveRecordStore < RailsBenchmark
391
549
 
392
550
  def initialize(options={})
393
551
  super(options)
394
- @session_class = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager].session_class rescue CGI::Session::ActiveRecordStore
552
+ @session_class = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager].session_class rescue CGI::Session::ActiveRecordStore rescue ActiveRecord::SessionStore
395
553
  end
396
554
 
397
555
  def delete_new_test_sessions
@@ -403,7 +561,7 @@ end
403
561
 
404
562
  __END__
405
563
 
406
- # Copyright (C) 2005, 2006, 2007 Stefan Kaes
564
+ # Copyright (C) 2005-2008 Stefan Kaes
407
565
  #
408
566
  # This program is free software; you can redistribute it and/or modify
409
567
  # it under the terms of the GNU General Public License as published by
@@ -1,9 +1,9 @@
1
- module Railsbench #:nodoc:
2
- module VERSION #:nodoc:
3
- MAJOR = 0
4
- MINOR = 9
5
- TINY = 2
6
-
7
- STRING = [MAJOR, MINOR, TINY].join('.')
8
- end
9
- end
1
+ module Railsbench #:nodoc:
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 9
5
+ TINY = 8
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end