railsbench 0.8.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.
- data/BUGS +3 -0
- data/CHANGELOG +0 -0
- data/INSTALL +55 -0
- data/LICENSE +222 -0
- data/Manifest.txt +38 -0
- data/README +254 -0
- data/Rakefile +51 -0
- data/bin/railsbench +50 -0
- data/config/benchmarking.rb +8 -0
- data/config/benchmarks.rb +20 -0
- data/config/benchmarks.yml +49 -0
- data/install.rb +60 -0
- data/lib/benchmark.rb +576 -0
- data/lib/railsbench/gc_info.rb +123 -0
- data/lib/railsbench/perf_info.rb +145 -0
- data/lib/railsbench/perf_utils.rb +65 -0
- data/lib/railsbench/railsbenchmark.rb +397 -0
- data/lib/railsbench/version.rb +9 -0
- data/lib/railsbench/write_headers_only.rb +15 -0
- data/ruby184gc.patch +516 -0
- data/ruby185gc.patch +535 -0
- data/script/perf_bench +76 -0
- data/script/perf_comp +155 -0
- data/script/perf_comp_gc +109 -0
- data/script/perf_diff +48 -0
- data/script/perf_diff_gc +89 -0
- data/script/perf_html +82 -0
- data/script/perf_loop +38 -0
- data/script/perf_plot +94 -0
- data/script/perf_plot_gc +111 -0
- data/script/perf_prof +51 -0
- data/script/perf_run +34 -0
- data/script/perf_run_gc +46 -0
- data/script/perf_tex +62 -0
- data/script/perf_times +70 -0
- data/script/perf_times_gc +86 -0
- data/script/run_urls +46 -0
- data/setup.rb +1585 -0
- data/test/railsbench_test.rb +11 -0
- metadata +91 -0
@@ -0,0 +1,123 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/perf_utils.rb")
|
2
|
+
require "set"
|
3
|
+
|
4
|
+
# Entry Format:
|
5
|
+
#
|
6
|
+
# Garbage collection started
|
7
|
+
# objects processed: 0223696
|
8
|
+
# live objects : 0192126
|
9
|
+
# freelist objects : 0000000
|
10
|
+
# freed objects : 0031570
|
11
|
+
# kept 0000370 / freed 0000609 objects of type OBJECT
|
12
|
+
# kept 0001071 / freed 0000062 objects of type CLASS
|
13
|
+
# kept 0000243 / freed 0000061 objects of type ICLASS
|
14
|
+
# kept 0000041 / freed 0000061 objects of type FLOAT
|
15
|
+
# kept 0013974 / freed 0015432 objects of type STRING
|
16
|
+
# kept 0000651 / freed 0000002 objects of type REGEXP
|
17
|
+
# kept 0000617 / freed 0009948 objects of type ARRAY
|
18
|
+
# kept 0000646 / freed 0001398 objects of type HASH
|
19
|
+
# kept 0000004 / freed 0000121 objects of type BIGNUM
|
20
|
+
# kept 0000006 / freed 0000005 objects of type FILE
|
21
|
+
# kept 0000400 / freed 0000253 objects of type DATA
|
22
|
+
# kept 0000001 / freed 0000093 objects of type MATCH
|
23
|
+
# kept 0000067 / freed 0000136 objects of type VARMAP
|
24
|
+
# kept 0000167 / freed 0000939 objects of type SCOPE
|
25
|
+
# kept 0173634 / freed 0002389 objects of type NODE
|
26
|
+
# GC time: 47 msec
|
27
|
+
|
28
|
+
GCAttributes = [:processed, :live, :freelist, :freed, :time]
|
29
|
+
GCSummaries = [:min, :max, :mean, :stddev, :stddev_percentage]
|
30
|
+
|
31
|
+
class GCLogEntry
|
32
|
+
attr_accessor *GCAttributes
|
33
|
+
attr_accessor :live_objects, :freed_objects
|
34
|
+
def initialize
|
35
|
+
@live_objects = {}
|
36
|
+
@freed_objects = {}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class GCInfo
|
41
|
+
|
42
|
+
attr_reader(*GCAttributes)
|
43
|
+
attr_reader :entries, :num_requests, :collections, :garbage_produced, :time_total, :topology
|
44
|
+
attr_reader :live_objects, :freed_objects, :object_types
|
45
|
+
|
46
|
+
GCAttributes.each do |attr|
|
47
|
+
GCSummaries.each do |method|
|
48
|
+
attr_reader "#{attr}_#{method}"
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def initialize(file)
|
53
|
+
@entries = []
|
54
|
+
@num_requests = 0
|
55
|
+
@topology = []
|
56
|
+
@object_types = Set.new
|
57
|
+
|
58
|
+
file.each_line do |line|
|
59
|
+
case line
|
60
|
+
when /^Garbage collection started$/
|
61
|
+
@entries << GCLogEntry.new
|
62
|
+
when /^objects processed\s*:\s*(\d+)$/
|
63
|
+
@entries.last.processed = $1.to_i
|
64
|
+
when /^live objects\s*:\s*(\d+)$/
|
65
|
+
@entries.last.live = $1.to_i
|
66
|
+
when /^freelist objects\s*:\s*(\d+)$/
|
67
|
+
@entries.last.freelist = $1.to_i
|
68
|
+
when /^freed objects\s*:\s*(\d+)$/
|
69
|
+
@entries.last.freed = $1.to_i
|
70
|
+
when /^GC time\s*:\s*(\d+)\s*msec$/
|
71
|
+
@entries.last.time = $1.to_i
|
72
|
+
when /^number of requests processed: (\d+)$/
|
73
|
+
@num_requests = $1.to_i
|
74
|
+
when /^HEAP\[\s*(\d+)\]: size=\s*(\d+)$/
|
75
|
+
@topology << $2.to_i
|
76
|
+
when /^kept (\d+) \/ freed (\d+) objects of type ([a-zA-Z]+)/
|
77
|
+
@object_types.add($3)
|
78
|
+
@entries.last.live_objects[$3] = $1.to_i
|
79
|
+
@entries.last.freed_objects[$3] = $2.to_i
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
@time_total = @entries.map{|e| e.time}.sum
|
84
|
+
@collections = @entries.length
|
85
|
+
@garbage_produced = @entries.map{|e| e.freed}.sum
|
86
|
+
@live_objects = @entries.map{|e| e.live_objects}
|
87
|
+
@freed_objects = @entries.map{|e| e.freed_objects}
|
88
|
+
|
89
|
+
GCAttributes.each do |attr|
|
90
|
+
a = @entries.map{|e| e.send attr}
|
91
|
+
a.pop
|
92
|
+
|
93
|
+
[:min, :max, :mean].each do |method|
|
94
|
+
instance_variable_set "@#{attr}_#{method}", (a.send method)
|
95
|
+
end
|
96
|
+
mean = instance_variable_get "@#{attr}_mean"
|
97
|
+
stddev = instance_variable_set "@#{attr}_stddev", (a.send :stddev, mean)
|
98
|
+
instance_variable_set "@#{attr}_stddev_percentage", stddev_percentage(stddev, mean)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
|
104
|
+
|
105
|
+
### Local Variables: ***
|
106
|
+
### mode:ruby ***
|
107
|
+
### End: ***
|
108
|
+
|
109
|
+
# Copyright (C) 2006 Stefan Kaes
|
110
|
+
#
|
111
|
+
# This program is free software; you can redistribute it and/or modify
|
112
|
+
# it under the terms of the GNU General Public License as published by
|
113
|
+
# the Free Software Foundation; either version 2 of the License, or
|
114
|
+
# (at your option) any later version.
|
115
|
+
#
|
116
|
+
# This program is distributed in the hope that it will be useful,
|
117
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
118
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
119
|
+
# GNU General Public License for more details.
|
120
|
+
#
|
121
|
+
# You should have received a copy of the GNU General Public License
|
122
|
+
# along with this program; if not, write to the Free Software
|
123
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
@@ -0,0 +1,145 @@
|
|
1
|
+
require File.expand_path(File.dirname(__FILE__) + "/perf_utils.rb")
|
2
|
+
|
3
|
+
# example of raw performance data
|
4
|
+
|
5
|
+
# /home/skaes/railsbench/script/perf_bench 100 -bm=all -mysql_session -patched_gc -links -OT
|
6
|
+
# user system total real
|
7
|
+
# loading environment 0.954000 1.938000 2.892000 ( 2.890000)
|
8
|
+
# /empty/index 0.093000 0.000000 0.093000 ( 0.172000)
|
9
|
+
# /welcome/index 0.156000 0.000000 0.156000 ( 0.172000)
|
10
|
+
# /rezept/index 0.125000 0.015000 0.140000 ( 0.203000)
|
11
|
+
# /rezept/myknzlpzl 0.125000 0.000000 0.125000 ( 0.203000)
|
12
|
+
# /rezept/show/413 0.406000 0.094000 0.500000 ( 0.594000)
|
13
|
+
# /rezept/cat/Hauptspeise 0.547000 0.094000 0.641000 ( 0.688000)
|
14
|
+
# /rezept/cat/Hauptspeise?page=5 0.531000 0.047000 0.578000 ( 0.688000)
|
15
|
+
# /rezept/letter/G 0.422000 0.078000 0.500000 ( 0.609000)
|
16
|
+
# GC.collections=0, GC.time=0.0
|
17
|
+
# user system total real
|
18
|
+
# loading environment 0.813000 2.078000 2.891000 ( 2.890000)
|
19
|
+
# /empty/index 0.125000 0.016000 0.141000 ( 0.157000)
|
20
|
+
# /welcome/index 0.109000 0.000000 0.109000 ( 0.187000)
|
21
|
+
# /rezept/index 0.110000 0.031000 0.141000 ( 0.219000)
|
22
|
+
# /rezept/myknzlpzl 0.109000 0.016000 0.125000 ( 0.219000)
|
23
|
+
# /rezept/show/413 0.422000 0.078000 0.500000 ( 0.625000)
|
24
|
+
# /rezept/cat/Hauptspeise 0.437000 0.125000 0.562000 ( 0.656000)
|
25
|
+
# /rezept/cat/Hauptspeise?page=5 0.453000 0.125000 0.578000 ( 0.688000)
|
26
|
+
# /rezept/letter/G 0.438000 0.000000 0.438000 ( 0.594000)
|
27
|
+
# GC.collections=0, GC.time=0.0
|
28
|
+
# user system total real
|
29
|
+
# loading environment 0.938000 1.968000 2.906000 ( 2.906000)
|
30
|
+
# /empty/index 0.109000 0.000000 0.109000 ( 0.172000)
|
31
|
+
# /welcome/index 0.094000 0.031000 0.125000 ( 0.171000)
|
32
|
+
# /rezept/index 0.110000 0.047000 0.157000 ( 0.219000)
|
33
|
+
# /rezept/myknzlpzl 0.140000 0.016000 0.156000 ( 0.203000)
|
34
|
+
# /rezept/show/413 0.422000 0.047000 0.469000 ( 0.593000)
|
35
|
+
# /rezept/cat/Hauptspeise 0.515000 0.015000 0.530000 ( 0.672000)
|
36
|
+
# /rezept/cat/Hauptspeise?page=5 0.484000 0.063000 0.547000 ( 0.672000)
|
37
|
+
# /rezept/letter/G 0.453000 0.015000 0.468000 ( 0.610000)
|
38
|
+
# GC.collections=0, GC.time=0.0
|
39
|
+
|
40
|
+
|
41
|
+
PerfAttributes = [:gc_calls, :gc_time, :load_time, :total_time]
|
42
|
+
PerfSummaries = [:min, :max, :mean, :stddev, :stddev_percentage]
|
43
|
+
|
44
|
+
class PerfEntry
|
45
|
+
attr_accessor *PerfAttributes
|
46
|
+
attr_accessor :keys, :timings
|
47
|
+
def initialize
|
48
|
+
@keys = []
|
49
|
+
@timings = {}
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
class PerfInfo
|
54
|
+
|
55
|
+
attr_reader :options, :iterations, :keys
|
56
|
+
attr_reader :entries, :runs, :request_count, :requests_per_key
|
57
|
+
|
58
|
+
def gc_stats?
|
59
|
+
@gc_stats
|
60
|
+
end
|
61
|
+
|
62
|
+
PerfSummaries.each do |method|
|
63
|
+
PerfAttributes.each do |attr|
|
64
|
+
attr_reader "#{attr}_#{method}"
|
65
|
+
end
|
66
|
+
class_eval "def timings_#{method}(key); @timings[:#{method}][key]; end"
|
67
|
+
end
|
68
|
+
|
69
|
+
def initialize(file)
|
70
|
+
@entries = []
|
71
|
+
file.each_line do |line|
|
72
|
+
case line
|
73
|
+
when /^.*perf_([a-zA-Z.]+)\s+(\d+)\s+(.*)$/
|
74
|
+
@iterations = $2.to_i
|
75
|
+
@options = $3
|
76
|
+
when /\s+user\s+system\s+total\s+real/
|
77
|
+
@entries << PerfEntry.new
|
78
|
+
when /^(.*)\s+([\d\.]+)\s+([\d\.]+)\s+([\d\.]+)\s+\(\s*([\d\.]+)\s*\)$/
|
79
|
+
key, time = $1.strip, $5.to_f
|
80
|
+
if key == "loading environment"
|
81
|
+
@entries.last.load_time = time
|
82
|
+
else
|
83
|
+
@entries.last.keys << key
|
84
|
+
@entries.last.timings[key] = time
|
85
|
+
end
|
86
|
+
when /^GC.collections=(\d+), GC.time=([\d\.]+)$/
|
87
|
+
@entries.last.gc_calls, @entries.last.gc_time = [$1.to_i,$2.to_f]
|
88
|
+
@gc_stats = true
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
@entries.each{ |e| e.total_time = e.timings.values.sum }
|
93
|
+
@keys = @entries.first.keys
|
94
|
+
@runs = @entries.length
|
95
|
+
if @keys.length == 1 && @keys[0] =~ /\((\d+) urls\)$/
|
96
|
+
@requests_per_key = $1.to_i
|
97
|
+
else
|
98
|
+
@requests_per_key = 1
|
99
|
+
end
|
100
|
+
@request_count = @iterations * @keys.length * @requests_per_key
|
101
|
+
@timings = PerfSummaries.inject({}){ |hash, method| hash[method] = Hash.new; hash }
|
102
|
+
|
103
|
+
@keys.each do |k|
|
104
|
+
a = @entries.map{|e| e.timings[k]}
|
105
|
+
[:min, :max, :mean].each do |method|
|
106
|
+
@timings[method][k] = a.send(method)
|
107
|
+
end
|
108
|
+
mean = @timings[:mean][k]
|
109
|
+
stddev = @timings[:stddev][k] = a.send(:stddev, mean)
|
110
|
+
@timings[:stddev_percentage][k] = stddev_percentage(stddev, mean)
|
111
|
+
end
|
112
|
+
|
113
|
+
PerfAttributes.each do |attr|
|
114
|
+
a = @entries.map{|e| e.send attr}
|
115
|
+
[:min, :max, :mean].each do |method|
|
116
|
+
instance_variable_set "@#{attr}_#{method}", (a.send method)
|
117
|
+
end
|
118
|
+
mean = instance_variable_get "@#{attr}_mean"
|
119
|
+
stddev = instance_variable_set "@#{attr}_stddev", (a.send :stddev, mean)
|
120
|
+
instance_variable_set "@#{attr}_stddev_percentage", stddev_percentage(stddev, mean)
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
|
127
|
+
### Local Variables: ***
|
128
|
+
### mode:ruby ***
|
129
|
+
### End: ***
|
130
|
+
|
131
|
+
# Copyright (C) 2006 Stefan Kaes
|
132
|
+
#
|
133
|
+
# This program is free software; you can redistribute it and/or modify
|
134
|
+
# it under the terms of the GNU General Public License as published by
|
135
|
+
# the Free Software Foundation; either version 2 of the License, or
|
136
|
+
# (at your option) any later version.
|
137
|
+
#
|
138
|
+
# This program is distributed in the hope that it will be useful,
|
139
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
140
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
141
|
+
# GNU General Public License for more details.
|
142
|
+
#
|
143
|
+
# You should have received a copy of the GNU General Public License
|
144
|
+
# along with this program; if not, write to the Free Software
|
145
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# some utility methods
|
2
|
+
|
3
|
+
class Array
|
4
|
+
def index_map
|
5
|
+
res = {}
|
6
|
+
each_with_index{|element, index| res[index] = element}
|
7
|
+
res
|
8
|
+
end
|
9
|
+
|
10
|
+
def restrict_to(index_set)
|
11
|
+
res = []
|
12
|
+
each_with_index{|e,i| res << e if index_set.include?(i)}
|
13
|
+
res
|
14
|
+
end
|
15
|
+
|
16
|
+
def sum
|
17
|
+
inject(0.0){|r,v| r += v }
|
18
|
+
end
|
19
|
+
|
20
|
+
def mean
|
21
|
+
sum/length
|
22
|
+
end
|
23
|
+
|
24
|
+
def stddev(mean=nil)
|
25
|
+
mean ||= self.mean
|
26
|
+
r = inject(0.0){|r,v| r += (v-mean)*(v-mean) }
|
27
|
+
Math.sqrt(r/(length-1))
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def stddev_percentage(stddev, mean)
|
32
|
+
stddev.zero? ? 0.0 : (stddev/mean)*100
|
33
|
+
end
|
34
|
+
|
35
|
+
def die(msg, error_code=1)
|
36
|
+
$stderr.puts msg
|
37
|
+
exit error_code
|
38
|
+
end
|
39
|
+
|
40
|
+
class File
|
41
|
+
def self.open_or_die(filename, &block)
|
42
|
+
filename = filename.sub(/^\/([cdefgh])(\/)/, '\1:\2') if RUBY_PLATFORM =~ /win32/
|
43
|
+
begin
|
44
|
+
if stat(filename).readable?
|
45
|
+
open(filename, &block)
|
46
|
+
else
|
47
|
+
die "file #{filename} is unreadable"
|
48
|
+
end
|
49
|
+
rescue
|
50
|
+
die "file #{filename} does not exist"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def truncate(text, length = 32, truncate_string = "...")
|
56
|
+
if text.nil? then return "" end
|
57
|
+
l = truncate_string.length + 1
|
58
|
+
|
59
|
+
if $KCODE == "NONE"
|
60
|
+
text.length > length ? text[0..(length - l)] + truncate_string : text
|
61
|
+
else
|
62
|
+
chars = text.split(//)
|
63
|
+
chars.length > length ? chars[0..(length - l)].join + truncate_string : text
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,397 @@
|
|
1
|
+
class RailsBenchmark
|
2
|
+
|
3
|
+
attr_accessor :gc_frequency, :iterations, :url_spec
|
4
|
+
attr_accessor :http_host, :remote_addr, :server_port
|
5
|
+
attr_accessor :relative_url_root
|
6
|
+
attr_accessor :perform_caching, :cache_template_loading
|
7
|
+
attr_accessor :session_data
|
8
|
+
|
9
|
+
def error_exit(msg)
|
10
|
+
STDERR.puts msg
|
11
|
+
raise msg
|
12
|
+
end
|
13
|
+
|
14
|
+
def patched_gc?
|
15
|
+
@patched_gc
|
16
|
+
end
|
17
|
+
|
18
|
+
def relative_url_root=(value)
|
19
|
+
ActionController::AbstractRequest.relative_url_root = value
|
20
|
+
@relative_url_root = value
|
21
|
+
end
|
22
|
+
|
23
|
+
def initialize(options={})
|
24
|
+
unless @gc_frequency = options[:gc_frequency]
|
25
|
+
@gc_frequency = 0
|
26
|
+
ARGV.each{|arg| @gc_frequency = $1.to_i if arg =~ /-gc(\d+)/ }
|
27
|
+
end
|
28
|
+
|
29
|
+
@iterations = (options[:iterations] || 100).to_i
|
30
|
+
|
31
|
+
@remote_addr = options[:remote_addr] || '127.0.0.1'
|
32
|
+
@http_host = options[:http_host] || '127.0.0.1'
|
33
|
+
@server_port = options[:server_port] || '80'
|
34
|
+
|
35
|
+
@session_data = options[:session_data] || {}
|
36
|
+
|
37
|
+
@url_spec = options[:url_spec]
|
38
|
+
|
39
|
+
ENV['RAILS_ENV'] = 'benchmarking'
|
40
|
+
|
41
|
+
require ENV['RAILS_ROOT'] + "/config/environment"
|
42
|
+
require 'dispatcher' # make edge rails happy
|
43
|
+
|
44
|
+
# we don't want local error template output, which crashes anyway
|
45
|
+
ActionController::Rescue.class_eval "def local_request?; false; end"
|
46
|
+
|
47
|
+
# make sure an error code gets returned for 1.1.6
|
48
|
+
ActionController::Rescue.class_eval <<-"end_eval"
|
49
|
+
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
|
56
|
+
end
|
57
|
+
end_eval
|
58
|
+
|
59
|
+
if ARGV.include?('-path')
|
60
|
+
$:.each{|f| STDERR.puts f}
|
61
|
+
exit
|
62
|
+
end
|
63
|
+
|
64
|
+
log_level = options[:log]
|
65
|
+
log_level = Logger::DEBUG if ARGV.include?('-log')
|
66
|
+
ARGV.each{|arg| arg =~ /-log=([a-zA-Z]*)/ && (log_level = eval("Logger::#{$1.upcase}")) }
|
67
|
+
|
68
|
+
if log_level
|
69
|
+
RAILS_DEFAULT_LOGGER.level = log_level
|
70
|
+
#ActiveRecord::Base.logger.level = log_level
|
71
|
+
#ActionController::Base.logger.level = log_level
|
72
|
+
#ActionMailer::Base.logger = level = log_level if defined?(ActionMailer)
|
73
|
+
else
|
74
|
+
ActiveRecord::Base.logger = nil
|
75
|
+
ActionController::Base.logger = nil
|
76
|
+
ActionMailer::Base.logger = nil if defined?(ActionMailer)
|
77
|
+
end
|
78
|
+
|
79
|
+
if options.has_key?(:perform_caching)
|
80
|
+
ActionController::Base.perform_caching = options[:perform_caching]
|
81
|
+
else
|
82
|
+
ActionController::Base.perform_caching = false if ARGV.include?('-nocache')
|
83
|
+
ActionController::Base.perform_caching = true if ARGV.include?('-cache')
|
84
|
+
end
|
85
|
+
|
86
|
+
if options.has_key?(:cache_template_loading)
|
87
|
+
ActionView::Base.cache_template_loading = options[:cache_template_loading]
|
88
|
+
else
|
89
|
+
ActionView::Base.cache_template_loading = true
|
90
|
+
end
|
91
|
+
|
92
|
+
self.relative_url_root = options[:relative_url_root] || ''
|
93
|
+
|
94
|
+
@patched_gc = GC.collections.is_a?(Numeric) rescue false
|
95
|
+
|
96
|
+
if ARGV.include? '-headers_only'
|
97
|
+
require File.dirname(__FILE__) + '/write_headers_only'
|
98
|
+
end
|
99
|
+
|
100
|
+
end
|
101
|
+
|
102
|
+
def establish_test_session
|
103
|
+
session_options = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS.stringify_keys
|
104
|
+
session_options = session_options.merge('new_session' => true)
|
105
|
+
@session = CGI::Session.new(Hash.new, session_options)
|
106
|
+
@session_data.each{ |k,v| @session[k] = v }
|
107
|
+
@session.update
|
108
|
+
@session_id = @session.session_id
|
109
|
+
end
|
110
|
+
|
111
|
+
def delete_test_session
|
112
|
+
@session.delete
|
113
|
+
@session = nil
|
114
|
+
end
|
115
|
+
|
116
|
+
# can be redefined in subclasses to clean out test sessions
|
117
|
+
def delete_new_test_sessions
|
118
|
+
end
|
119
|
+
|
120
|
+
def setup_test_urls(name)
|
121
|
+
raise "There is no benchmark named '#{name}'" unless @url_spec[name]
|
122
|
+
@urls = self.class.parse_url_spec(@url_spec, name)
|
123
|
+
end
|
124
|
+
|
125
|
+
def setup_initial_env
|
126
|
+
ENV['REMOTE_ADDR'] = remote_addr
|
127
|
+
ENV['HTTP_HOST'] = http_host
|
128
|
+
ENV['SERVER_PORT'] = server_port.to_s
|
129
|
+
ENV['REQUEST_METHOD'] = 'GET'
|
130
|
+
end
|
131
|
+
|
132
|
+
def setup_request_env(uri, query_string, new_session)
|
133
|
+
ENV['REQUEST_URI'] = @relative_url_root + uri
|
134
|
+
ENV['QUERY_STRING'] = query_string || ''
|
135
|
+
ENV['CONTENT_LENGTH'] = (query_string || '').length.to_s
|
136
|
+
ENV['HTTP_COOKIE'] = new_session ? '' : "_session_id=#{@session_id}"
|
137
|
+
end
|
138
|
+
|
139
|
+
def warmup
|
140
|
+
error_exit "No urls given for performance test" unless @urls && @urls.size>0
|
141
|
+
setup_initial_env
|
142
|
+
@urls.each do |entry|
|
143
|
+
error_exit "No uri given for benchmark entry: #{entry.inspect}" unless entry['uri']
|
144
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
145
|
+
Dispatcher.dispatch
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def run_urls_without_benchmark(gc_stats)
|
150
|
+
# support for running Ruby Performance Validator
|
151
|
+
# or Ruby Memory Validator
|
152
|
+
svl = nil
|
153
|
+
begin
|
154
|
+
if ARGV.include?('-svlPV')
|
155
|
+
require 'svlRubyPV'
|
156
|
+
svl = SvlRubyPV.new
|
157
|
+
elsif ARGV.include?('-svlMV')
|
158
|
+
require 'svlRubyMV'
|
159
|
+
svl = SvlRubyMV.new
|
160
|
+
end
|
161
|
+
rescue LoadError
|
162
|
+
# SVL dll not available, do nothing
|
163
|
+
end
|
164
|
+
|
165
|
+
# support ruby-prof
|
166
|
+
ruby_prof = nil
|
167
|
+
ARGV.each{|arg| ruby_prof=$1 if arg =~ /-ruby_prof=(\d*\.?\d*)/ }
|
168
|
+
begin
|
169
|
+
if ruby_prof
|
170
|
+
require 'ruby-prof'
|
171
|
+
RubyProf.clock_mode = RubyProf::WALL_TIME
|
172
|
+
RubyProf.start
|
173
|
+
end
|
174
|
+
rescue LoadError
|
175
|
+
# ruby-prof not available, do nothing
|
176
|
+
end
|
177
|
+
|
178
|
+
# start profiler and trigger data collection if required
|
179
|
+
if svl
|
180
|
+
svl.startProfiler
|
181
|
+
svl.startDataCollection
|
182
|
+
end
|
183
|
+
|
184
|
+
setup_initial_env
|
185
|
+
GC.enable_stats if gc_stats
|
186
|
+
if gc_frequency==0
|
187
|
+
run_urls_without_benchmark_and_without_gc_control(@urls, iterations)
|
188
|
+
else
|
189
|
+
run_urls_without_benchmark_but_with_gc_control(@urls, iterations, gc_frequency)
|
190
|
+
end
|
191
|
+
if gc_stats
|
192
|
+
GC.enable if gc_frequency
|
193
|
+
GC.start
|
194
|
+
GC.dump
|
195
|
+
GC.disable_stats
|
196
|
+
GC.log "number of requests processed: #{@urls.size * iterations}"
|
197
|
+
end
|
198
|
+
|
199
|
+
# stop data collection if necessary
|
200
|
+
svl.stopDataCollection if svl
|
201
|
+
|
202
|
+
if defined? RubyProf
|
203
|
+
result = RubyProf.stop
|
204
|
+
# Print a flat profile to text
|
205
|
+
printer = RubyProf::GraphHtmlPrinter.new(result)
|
206
|
+
printer.print(STDERR, ruby_prof.to_f)
|
207
|
+
end
|
208
|
+
|
209
|
+
delete_test_session
|
210
|
+
delete_new_test_sessions
|
211
|
+
end
|
212
|
+
|
213
|
+
def run_urls(test)
|
214
|
+
setup_initial_env
|
215
|
+
if gc_frequency>0
|
216
|
+
run_urls_with_gc_control(test, @urls, iterations, gc_frequency)
|
217
|
+
else
|
218
|
+
run_urls_without_gc_control(test, @urls, iterations)
|
219
|
+
end
|
220
|
+
delete_test_session
|
221
|
+
delete_new_test_sessions
|
222
|
+
end
|
223
|
+
|
224
|
+
def run_url_mix(test)
|
225
|
+
if gc_frequency>0
|
226
|
+
run_url_mix_with_gc_control(test, @urls, iterations, gc_frequency)
|
227
|
+
else
|
228
|
+
run_url_mix_without_gc_control(test, @urls, iterations)
|
229
|
+
end
|
230
|
+
delete_test_session
|
231
|
+
delete_new_test_sessions
|
232
|
+
end
|
233
|
+
|
234
|
+
private
|
235
|
+
|
236
|
+
def run_urls_without_benchmark_but_with_gc_control(urls, n, gc_frequency)
|
237
|
+
urls.each do |entry|
|
238
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
239
|
+
GC.enable; GC.start; GC.disable
|
240
|
+
request_count = 0
|
241
|
+
n.times do
|
242
|
+
Dispatcher.dispatch
|
243
|
+
if (request_count += 1) == gc_frequency
|
244
|
+
GC.enable; GC.start; GC.disable
|
245
|
+
request_count = 0
|
246
|
+
end
|
247
|
+
end
|
248
|
+
end
|
249
|
+
end
|
250
|
+
|
251
|
+
def run_urls_without_benchmark_and_without_gc_control(urls, n)
|
252
|
+
urls.each do |entry|
|
253
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
254
|
+
n.times do
|
255
|
+
Dispatcher.dispatch
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def run_urls_with_gc_control(test, urls, n, gc_freq)
|
261
|
+
gc_stats = patched_gc?
|
262
|
+
GC.clear_stats if gc_stats
|
263
|
+
urls.each do |entry|
|
264
|
+
request_count = 0
|
265
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
266
|
+
test.report(entry['uri']) do
|
267
|
+
GC.disable_stats if gc_stats
|
268
|
+
GC.enable; GC.start; GC.disable
|
269
|
+
GC.enable_stats if gc_stats
|
270
|
+
n.times do
|
271
|
+
Dispatcher.dispatch
|
272
|
+
if (request_count += 1) == gc_freq
|
273
|
+
GC.enable; GC.start; GC.disable
|
274
|
+
request_count = 0
|
275
|
+
end
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
if gc_stats
|
280
|
+
GC.disable_stats
|
281
|
+
Benchmark::OUTPUT.puts "GC.collections=#{GC.collections}, GC.time=#{GC.time/1E6}"
|
282
|
+
GC.clear_stats
|
283
|
+
end
|
284
|
+
end
|
285
|
+
|
286
|
+
def run_urls_without_gc_control(test, urls, n)
|
287
|
+
gc_stats = patched_gc?
|
288
|
+
GC.clear_stats if gc_stats
|
289
|
+
urls.each do |entry|
|
290
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
291
|
+
GC.disable_stats if gc_stats
|
292
|
+
GC.start
|
293
|
+
GC.enable_stats if gc_stats
|
294
|
+
test.report(entry['uri']) do
|
295
|
+
n.times do
|
296
|
+
Dispatcher.dispatch
|
297
|
+
end
|
298
|
+
end
|
299
|
+
end
|
300
|
+
if gc_stats
|
301
|
+
GC.disable_stats
|
302
|
+
Benchmark::OUTPUT.puts "GC.collections=#{GC.collections}, GC.time=#{GC.time/1E6}"
|
303
|
+
GC.clear_stats
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def run_url_mix_without_gc_control(test, urls, n)
|
308
|
+
gc_stats = patched_gc?
|
309
|
+
GC.start
|
310
|
+
if gc_stats
|
311
|
+
GC.clear_stats; GC.enable_stats
|
312
|
+
end
|
313
|
+
test.report("url_mix (#{urls.length} urls)") do
|
314
|
+
n.times do
|
315
|
+
urls.each do |entry|
|
316
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
317
|
+
Dispatcher.dispatch
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
321
|
+
if gc_stats
|
322
|
+
GC.disable_stats
|
323
|
+
Benchmark::OUTPUT.puts "GC.collections=#{GC.collections}, GC.time=#{GC.time/1E6}"
|
324
|
+
GC.clear_stats
|
325
|
+
end
|
326
|
+
end
|
327
|
+
|
328
|
+
def run_url_mix_with_gc_control(test, urls, n, gc_frequency)
|
329
|
+
gc_stats = patched_gc?
|
330
|
+
GC.enable; GC.start; GC.disable
|
331
|
+
if gc_stats
|
332
|
+
GC.clear_stats; GC.enable_stats
|
333
|
+
end
|
334
|
+
test.report("url_mix (#{urls.length} urls)") do
|
335
|
+
request_count = 0
|
336
|
+
n.times do
|
337
|
+
urls.each do |entry|
|
338
|
+
setup_request_env(entry['uri'], entry['query_string'], entry['new_session'])
|
339
|
+
Dispatcher.dispatch
|
340
|
+
if (request_count += 1) == gc_frequency
|
341
|
+
GC.enable; GC.start; GC.disable
|
342
|
+
request_count = 0
|
343
|
+
end
|
344
|
+
end
|
345
|
+
end
|
346
|
+
end
|
347
|
+
if gc_stats
|
348
|
+
GC.disable_stats
|
349
|
+
Benchmark::OUTPUT.puts "GC.collections=#{GC.collections}, GC.time=#{GC.time/1E6}"
|
350
|
+
GC.clear_stats
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def self.parse_url_spec(url_spec, name)
|
355
|
+
res = url_spec[name]
|
356
|
+
if res.is_a?(String)
|
357
|
+
res = res.split(/, */).collect!{ |n| parse_url_spec(url_spec, n) }.flatten
|
358
|
+
elsif res.is_a?(Hash)
|
359
|
+
res = [ res ]
|
360
|
+
end
|
361
|
+
res
|
362
|
+
end
|
363
|
+
|
364
|
+
end
|
365
|
+
|
366
|
+
|
367
|
+
class RailsBenchmarkWithActiveRecordStore < RailsBenchmark
|
368
|
+
|
369
|
+
def initialize(options={})
|
370
|
+
super(options)
|
371
|
+
@session_class = ActionController::CgiRequest::DEFAULT_SESSION_OPTIONS[:database_manager].session_class rescue CGI::Session::ActiveRecordStore
|
372
|
+
end
|
373
|
+
|
374
|
+
def delete_new_test_sessions
|
375
|
+
@session_class.delete_all if @session_class.respond_to?(:delete_all)
|
376
|
+
end
|
377
|
+
|
378
|
+
end
|
379
|
+
|
380
|
+
|
381
|
+
__END__
|
382
|
+
|
383
|
+
# Copyright (C) 2005, 2006 Stefan Kaes
|
384
|
+
#
|
385
|
+
# This program is free software; you can redistribute it and/or modify
|
386
|
+
# it under the terms of the GNU General Public License as published by
|
387
|
+
# the Free Software Foundation; either version 2 of the License, or
|
388
|
+
# (at your option) any later version.
|
389
|
+
#
|
390
|
+
# This program is distributed in the hope that it will be useful,
|
391
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
392
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
393
|
+
# GNU General Public License for more details.
|
394
|
+
#
|
395
|
+
# You should have received a copy of the GNU General Public License
|
396
|
+
# along with this program; if not, write to the Free Software
|
397
|
+
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|