Module | RubyRunInstrumentor__ |
In: |
lib/rubyrun/rubyrun_instrumentor__.rb
|
This is the piece of code that actually executed under the application thread during runtime and is not executed during instrumentation time
metrics but only if this class is a Rails Active Controller class
The original method is invoked via ‘yield‘
only if a thread local exists
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 184 184: def collect_method_data(obj, klass, mid, *args) 185: tid = get_thread_id 186: create_thread_local(tid, obj.request, klass, mid) if is_rails_controller?(klass, mid) 187: rubyrun_trace = is_in?($rubyrun_trace_hash, klass, mid) 188: if rubyrun_trace 189: invoker = get_caller_detail 190: enter_trace(tid, " Entry", obj, invoker, klass, mid, *args) 191: end 192: t1 = Time.new 193: result = yield 194: t2 = Time.new 195: if rubyrun_trace 196: (t2 - t1) >= RUBYRUN_HIGHLIGHT_THRESHOLD ? (type = "* #{sprintf("%6.2f", t2-t1)} Exit ") : (type = " #{sprintf("%6.2f", t2-t1)} Exit ") 197: enter_trace(tid, type, nil, nil, klass, mid, nil) 198: end 199: report_rails_timing(klass, mid, t2, t1, tid) if $rubyrun_thread_local[tid] && (t2 - t1) > 0 200: result 201: end
To instrument an instance method of a class, a method proxy is used:
create another one with a new name)
the original code. This wrapper code is embodied in collect_method_data
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 140 140: def insert_code_to_instance_method(klass, mid) 141: klass.class_eval { 142: alias_method "#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}", mid.id2name 143: eval "def \#{mid.id2name} (*args, &blk)\nRubyRunInstrumentor__.collect_method_data(self, \#{klass}, '\#{mid}', *args) {self.send(\"\#{RubyRunGlobals::RUBYRUN_PREFIX}_\#{mid.id2name}\", *args, &blk)}\nend\n" 144: if klass.private_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}") 145: private mid 146: elsif klass.protected_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}") 147: protected mid 148: end 149: } 150: end
Same as insert_code_to_instance_method
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 158 158: def insert_code_to_singleton_method(klass, mid) 159: (class << klass; self; end).class_eval { 160: alias_method "#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}", mid.id2name 161: eval "def \#{mid.id2name} (*args, &blk)\nRubyRunInstrumentor__.collect_method_data(self, \#{klass}, '\#{mid}', *args) {self.send(\"\#{RubyRunGlobals::RUBYRUN_PREFIX}_\#{mid.id2name}\", *args, &blk)}\nend\n" 162: if self.private_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}") 163: private mid 164: elsif self.protected_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}") 165: protected mid 166: end 167: } 168: end
Invoked by the traps set up in rubyrun.rb. This indicates that a file has been required/loaded such that the methods within are being added to the process. Each method being added will go through the following process to determine if it should be intrumented.
identified as candiates for instrumnetation in RubyRunIntializer__. This process forms the initial INCLUDE_HASH which states ALL methods belonging to these classes/methods should be instrumented
class => methods hash entries provide further candidates for instrumentation.
of instrumentation.
are identifed in constants FIREWALL_HASH.
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 53 53: def instrument_it?(type, klass, id) 54: get_dad(type, klass, id) 55: instrument_target(type, klass, id) \ 56: if !(is_non_negotiably_excluded?(type, klass, id)) && 57: !is_in?($rubyrun_exclude_hash, klass, id, 'strict') && 58: is_in?($rubyrun_include_hash, klass, id, 'strict') 59: end
First layer of code performing instrummentation on a class.method The injecting code is different depending on whether the method is an instance method, or singleton(static method of a class, specific method added to an object).
If this class is a Rails active controller class, create a hash entry if it does not already exist. This hash is used to keep track of performance metrics by action by controller.
If we fail to instrument for whatever reason, log the errors and leave the method alone.
Also, create metrics hash for a RAILS controller class if it doesn‘t exist
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 112 112: def instrument_target(type, klass, id) 113: $rubyrun_logger.info "instrumenting #{klass.to_s}.#{id.id2name}." 114: create_metrics_hash(klass) if is_rails_controller?(klass, id) 115: begin 116: case type 117: when 'i' 118: insert_code_to_instance_method(klass, id) 119: when 's' 120: insert_code_to_singleton_method(klass, id) 121: else 122: raise "undefined instrumentation type" 123: end 124: $rubyrun_logger.info "#{klass.to_s}.#{id.id2name} instrumented." 125: rescue Exception => e 126: $rubyrun_logger.info "Class #{klass.to_s}.#{id.id2name} failed to instrument" 127: $rubyrun_logger.info e.to_s + "\n" + e.backtrace.join("\n") 128: end 129: end
Instrument Thread.new by wrapping target proc with a begin-rescue clause around the application block. When the thread monitor shoot the thread via thr.raise the rescue clause will catch the interrupt and collect the stack entries in $@ and store them in a global hash, later on printed in rubyrun log by thread id. If the thread dies naturally, print the stack trace on the rubyrun log
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 210 210: def instrument_thread_new 211: (class << Thread; self; end).class_eval { 212: alias_method "#{RubyRunGlobals::RUBYRUN_PREFIX}_new", "new" 213: def new(*rubyrun_args, &rubyrun_apps_block) 214: rubyrun_proc = lambda { 215: begin 216: rubyrun_apps_block.call(*rubyrun_args) 217: rescue Exception => e 218: e.message == RUBYRUN_KILL_3_STRING ? 219: $@.each {|line| ($rubyrun_thread_stack[Thread.current] ||= []) << line} : 220: $@.each {|line| $rubyrun_logger.info "#{line}"} 221: end 222: } 223: self.send("#{RubyRunGlobals::RUBYRUN_PREFIX}_new", *rubyrun_args, &rubyrun_proc) 224: end 225: } 226: end
Never instrument the following classes/methods to avoid recursion
The way this works is that if m is one of these non-inherited instance methods or singleton methods then it should NOT be excluded. Otherwise it is assumed it is an inherited one and hence excluded.
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 70 70: def is_non_negotiably_excluded?(type, klass, id) 71: return true if is_in?(RUBYRUN_FIREWALL_HASH, klass, id) 72: return true if id.id2name[-1,1] == '=' 73: if id.id2name[0, RUBYRUN_PREFIX_LENGTH] == RUBYRUN_PREFIX 74: $rubyrun_prev_method = id.id2name 75: return true 76: end 77: if ($rubyrun_prev_method ||="").include?(id.id2name) 78: $rubyrun_prev_method = nil 79: return true 80: end 81: if type == 'i' 82: klass.instance_methods(false).each {|m| 83: return false if m == id.id2name 84: } 85: klass.private_instance_methods(false).each {|m| 86: return false if m == id.id2name 87: } 88: klass.protected_instance_methods(false).each {|m| 89: return false if m == id.id2name 90: } 91: else 92: klass.singleton_methods.each {|m| 93: return false if m == id.id2name 94: } 95: end 96: true 97: end
# File lib/rubyrun/rubyrun_instrumentor__.rb, line 213 213: def new(*rubyrun_args, &rubyrun_apps_block) 214: rubyrun_proc = lambda { 215: begin 216: rubyrun_apps_block.call(*rubyrun_args) 217: rescue Exception => e 218: e.message == RUBYRUN_KILL_3_STRING ? 219: $@.each {|line| ($rubyrun_thread_stack[Thread.current] ||= []) << line} : 220: $@.each {|line| $rubyrun_logger.info "#{line}"} 221: end 222: } 223: self.send("#{RubyRunGlobals::RUBYRUN_PREFIX}_new", *rubyrun_args, &rubyrun_proc) 224: end