Skip to content

Instantly share code, notes, and snippets.

@markbates
Last active December 27, 2015 23:08
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save markbates/7403345 to your computer and use it in GitHub Desktop.
Save markbates/7403345 to your computer and use it in GitHub Desktop.
Code and slides from my RubyConf 2013 talk, "Mangling Ruby with TracePoint". Slides: http://www.slideshare.net/markykang/mangling-ruby-withtracepoint
require 'singleton'
tracer = TracePoint.new(:end) do |tp|
ancestors = tp.self.ancestors.dup
ancestors.delete(tp.self)
missing_methods = []
ancestors.each do |ancestor|
methods = AbstractMethodManager.instance.methods[ancestor]
if methods
methods.each do |m|
unless tp.self.instance_methods.include?(m)
missing_methods << m
end
end
end
end
if missing_methods.any?
raise AbstractInterface::NotImplementedError.new(*missing_methods)
end
end
tracer.enable
class AbstractMethodManager
include Singleton
attr_accessor :methods
def initialize
@methods = {}
end
def add(klass, *methods)
@methods[klass] ||= []
@methods[klass].concat(methods)
end
end
module AbstractInterface
class NotImplementedError < StandardError
def initialize(*methods)
super "You must implement the following methods: #{methods.join(', ')}"
end
end
class << self
def included(klass)
klass.extend(ClassMethods)
end
end
module ClassMethods
def abstract_method(*methods)
AbstractMethodManager.instance.add(self, *methods)
end
end
end
require_relative "./method_call_collector.rb"
class Foo
def bar
"bar"
end
end
foo = Foo.new
puts foo.bar
require_relative "./abstract_interfaces.rb"
module ApiInterface
include AbstractInterface
abstract_method :get, :put
end
class HttpLibrary
include ApiInterface
end
require 'singleton'
class TraceCollector
include Singleton
def initialize
@calls = {}
end
def collect(tp)
klass = tp.self.class.name
klass = klass ? klass : "NilClass"
items = @calls.dup
items[klass] ||= {}
items[klass][tp.method_id] ||= 0
items[klass][tp.method_id] += 1
@calls = items
end
def self.method_missing(name, *args, &block)
instance.send(name, *args, &block)
end
def each(&block)
@calls.each(&block)
end
end
tracers = []
[:c_call, :call].each do |event|
tracers << TracePoint.new(event) do |tp|
TraceCollector.collect(tp)
end
end
tracers.map(&:enable)
at_exit do
tracers.map(&:disable)
totals = {}
longest_name_size = 0
TraceCollector.each do |klass, methods|
if klass && klass.length > longest_name_size
longest_name_size = klass.length
end
totals[klass] ||= 0
puts "#{klass}:"
methods = methods.sort_by {|k, v| v}.reverse
methods.each do |method, count|
totals[klass] += count
printf "\t%-30s %s\n", method, count
end
puts "\n"
end
puts "-------------"
puts "Totals:\n"
totals = totals.sort_by {|k, v| v}.reverse
total_calls = 0
totals.each do |klass, total|
total_calls += total
printf "\t%-#{longest_name_size}s %s\n", klass, total
end
puts "\n\n Total Method Calls: #{total_calls}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment