railsbench 0.9.2 → 0.9.8

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