Gran pregunta. Este es mi intento rápido de implementación (no intenté optimizar el código). Me tomé la libertad de agregar el método profile
a la clase Module
. De esta manera estará disponible en cada clase y definición de módulo. Sería incluso mejor extraerlo en un módulo y mezclarlo en la clase Module
siempre que lo necesite.
Yo tampoco sabía si el punto era hacer que el método profile
se comportan como public
/protected
/private
palabras clave de Ruby, pero me puso en práctica el estilo de todos modos. Todos los métodos definidos después de llamar a profile
son perfilados, hasta que se llama al noprofile
.
class Module
def profile
require "benchmark"
@profiled_methods ||= []
class << self
# Save any original method_added callback.
alias_method :__unprofiling_method_added, :method_added
# Create new callback.
def method_added(method)
# Possible infinite loop if we do not check if we already replaced this method.
unless @profiled_methods.include?(method)
@profiled_methods << method
unbound_method = instance_method(method)
define_method(method) do |*args|
puts "#{self.class}##{method} was called with params #{args.join(", ")}"
bench = Benchmark.measure do
unbound_method.bind(self).call(*args)
end
puts "#{self.class}##{method} finished in %.5fs" % bench.real
end
# Call the original callback too.
__unprofiling_method_added(method)
end
end
end
end
def noprofile # What's the opposite of profile?
class << self
# Remove profiling callback and restore previous one.
alias_method :method_added, :__unprofiling_method_added
end
end
end
Ahora puede utilizar de la siguiente manera:
class Foo
def self.method_added(method) # This still works.
puts "Method '#{method}' has been added to '#{self}'."
end
profile
def foo(arg1, arg2, arg3 = nil)
puts "> body of foo"
sleep 1
end
def bar(arg)
puts "> body of bar"
end
noprofile
def baz(arg)
puts "> body of baz"
end
end
llamar a los métodos como lo haría normalmente:
foo = Foo.new
foo.foo(1, 2, 3)
foo.bar(2)
foo.baz(3)
Y obtener una salida como punto de referencia (y el resultado del original method_added
de devolución de llamada solo para mostrar que todavía funciona):
Method 'foo' has been added to 'Foo'.
Method 'bar' has been added to 'Foo'.
Method 'baz' has been added to 'Foo'.
Foo#foo was called with params 1, 2, 3
> body of foo
Foo#foo finished in 1.00018s
Foo#bar was called with params 2
> body of bar
Foo#bar finished in 0.00016s
> body of baz
Una cosa a tener en cuenta es que es imposible obtener dinámicamente el nombre de los argumentos con la meta-programación de Ruby. Tendría que analizar el archivo Ruby original, que sin duda es posible pero un poco más complejo. Consulte las gemas parse_tree and ruby_parser para obtener más detalles.
Una mejora divertida sería poder definir este tipo de comportamiento con un método de clase en la clase Module
. Sería genial ser capaz de hacer algo como:
class Module
method_wrapper :profile do |*arguments|
# Do something before calling method.
yield *arguments # Call original method.
# Do something afterwards.
end
end
voy a dejar este ejercicio meta-meta-programación para otro momento. :-)
+1 respuesta fantástica. –