Module RubyRunInstrumentor__
In: lib/rubyrun/rubyrun_instrumentor__.rb

Methods

Included Modules

RubyRunGlobals RubyRunUtils__ RubyRunMonitor__ RubyRunTracer__ RubyRunDad__ RubyRunHTML__

Public Instance methods

This is the piece of code that actually executed under the application thread during runtime and is not executed during instrumentation time

  1. Create a equivalent thread local storage to store request performance

metrics but only if this class is a Rails Active Controller class

  1. Trace pre and post execution of the original method which has been aliased

The original method is invoked via ‘yield‘

  1. When a method ends, report the timings to the response time component but

only if a thread local exists

[Source]

     # 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:

  1. Alias the method to one with a prefix rubyrun_ (i.e., copy the method and

create another one with a new name)

  1. Create a new method with the original name. This is the method proxy.
  2. Preserve the intended visibility of the original method in the proxy
  3. This proxy method essentially adds pre and post wrapper code to

the original code. This wrapper code is embodied in collect_method_data

  1. All these must be done in the context of the class

[Source]

     # 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

[Source]

     # 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.

  1. Through APP_PATHS a stream of class names whose methods are

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

  1. Through additional INCLUDE_HASH in rubyrun_opts a stream of

class => methods hash entries provide further candidates for instrumentation.

  1. Thru EXCLUDE_HASH the exclusion logic is then applied to reduce the scope

of instrumentation.

  1. Some classes and methods are never instrumented regarldess. These

are identifed in constants FIREWALL_HASH.

[Source]

    # 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

[Source]

     # 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

[Source]

     # 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

  1. Exclude classes and methods that the instrumentation code uses
  2. Exclude method=
  3. Exclude method aliased by rubyrun instrumentation code
  4. Exclude method re-defined by rubyrun instrumentation code
  5. Exclude inherited instances, private, protected, and singleton methods.

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.

[Source]

    # 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

[Source]

     # 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

[Validate]