Created
April 22, 2021 19:29
-
-
Save mwlang/ef1693378fbea3e0e5349ca4c8965599 to your computer and use it in GitHub Desktop.
Tracer exploration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# This Tracer module is an overly simplified demonstration of what is currently implemented | |
# in our MethodTracer module. It shows the alternative implementation that completely eliminates | |
# the string evaluated code. | |
# | |
# This implementation also shows how to handle keyword arguments across Ruby versions | |
# Whether to trace with and without scope is not presented here, but it's fairly straightforward | |
# to conditionally handle options passed accordingly. | |
module Tracer | |
module_function | |
# expose tracer methods at class and instance level | |
def self.included instrumented_class | |
instrumented_class.extend self | |
end | |
# Only invoke a callback if one was provided | |
def trace_callback proc | |
proc.call unless proc.nil? | |
end | |
# This is only place we're conditional on Ruby versions and the main difference | |
# is the additional **kwargs parameter. | |
# | |
# NOTE: personally, I think we should get away from conditional declarations like this and | |
# factor out to individual files that are then conditionally loaded. | |
# For example, if we didn't define the below, but had a ruby2.rb and ruby3.rb with the two | |
# variations, then we'd simply do something like this: | |
# require_relative RUBY_VERSION < "2.7.0" ? "ruby2" : "ruby3" | |
def wrap_with_tracer method_name, metric_name, options | |
if RUBY_VERSION < "2.7.0" | |
define_method method_name do |*args, &block| | |
with_tracing(method_name, metric_name, options, *args) { super(*args, &block) } | |
end | |
else | |
define_method method_name do |*args, **kwargs, &block| | |
with_tracing(method_name, metric_name, options, *args) { super(*args, **kwargs, &block) } | |
end | |
end | |
end | |
# Mostly taken as-is from the MethodTracer module, but quickly tweaked to deal with | |
# using prepend to attach tracing to methods. WARNING: almost certainly has bugs! | |
def derived_class_name | |
return self.name if respond_to?(:name) && !self.name.empty? | |
name = method(__method__).receiver.to_s | |
return "AnonymousModule" if name.start_with?("#<Module:") | |
# trying to get the "MyClass" portion of "#<Class:MyClass>" | |
if class_name = name[/^#<Class:(.+)>$/, 1] | |
if class_name.start_with?("0x") | |
"AnonymousClass" | |
elsif class_name.start_with?("#<Class:") | |
"AnonymousClass/Class" | |
else | |
"#{class_name}/Class" | |
end | |
else | |
name | |
end | |
end | |
def default_metric_name method_name, options | |
"Custom/#{derived_class_name}/#{method_name}" | |
end | |
# everything that needs to be accessed as part of our tracing logic needs to be | |
# passed in as method parameters | |
def with_tracing method_name, metric_name, options, *args | |
begin | |
trace_callback(options[:header]) | |
start_time = Time.now | |
yield | |
ensure | |
duration = (Time.now - start_time).to_f * 1000 | |
metric_name ||= default_metric_name(method_name, options) | |
resolved_metric_name = metric_name.is_a?(Proc) ? metric_name.call(method_name, *args) : metric_name | |
puts "#{resolved_metric_name} : #{duration.round(3)}" | |
trace_callback(options[:footer]) | |
end | |
end | |
# Trace is our "API" to wrapping a tracer around a given customer's method. This approach | |
# is building an anonymous module that we then prepend to the module where the given method | |
# was originally defined. | |
def trace method_name, metric_name=nil, options={} | |
tracer = Module.new do | |
include Tracer | |
wrap_with_tracer method_name, metric_name, options | |
end | |
prepend tracer | |
end | |
end | |
# ################################################################# | |
# The examples below here demonstrates the code above. | |
# ################################################################# | |
class Foo | |
include Tracer | |
def speak a, b | |
puts "#{a} says #{b}" | |
end | |
trace :speak | |
trace def bark! | |
puts "woof!" | |
end | |
end | |
Foo.new.speak("John", "Hello!") | |
Foo.new.bark! | |
# This is a fairly complete demonstration of all the bells and whistles | |
class Bar | |
include Tracer | |
def speak a, b | |
puts "#{a} said #{b}" | |
end | |
trace :speak, | |
-> (method_name, *args) {"Custom/#{args[0]}/#{method_name}/#{Time.now.to_i}"}, | |
header: -> { puts "*" * 40 }, | |
footer: -> { puts "footer" } | |
end | |
Bar.new.speak("Sally", "Howdy!") | |
Bar.new.speak("Homer", "Yo") |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Tracer | |
module_function | |
def wrap_with_tracer method_name, metric_name, options | |
define_method method_name do |*args, &block| | |
with_tracing(method_name, metric_name, options) { super(*args, &block) } | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
module Tracer | |
module_function | |
def wrap_with_tracer method_name, metric_name, options | |
define_method method_name do |*args, **kwargs, &block| | |
with_tracing(method_name, metric_name, options) { super(*args, **kwargs, &block) } | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment