benchmark_for_rails 1.0.0
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/LICENSE +20 -0
- data/README +12 -0
- data/Rakefile +22 -0
- data/lib/benchmark_for_rails.rb +72 -0
- data/lib/benchmark_for_rails/log_parser.rb +117 -0
- data/lib/benchmark_for_rails/report.rb +34 -0
- data/lib/benchmark_for_rails/resource_path.rb +48 -0
- data/lib/benchmark_for_rails/rewatching.rb +32 -0
- data/lib/reporting.rb +43 -0
- data/lib/tasks/logs.rake +24 -0
- data/rails/init.rb +31 -0
- data/test/app/some/class.rb +0 -0
- data/test/app/some/klass.rb +7 -0
- data/test/app/some/module.rb +4 -0
- data/test/benchmark_for_rails_test.rb +79 -0
- data/test/reloading_test.rb +14 -0
- data/test/test_helper.rb +12 -0
- metadata +88 -0
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 [name of plugin creator]
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
BenchmarkForRails
|
2
|
+
=================
|
3
|
+
|
4
|
+
Automatic Rails benchmarking for:
|
5
|
+
* Before Filters
|
6
|
+
* After Filters
|
7
|
+
* Session Initialization
|
8
|
+
* ActiveRecord finds (not just DB access!)
|
9
|
+
* ... and easily add your own
|
10
|
+
|
11
|
+
|
12
|
+
Copyright (c) 2007 Lance Ivy, released under the MIT license
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'rake'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/rdoctask'
|
4
|
+
|
5
|
+
desc 'Default: run unit tests.'
|
6
|
+
task :default => :test
|
7
|
+
|
8
|
+
desc 'Test the benchmark_for_rails plugin.'
|
9
|
+
Rake::TestTask.new(:test) do |t|
|
10
|
+
t.libs << 'lib'
|
11
|
+
t.pattern = 'test/**/*_test.rb'
|
12
|
+
t.verbose = true
|
13
|
+
end
|
14
|
+
|
15
|
+
desc 'Generate documentation for the benchmark_for_rails plugin.'
|
16
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
17
|
+
rdoc.rdoc_dir = 'rdoc'
|
18
|
+
rdoc.title = 'BenchmarkForRails'
|
19
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
20
|
+
rdoc.rdoc_files.include('README')
|
21
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
22
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/benchmark_for_rails/rewatching'
|
2
|
+
|
3
|
+
# BenchmarkForRails addresses a few issues with ActionController's benchmarking:
|
4
|
+
# * hidden query costs (ActiveRecord's cost of building a query)
|
5
|
+
# * no visibility on the cost of before/after filters
|
6
|
+
# * no visibility on cost of session management
|
7
|
+
#
|
8
|
+
# Other strengths:
|
9
|
+
# * very easy to benchmark new things without implementing your own alias_method_chain (use BenchmarkForRails.watch)
|
10
|
+
# * automatically handles methods called multiple times (e.g. ActiveRecord::Base#find)
|
11
|
+
module BenchmarkForRails
|
12
|
+
class << self
|
13
|
+
# Starts benchmarking for some method. Results will be automatically
|
14
|
+
# logged after the request finishes processing.
|
15
|
+
#
|
16
|
+
# Arguments:
|
17
|
+
# * name: how to refer to this benchmark in the logs
|
18
|
+
# * obj: the object that defines the method
|
19
|
+
# * method: the name of the method to be benchmarked
|
20
|
+
# * instance: whether the method is an instance method or not
|
21
|
+
def watch(name, obj, method, instance = true)
|
22
|
+
rewatchable(name, obj.to_s, method, instance) if ActiveSupport::Dependencies.will_unload?(obj)
|
23
|
+
|
24
|
+
obj.class_eval <<-EOL, __FILE__, __LINE__
|
25
|
+
#{"class << self" unless instance}
|
26
|
+
def #{method}_with_benchmark_for_rails(*args, &block)
|
27
|
+
BenchmarkForRails.measure(#{name.inspect}) {#{method}_without_benchmark_for_rails(*args, &block)}
|
28
|
+
end
|
29
|
+
|
30
|
+
alias_method_chain :#{method}, :benchmark_for_rails
|
31
|
+
#{"end" unless instance}
|
32
|
+
EOL
|
33
|
+
end
|
34
|
+
|
35
|
+
def results #:nodoc:
|
36
|
+
Thread.current[:results] ||= Hash.new(0)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Used by watch to record the time of a method call without losing the
|
40
|
+
# method's return value
|
41
|
+
def measure(name, &block)
|
42
|
+
if self.running? name
|
43
|
+
yield
|
44
|
+
else
|
45
|
+
result = nil
|
46
|
+
self.running << name
|
47
|
+
begin
|
48
|
+
self.results[name] += Benchmark.ms{result = yield} / 1000
|
49
|
+
rescue
|
50
|
+
raise
|
51
|
+
ensure
|
52
|
+
self.running.delete(name)
|
53
|
+
end
|
54
|
+
result
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def logger #:nodoc:
|
59
|
+
Rails.logger
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
def running?(name)
|
65
|
+
running.include? name
|
66
|
+
end
|
67
|
+
|
68
|
+
def running
|
69
|
+
Thread.current[:running] ||= []
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module BenchmarkForRails
|
2
|
+
class LogParser
|
3
|
+
# The log file to be read.
|
4
|
+
attr_accessor :file
|
5
|
+
|
6
|
+
# If set to a Time, will limit the parsed responses to those *after*
|
7
|
+
# the threshold. This lets you parse the last hour's worth of data out
|
8
|
+
# of a log.
|
9
|
+
#
|
10
|
+
# Depends on the 'elif' gem.
|
11
|
+
attr_accessor :threshold
|
12
|
+
|
13
|
+
def initialize(file = nil, threshold = nil)
|
14
|
+
self.file = file || RAILS_ROOT + '/log/production.log'
|
15
|
+
self.threshold = threshold
|
16
|
+
end
|
17
|
+
|
18
|
+
# pulls B4R lines out of the logfile.
|
19
|
+
B4R_RE = /^B4R/
|
20
|
+
PROCESSING_RE = /\AProcessing .+ at (\d+-\d+-\d+ \d+:\d+:\d+)\Z/
|
21
|
+
def lines
|
22
|
+
if @lines.nil?
|
23
|
+
if threshold
|
24
|
+
require 'elif'
|
25
|
+
@lines = []
|
26
|
+
Elif.foreach(self.file) do |line|
|
27
|
+
if B4R_RE.match(line)
|
28
|
+
@lines << line
|
29
|
+
elsif threshold and PROCESSING_RE.match(line)
|
30
|
+
@lines.pop and break if Time.parse($1) < self.threshold
|
31
|
+
end
|
32
|
+
end
|
33
|
+
else
|
34
|
+
@lines ||= File.read(self.file).grep(/^B4R/)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
@lines
|
38
|
+
end
|
39
|
+
|
40
|
+
# reads the resource_paths to calculate average times for *each* benchmark type.
|
41
|
+
# that is, this returns averages indexed by benchmark, not path.
|
42
|
+
def benchmarks
|
43
|
+
if @benchmarks.nil?
|
44
|
+
benchmarks = {}
|
45
|
+
resource_paths.each do |path|
|
46
|
+
path.requests.each do |request|
|
47
|
+
request.each do |benchmark, time| (benchmarks[benchmark] ||= []) << time end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
@benchmarks = {}
|
52
|
+
benchmarks.each do |benchmark, times| @benchmarks[benchmark] = (times.sum || 0) / times.size end
|
53
|
+
end
|
54
|
+
@benchmarks
|
55
|
+
end
|
56
|
+
|
57
|
+
# pulls ResourcePath objects out of the file
|
58
|
+
def resource_paths
|
59
|
+
if @resource_paths.nil?
|
60
|
+
paths_by_path = {}
|
61
|
+
lines.each do |line|
|
62
|
+
path_name = ResourcePath.path_from(line)
|
63
|
+
paths_by_path[path_name] ||= ResourcePath.new(path_name)
|
64
|
+
paths_by_path[path_name].add_request(line)
|
65
|
+
end
|
66
|
+
@resource_paths = paths_by_path.values
|
67
|
+
end
|
68
|
+
@resource_paths
|
69
|
+
end
|
70
|
+
|
71
|
+
# returns the slowest few ResourcePaths
|
72
|
+
# optionally determines slowness by weighting speed vs frequency (e.g. total time, not average time)
|
73
|
+
def slowest(number = 10, total_time = false)
|
74
|
+
resource_paths.sort_by(&(total_time ? :total_time : :average_time)).reverse[0...number]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
class ResourcePath
|
79
|
+
# pre-generate the regular expressions. this matters when you loop one million times.
|
80
|
+
PATH_STRIP_RE = /.*\] */
|
81
|
+
MEASUREMENT_SPLIT_RE = / *: */
|
82
|
+
PATH_RE = /\[(.*?)\]/
|
83
|
+
|
84
|
+
attr_reader :path
|
85
|
+
attr_reader :requests
|
86
|
+
|
87
|
+
def initialize(path)
|
88
|
+
@path = path
|
89
|
+
@requests = []
|
90
|
+
end
|
91
|
+
|
92
|
+
def add_request(line)
|
93
|
+
measurements = {}
|
94
|
+
line.sub(PATH_STRIP_RE, '').split(' | ').each do |measurement|
|
95
|
+
md = MEASUREMENT_SPLIT_RE.match(measurement)
|
96
|
+
measurements[md.pre_match] = md.post_match.to_f
|
97
|
+
end
|
98
|
+
self.requests << measurements
|
99
|
+
end
|
100
|
+
|
101
|
+
def frequency
|
102
|
+
@frequency ||= requests.length
|
103
|
+
end
|
104
|
+
|
105
|
+
def total_time
|
106
|
+
@total_time ||= requests.collect{|r| r['request']}.sum
|
107
|
+
end
|
108
|
+
|
109
|
+
def average_time
|
110
|
+
@average_time ||= total_time / frequency
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.path_from(str)
|
114
|
+
PATH_RE.match(str)[1]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
class BenchmarkForRails::Report
|
2
|
+
PADDING = 2
|
3
|
+
|
4
|
+
attr_accessor :rows
|
5
|
+
def initialize
|
6
|
+
self.rows = []
|
7
|
+
end
|
8
|
+
|
9
|
+
def render(*headers)
|
10
|
+
headers.each_index do |i|
|
11
|
+
print headers[i].to_s.ljust(width_of_column(i, headers[i].length + PADDING), ' ')
|
12
|
+
end
|
13
|
+
puts ""
|
14
|
+
|
15
|
+
headers.each_index do |i|
|
16
|
+
print '-' * (width_of_column(i) - PADDING)
|
17
|
+
print ' ' * PADDING
|
18
|
+
end
|
19
|
+
puts ""
|
20
|
+
|
21
|
+
rows.each do |row|
|
22
|
+
row.each_index do |i|
|
23
|
+
print row[i].to_s.ljust(width_of_column(i), ' ')
|
24
|
+
end
|
25
|
+
puts ""
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def width_of_column(i, min = 8)
|
30
|
+
@column_widths ||= []
|
31
|
+
@column_widths[i] = (rows.collect{|r| r[i]}.collect{|c| c.to_s.length + PADDING} << min).max if @column_widths[i].nil?
|
32
|
+
@column_widths[i]
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module BenchmarkForRails
|
2
|
+
class ResourcePath
|
3
|
+
attr_reader :path
|
4
|
+
attr_reader :requests
|
5
|
+
|
6
|
+
def initialize(path)
|
7
|
+
@path = path
|
8
|
+
@requests = []
|
9
|
+
end
|
10
|
+
|
11
|
+
def add_request(line)
|
12
|
+
measurements = {}
|
13
|
+
line.sub(/.*\]/, '').split(' | ').each do |measurement|
|
14
|
+
name, seconds = *measurement.split(':').collect{|p| p.strip}
|
15
|
+
measurements[name] = seconds.to_f
|
16
|
+
end
|
17
|
+
self.requests << measurements
|
18
|
+
end
|
19
|
+
|
20
|
+
def frequency
|
21
|
+
@frequency ||= requests.length
|
22
|
+
end
|
23
|
+
|
24
|
+
def total_time
|
25
|
+
@total_time ||= self['request'].sum
|
26
|
+
end
|
27
|
+
|
28
|
+
def average_time
|
29
|
+
@average_time ||= total_time / frequency
|
30
|
+
end
|
31
|
+
|
32
|
+
def min
|
33
|
+
@min ||= self['request'].min
|
34
|
+
end
|
35
|
+
|
36
|
+
def max
|
37
|
+
@max ||= self['request'].max
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.path_from(str)
|
41
|
+
str.match(/\[(.*)\]/)[1]
|
42
|
+
end
|
43
|
+
|
44
|
+
def [](benchmark_name)
|
45
|
+
requests.collect{|r| r[benchmark_name.to_s]}
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module BenchmarkForRails
|
2
|
+
class << self
|
3
|
+
def rewatch!
|
4
|
+
rewatchables.each do |(name, object_name, method, instance)|
|
5
|
+
watch(name, object_name.constantize, method, instance)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def rewatchable(*args)
|
10
|
+
rewatchables << args unless rewatchables.include? args
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def rewatchables
|
16
|
+
@rewatchables ||= []
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
# hook into dependency unloading
|
22
|
+
module ActiveSupport::Dependencies
|
23
|
+
class << self
|
24
|
+
def clear_with_rewatching(*args, &block)
|
25
|
+
clear_without_rewatching(*args, &block).tap do
|
26
|
+
BenchmarkForRails.rewatch!
|
27
|
+
end
|
28
|
+
end
|
29
|
+
alias_method_chain :clear, :rewatching
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
data/lib/reporting.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
module BenchmarkForRails
|
2
|
+
class << self
|
3
|
+
# Prints the benchmarks for the request into the log, with some
|
4
|
+
# basic ASCII formatting (yay). Uses a multi-line format for
|
5
|
+
# development mode, and a one-line format for production mode.
|
6
|
+
def report(request)
|
7
|
+
request_action = "#{request.method.to_s.upcase} #{request.path.chomp('/')}"
|
8
|
+
request_time = results.delete(:request) || 0.0
|
9
|
+
|
10
|
+
if RAILS_ENV.to_sym == :production
|
11
|
+
# in production mode, we want to use a one-line format that makes it easy to
|
12
|
+
# parse logs generated by multiple processes.
|
13
|
+
benchmarks = ["request: #{'%.4f' % request_time}"]
|
14
|
+
benchmarks += results.to_a.collect{|(name, seconds)| "#{name}: #{'%.4f' % seconds}"}
|
15
|
+
|
16
|
+
logger.info "B4R: [#{request_action}] #{benchmarks.join(' | ')}"
|
17
|
+
else
|
18
|
+
# in development mode, we want to use a multi-line format that makes it easy
|
19
|
+
# for a human to read.
|
20
|
+
logger.info "- [#{'%.4f' % request_time}] #{request_action} ".ljust(50, '-')
|
21
|
+
|
22
|
+
results.to_a.sort_by{|(name, seconds)| seconds}.reverse.each do |(name, seconds)|
|
23
|
+
logger.info " #{'%.4f' % seconds} #{name}"
|
24
|
+
end
|
25
|
+
|
26
|
+
logger.info " BenchmarkForRails -".rjust(50, '-')
|
27
|
+
end
|
28
|
+
|
29
|
+
results.clear
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class ActionController::Dispatcher
|
35
|
+
# print reports at the end
|
36
|
+
def call_with_benchmark_for_rails_reporting(*args, &block) #:nodoc:
|
37
|
+
call_without_benchmark_for_rails_reporting(*args, &block).tap do |status, headers, response|
|
38
|
+
BenchmarkForRails.report(ActionController::Request.new(args.first))
|
39
|
+
Rails.logger.flush if Rails.logger.respond_to? :flush
|
40
|
+
end
|
41
|
+
end
|
42
|
+
alias_method_chain :call, :benchmark_for_rails_reporting
|
43
|
+
end
|
data/lib/tasks/logs.rake
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# Bleh. Why isn't this already loaded? I need Symbol#& !
|
2
|
+
require 'active_support'
|
3
|
+
require File.dirname(__FILE__) + '/../benchmark_for_rails'
|
4
|
+
require File.dirname(__FILE__) + '/../benchmark_for_rails/report'
|
5
|
+
require File.dirname(__FILE__) + '/../benchmark_for_rails/log_parser'
|
6
|
+
require File.dirname(__FILE__) + '/../benchmark_for_rails/resource_path'
|
7
|
+
|
8
|
+
namespace :log do
|
9
|
+
desc "run reports on BenchmarkForRails log output. examples: `rake log:analyze path='GET /'`, or `rake log:analyze order_by=averages`"
|
10
|
+
task :analyze do
|
11
|
+
report = BenchmarkForRails::Report.new
|
12
|
+
order_by_totals = !(ENV['order_by'] =~ /^averages?/)
|
13
|
+
BenchmarkForRails::LogParser.new.slowest(10, order_by_totals).each do |resource_path|
|
14
|
+
report.rows << [
|
15
|
+
resource_path.path,
|
16
|
+
'%.4f' % resource_path.total_time,
|
17
|
+
resource_path.frequency,
|
18
|
+
'%.4f' % resource_path.average_time,
|
19
|
+
"#{'%.3f' % resource_path.min} - #{'%.3f' % resource_path.max}"
|
20
|
+
]
|
21
|
+
end
|
22
|
+
report.render('resource path', 'total time', 'frequency', 'average', 'min - max')
|
23
|
+
end
|
24
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# The indentation of this benchmark tree indicates which benchmarks include which other benchmarks.
|
2
|
+
# For example, :request = :reloading + :dispatch + ?
|
3
|
+
# And, :dispatch = :routing + :process + ?
|
4
|
+
BenchmarkForRails.watch(:request, ActionController::Dispatcher, :call)
|
5
|
+
BenchmarkForRails.watch("reloading", ActionController::Dispatcher, :reload_application, false) if RAILS_ENV == 'development'
|
6
|
+
# this runs after all the middleware
|
7
|
+
BenchmarkForRails.watch('dispatch', ActionController::Dispatcher, :_call)
|
8
|
+
BenchmarkForRails.watch('routing', ActionController::Routing::RouteSet, :recognize)
|
9
|
+
BenchmarkForRails.watch('process', ActionController::Base, :process)
|
10
|
+
BenchmarkForRails.watch("filters", ActionController::Filters::BeforeFilter, :call)
|
11
|
+
# Processing the action itself (calling the controller method)
|
12
|
+
# This is what Rails' default benchmarks claim is the response time.
|
13
|
+
BenchmarkForRails.watch("action", ActionController::Base, :perform_action)
|
14
|
+
# And yes, it's still important to know how much time is spent rendering.
|
15
|
+
BenchmarkForRails.watch("rendering", ActionController::Base, :render)
|
16
|
+
BenchmarkForRails.watch("filters", ActionController::Filters::AfterFilter, :call)
|
17
|
+
# TODO: this happens at the *end* of a request, after the middleware stack and definitely
|
18
|
+
# after reporting. so these times get attributed to the *next* request. too confusing.
|
19
|
+
# BenchmarkForRails.watch("reloading", ActionController::Dispatcher, :cleanup_application, false) if RAILS_ENV == 'development'
|
20
|
+
|
21
|
+
# The real cost of database access should include query construction.
|
22
|
+
# Hence why we try and watch the core finder. More watches might be added
|
23
|
+
# to this group to form a more complete picture of database access. The
|
24
|
+
# question is simply which methods bypass find().
|
25
|
+
# TODO: disabling this until it can be more comprehensive.
|
26
|
+
# BenchmarkForRails.watch("queries", ActiveRecord::Base, :find, false)
|
27
|
+
# BenchmarkForRails.watch("queries", ActiveRecord::Base, :find_by_sql, false)
|
28
|
+
|
29
|
+
# Should be included after the BenchmarkForRails.watch(:request, ...),
|
30
|
+
# since it hooks into the same method.
|
31
|
+
require 'reporting'
|
File without changes
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class BenchmarkForRailsTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
def setup
|
6
|
+
BenchmarkForRails.results.clear
|
7
|
+
end
|
8
|
+
|
9
|
+
def test_measure_preserves_return
|
10
|
+
assert_equal 'gyro', BenchmarkForRails.measure('deliciousness') { 'gyro' }
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_measure_adds_result
|
14
|
+
assert_equal 0, BenchmarkForRails.results['sky']
|
15
|
+
BenchmarkForRails.measure('sky') { 'wide' }
|
16
|
+
assert BenchmarkForRails.results['sky'] > 0
|
17
|
+
end
|
18
|
+
|
19
|
+
def test_measure_adds_to_existing_result
|
20
|
+
assert_equal 0, BenchmarkForRails.results['nalgene']
|
21
|
+
BenchmarkForRails.measure('nalgene') { 'liter' }
|
22
|
+
liter_measurement = BenchmarkForRails.results['nalgene']
|
23
|
+
BenchmarkForRails.measure('nalgene') { '750ml' }
|
24
|
+
assert BenchmarkForRails.results['nalgene'] > liter_measurement
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_basic_accuracy_of_measure
|
28
|
+
BenchmarkForRails.measure('somnolence') { sleep 0.2 }
|
29
|
+
assert BenchmarkForRails.results['somnolence'] < 0.21
|
30
|
+
assert BenchmarkForRails.results['somnolence'] > 0.19
|
31
|
+
end
|
32
|
+
|
33
|
+
# a test for measuring recursive functions
|
34
|
+
def test_nested_duplicate_measurements
|
35
|
+
BenchmarkForRails.measure('recursive') do
|
36
|
+
sleep 0.2
|
37
|
+
BenchmarkForRails.measure('recursive') do
|
38
|
+
sleep 0.2
|
39
|
+
end
|
40
|
+
end
|
41
|
+
assert BenchmarkForRails.results['recursive'] < 0.41
|
42
|
+
assert BenchmarkForRails.results['recursive'] > 0.39
|
43
|
+
end
|
44
|
+
|
45
|
+
def test_watching_passes_blocks
|
46
|
+
BenchmarkForRails.watch('yielding', Some::Klass, :yielder)
|
47
|
+
assert_equal 'christmas', Some::Klass.new.yielder { 'christmas' }
|
48
|
+
end
|
49
|
+
|
50
|
+
def test_watching_passes_args
|
51
|
+
BenchmarkForRails.watch('echoer', Some::Klass, :echoer)
|
52
|
+
assert_equal [1, 1, 2, 3, 5], Some::Klass.new.echoer(1, 1, 2, 3, 5)
|
53
|
+
end
|
54
|
+
|
55
|
+
def test_watching_instance_method
|
56
|
+
BenchmarkForRails.watch('instance_method', Some::Klass, :hello)
|
57
|
+
assert_equal 'hello', Some::Klass.new.hello
|
58
|
+
assert_not_nil BenchmarkForRails.results['instance_method']
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_watching_class_method
|
62
|
+
BenchmarkForRails.watch('class_method', Some::Klass, :world, false)
|
63
|
+
assert_equal 'world', Some::Klass.world
|
64
|
+
assert_not_nil BenchmarkForRails.results['class_method']
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_watching_module_method
|
68
|
+
BenchmarkForRails.watch('module_method', Some::Module, :foo)
|
69
|
+
assert_equal 'foo', Some::Klass.new.foo
|
70
|
+
assert_not_nil BenchmarkForRails.results['module_method']
|
71
|
+
end
|
72
|
+
|
73
|
+
# it's not called a module class method, is it? oh well.
|
74
|
+
def test_watching_module_class_method
|
75
|
+
BenchmarkForRails.watch('module_class_method', Some::Module, :bar, false)
|
76
|
+
assert_equal 'bar', Some::Module.bar
|
77
|
+
assert_not_nil BenchmarkForRails.results['module_class_method']
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper'
|
2
|
+
|
3
|
+
class ReloadingTest < Test::Unit::TestCase
|
4
|
+
def test_rewatching_reloaded_module
|
5
|
+
BenchmarkForRails.watch('reloading', Some::Klass, :hello)
|
6
|
+
original_oid = Some::Klass.object_id
|
7
|
+
assert ActiveSupport::Dependencies.will_unload?(Some::Klass)
|
8
|
+
assert Some::Klass.instance_methods.include?('hello_with_benchmark_for_rails')
|
9
|
+
|
10
|
+
ActiveSupport::Dependencies.clear
|
11
|
+
assert_not_equal original_oid, Some::Klass.object_id
|
12
|
+
assert Some::Klass.instance_methods.include?('hello_with_benchmark_for_rails')
|
13
|
+
end
|
14
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'test/unit'
|
3
|
+
gem 'rails', '2.3.11'
|
4
|
+
require 'benchmark'
|
5
|
+
require 'active_support'
|
6
|
+
|
7
|
+
# load the b4r module
|
8
|
+
lib_path = File.dirname(__FILE__) + '/../lib/'
|
9
|
+
ActiveSupport::Dependencies.autoload_paths << lib_path
|
10
|
+
|
11
|
+
# for the fake application directory - needed to test reloading
|
12
|
+
ActiveSupport::Dependencies.autoload_paths << File.dirname(__FILE__) + '/app/'
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: benchmark_for_rails
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Lance Ivy
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-19 00:00:00 -07:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Provides better benchmarking of the Rails request stack. Currently tuned for Rails 2.3.x.
|
23
|
+
email: lance@cainlevy.net
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files: []
|
29
|
+
|
30
|
+
files:
|
31
|
+
- lib/reporting.rb
|
32
|
+
- lib/tasks/logs.rake
|
33
|
+
- lib/benchmark_for_rails.rb
|
34
|
+
- lib/benchmark_for_rails/log_parser.rb
|
35
|
+
- lib/benchmark_for_rails/rewatching.rb
|
36
|
+
- lib/benchmark_for_rails/report.rb
|
37
|
+
- lib/benchmark_for_rails/resource_path.rb
|
38
|
+
- LICENSE
|
39
|
+
- README
|
40
|
+
- Rakefile
|
41
|
+
- rails/init.rb
|
42
|
+
- test/test_helper.rb
|
43
|
+
- test/benchmark_for_rails_test.rb
|
44
|
+
- test/app/some/module.rb
|
45
|
+
- test/app/some/klass.rb
|
46
|
+
- test/app/some/class.rb
|
47
|
+
- test/reloading_test.rb
|
48
|
+
has_rdoc: true
|
49
|
+
homepage: http://github.com/cainlevy/benchmarkforrails
|
50
|
+
licenses: []
|
51
|
+
|
52
|
+
post_install_message:
|
53
|
+
rdoc_options: []
|
54
|
+
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
hash: 3
|
63
|
+
segments:
|
64
|
+
- 0
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
72
|
+
segments:
|
73
|
+
- 0
|
74
|
+
version: "0"
|
75
|
+
requirements: []
|
76
|
+
|
77
|
+
rubyforge_project:
|
78
|
+
rubygems_version: 1.3.7
|
79
|
+
signing_key:
|
80
|
+
specification_version: 3
|
81
|
+
summary: Improved benchmarking in Rails, with easy customization.
|
82
|
+
test_files:
|
83
|
+
- test/test_helper.rb
|
84
|
+
- test/benchmark_for_rails_test.rb
|
85
|
+
- test/app/some/module.rb
|
86
|
+
- test/app/some/klass.rb
|
87
|
+
- test/app/some/class.rb
|
88
|
+
- test/reloading_test.rb
|