Reputation: 3068
I wrote a small benchmarking Class for testing my code doing development. At the moment I have to add the Class to the beginning and end of every method. Is it posible to prepend, append on the fly, so that I don't have to clutter my code?
class ApplicationController
before_filter :init_perf
after_filter :write_perf_results_to_log!
def init_perf
@perf ||= Perf.new
end
def write_perf_results_to_log!
@perf.results
end
end
class Products < ApplicationsController
def foo
@perf.log(__methond__.to_s)
caculation = 5 *4
@perf.write!
end
def bar
@perf.log(__methond__.to_s)
caculation = 1 / 5
@perf.write!
end
end
This is the Perf class. It is located in the services folder.
class Perf
def initialize
@results = []
end
def log(note)
@start = Time.now
@note = note
end
def write!
if @results.find {|h| h[:note] == @note } # Update :sec method exists in results
@results.select { |h| h["note"] == @note; h[":sec"] = (Time.now - @start).round(3) }
else # Add new Hash to results
@results << { :note => @note, :sec => (Time.now - @start).round(3) }
end
end
def results
content = "
PERFORMANCE STATISTICS!
"
@results.each do |r|
content += r[:note] + " " + r[:sec].to_s + "
"
end
content += "
"
Rails.logger.info content
end
end
Upvotes: 2
Views: 3064
Reputation: 507
I'm the author of aspector gem. Thanks to dimuch for mentioning it.
I've come up with a solution using aspector. Here are the high level steps:
The full code can be found in this gist. Please feel free to let me know if you have questions or the solution doesn't do what you intend to.
class PerfAspect < Aspector::Base around options[:action_methods] do |proxy| @perf ||= Perf.new proxy.call @perf.results end around options[:other_methods], :method_arg => true do |method, proxy, *args, &block| @perf.log(method) result = proxy.call *args, &block @perf.write! result end end action_methods = [:action] other_methods = Products.instance_methods(false) - action_methods PerfAspect.apply(Products, :action_methods => action_methods, :other_methods => other_methods)
Upvotes: 2
Reputation: 4847
There is better solution.
class ApplicationController
def self.inherited(klass)
def klass.method_added(name)
return if @_not_new
@_not_new = true
original = "original #{name}"
alias_method original, name
define_method(name) do |*args, &block|
puts "==> called #{name} with args: #{args.inspect}"
result = send original, *args, &block
puts "<== result is #{result}"
result
end
@_not_new = false
end
end
end
class Product < ApplicationController
def meth(a1, a2)
a1 + a2
end
end
product = Product.new
puts product.meth(2,3)
And the result:
==> called meth with args: [2, 3]
<== result is 5
5
The source & explanation are here: http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming. I recommend to spend not a big money to get this course.
Upvotes: 2
Reputation: 34308
In general computing terms what you want to do is called code instrumentation. There are several ways to accomplish this, however here's one (crude) example using some metaprogramming:
First define a new method that we will use for injecting our instrumentation code:
class ApplicationController
def self.instrument_methods(*methods)
methods.each { |m|
# Rename original method
self.send(:alias_method, "#{m}_orig", m)
# Redefine old method with instrumentation code added
define_method m do
puts "Perf log #{m}"
self.send "#{m}_orig"
puts "Perf write"
end
}
end
end
How to use it:
class Product < ApplicationController
def foo
puts "Foo"
end
def bar
puts "Bar"
end
# This has to be called last, once the original methods are defined
instrument_methods :foo, :bar
end
Then:
p = Product.new
p.foo
p.bar
Will output:
Perf log foo
Foo
Perf write
Perf log bar
Bar
Perf write
Here are some other ways to instrument ruby code and measure performance:
http://ruby-prof.rubyforge.org/
http://www.igvita.com/2009/06/13/profiling-ruby-with-googles-perftools/
Upvotes: 5