Module: RubyRunInstrumentor__

Includes:
RubyRunDad__, RubyRunGlobals, RubyRunHTML__, RubyRunMonitor__, RubyRunTracer__, RubyRunUtils__
Defined in:
lib/rubyrun/rubyrun_instrumentor__.rb

Overview

—————————————————————#

                                                             #  
(C) Copyright Rubysophic Inc. 2007-2008                      #
All rights reserved.                                         #
                                                             #  
Use, duplication or disclosure of the code is not permitted  #
unless licensed.                                             #  
                                                             #  
Last Updated: 7/09/08                                        #

—————————————————————#

#

RubyRunInstrumentor__ module is responsible for performing # instrumentation on ruby methods via metaprogramming. These # methods belong to the candidate classes/modules discovered # earlier in RubyRunInitializer__ plus others which are # explicitly requested in rubyrun_opts via the use of # INCLUDE_HASH. #

#

—————————————————————#

Constant Summary

Constants included from RubyRunHTML__

RubyRunHTML__::METHOD_TRACE_EVEN_ROW, RubyRunHTML__::METHOD_TRACE_HEADER, RubyRunHTML__::METHOD_TRACE_ODD_ROW, RubyRunHTML__::OBJ_MAP_EVEN_ROW, RubyRunHTML__::OBJ_MAP_HTML, RubyRunHTML__::OBJ_MAP_ODD_ROW, RubyRunHTML__::REQ_PERF_BREAKDOWN_HTML, RubyRunHTML__::REQ_PERF_BREAKDOWN_TABLE_EVEN, RubyRunHTML__::REQ_PERF_BREAKDOWN_TABLE_ODD, RubyRunHTML__::THREAD_STATUS_EVEN_ROW, RubyRunHTML__::THREAD_STATUS_HTML, RubyRunHTML__::THREAD_STATUS_ODD_ROW, RubyRunHTML__::THROUGHPUT_BAR_TABLE, RubyRunHTML__::THROUGHPUT_HTML, RubyRunHTML__::THROUGHPUT_LABEL_TABLE, RubyRunHTML__::TOP_SLOWEST_REQUESTS_HTML, RubyRunHTML__::TOP_SLOWEST_REQUESTS_TABLE

Constants included from RubyRunGlobals

RubyRunGlobals::RUBYRUN_ACTIVERECORD, RubyRunGlobals::RUBYRUN_CMD_EXIT, RubyRunGlobals::RUBYRUN_CMD_HARD_KILL, RubyRunGlobals::RUBYRUN_CMD_OBJECT_MAP, RubyRunGlobals::RUBYRUN_CMD_SOFT_KILL, RubyRunGlobals::RUBYRUN_CMD_STATUS, RubyRunGlobals::RUBYRUN_DIR_HASH_FILE, RubyRunGlobals::RUBYRUN_DOC_DIR, RubyRunGlobals::RUBYRUN_ETC_DIR, RubyRunGlobals::RUBYRUN_EXCLUDE_HASH_FILE, RubyRunGlobals::RUBYRUN_FIREWALL_HASH, RubyRunGlobals::RUBYRUN_HIGHLIGHT_THRESHOLD, RubyRunGlobals::RUBYRUN_INCLUDE_HASH_FILE, RubyRunGlobals::RUBYRUN_INNER_DISPATCH_HASH, RubyRunGlobals::RUBYRUN_KILL_3_STRING, RubyRunGlobals::RUBYRUN_LOG, RubyRunGlobals::RUBYRUN_MONITOR_TIMER, RubyRunGlobals::RUBYRUN_OPTS_FILE, RubyRunGlobals::RUBYRUN_OUTER_DISPATCH_HASH, RubyRunGlobals::RUBYRUN_OUTPUT_PERF_SUMMARY, RubyRunGlobals::RUBYRUN_OUTPUT_TXN_LOG, RubyRunGlobals::RUBYRUN_PREFIX, RubyRunGlobals::RUBYRUN_PREFIX_LENGTH, RubyRunGlobals::RUBYRUN_PROP_DEFAULTS, RubyRunGlobals::RUBYRUN_REPORT, RubyRunGlobals::RUBYRUN_SIGNATURE, RubyRunGlobals::RUBYRUN_STARTUP_ID_TYPE_PORT, RubyRunGlobals::RUBYRUN_STARTUP_ID_TYPE_PROCESS, RubyRunGlobals::RUBYRUN_THREAD_END_HASH, RubyRunGlobals::RUBYRUN_VIEW_HASH, RubyRunGlobals::RUBYRUN_WORKING_DIR_NAME

Instance Method Summary collapse

Methods included from RubyRunDad__

#get_dad

Methods included from RubyRunTracer__

#back_trace_all, #enter_trace, #write_trace

Methods included from RubyRunUtils__

#env_var_exists?, #fatal_exit, #get_caller_detail, #get_thread_id, #is_action?, #is_application_controller, #is_in?, #is_rails_controller?, #return_class_name, #return_method_name, #return_mid

Methods included from RubyRunMonitor__

#create_metrics_hash, #create_thread_local, #report_rails_timing, #start_thread_monitor

Methods included from RubyRunCommander__

#dump_object_map, #dump_reports, #dump_thread_status, #exit_monitor?, #hard_kill?, #kill_threads, #object_map?, #remove_cmd_folder, #return_joined_thread, #soft_kill?, #thread_status?, #unsupport_function, #update_perf_metrics

Methods included from RubyRunReport__

#add_perf_summary_rss_item, #add_txn_log_csv_item, #create_csv_files, #create_rss_channels

Methods included from RubyRunBufferMgr__

#push_current_buffer, #return_and_switch_buffer

Instance Method Details

#collect_method_data(obj, klass, mid, *args) ⇒ Object

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



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 188

def collect_method_data(obj, klass, mid, *args)
  tid = get_thread_id
  create_thread_local(tid, obj.request, klass, mid) if is_rails_controller?(klass) && obj.respond_to?('request')
  rubyrun_trace = is_in?($rubyrun_trace_hash, klass, mid)
  if rubyrun_trace
    invoker = get_caller_detail
    enter_trace(tid, "         Entry", obj, invoker, klass, mid, *args)
  end
  t1 = Time.new
  result = yield
  t2 = Time.new
  if rubyrun_trace
    (t2 - t1) >= RUBYRUN_HIGHLIGHT_THRESHOLD ? (type = "* #{sprintf("%6.2f", t2-t1)} Exit ") : (type = "  #{sprintf("%6.2f", t2-t1)} Exit ")
    enter_trace(tid, type, nil, nil, klass, mid, nil) 
  end
  report_rails_timing(klass, mid, t2, t1, tid) if $rubyrun_thread_local[tid] 
  result
end

#insert_code_to_instance_method(klass, mid) ⇒ Object

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



141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 141

def insert_code_to_instance_method(klass, mid)
  klass.class_eval {
    alias_name = "#{RubyRunGlobals::RUBYRUN_PREFIX}_#{rand.to_s.split('.')[1]}_#{mid.id2name}" 
    alias_method alias_name, mid.id2name
    eval <<-BODY1
      def #{mid.id2name} (*args, &blk)
        RubyRunInstrumentor__.collect_method_data(self, #{klass}, '#{mid}', *args) {self.send("#{alias_name}", *args, &blk)} 
      end
    BODY1
    m_name = return_mid(alias_name)
    if klass.private_instance_methods(false).include?(m_name)
      private mid
    elsif klass.protected_instance_methods(false).include?(m_name)
      protected mid
    end
  }
end

#insert_code_to_singleton_method(klass, mid) ⇒ Object

Same as insert_code_to_instance_method



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 160

def insert_code_to_singleton_method(klass, mid)
  klass_name = return_class_name(klass)
  (class << klass; self; end).class_eval {
    alias_name = "#{RubyRunGlobals::RUBYRUN_PREFIX}_#{rand.to_s.split('.')[1]}_#{mid.id2name}" 
    alias_method alias_name, mid.id2name
    eval <<-BODY2
      def #{mid.id2name} (*args, &blk)
         RubyRunInstrumentor__.collect_method_data(self, #{klass_name}, '#{mid}', *args) {self.send("#{alias_name}", *args, &blk)}
      end
    BODY2
    m_name = return_mid(alias_name)
    if self.private_instance_methods(false).include?(m_name)
      private mid
    elsif self.protected_instance_methods(false).include?(m_name)
      protected mid
    end
  }
end

#instrument_it?(type, klass, id) ⇒ Boolean

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.

Returns:

  • (Boolean)


53
54
55
56
57
58
59
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 53

def instrument_it?(type, klass, id)
  get_dad(type, klass, id)
  instrument_target(type, klass, id) \
    if !(is_non_negotiably_excluded?(type, klass, id)) &&
      !is_in?($rubyrun_exclude_hash, klass, id, 'strict') &&
      is_in?($rubyrun_include_hash, klass, id, 'strict')
end

#instrument_target(type, klass, id) ⇒ Object

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


113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 113

def instrument_target(type, klass, id)
  $rubyrun_logger.info "instrumenting #{klass.to_s}.#{id.id2name}."
  create_metrics_hash(klass) if is_rails_controller?(klass) 
  begin 
    case type
      when 'i'
        insert_code_to_instance_method(klass, id)
      when 's' 
        insert_code_to_singleton_method(klass, id)
      else
        raise "undefined instrumentation type"
    end
    $rubyrun_logger.info "#{klass.to_s}.#{id.id2name} instrumented."
  rescue Exception => e
    $rubyrun_logger.info "Class #{klass.to_s}.#{id.id2name} failed to instrument"
    $rubyrun_logger.info e.to_s + "\n" + e.backtrace.join("\n")
  end
end

#instrument_thread_newObject

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



214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 214

def instrument_thread_new
  (class << Thread; self; end).class_eval {
    alias_method "#{RubyRunGlobals::RUBYRUN_PREFIX}_new", "new"
    def new(*rubyrun_args, &rubyrun_apps_block)
      rubyrun_proc = lambda {
        begin    
          rubyrun_apps_block.call(*rubyrun_args)
        rescue Exception => e
          e.message == RUBYRUN_KILL_3_STRING ? 
            $@.each {|line| ($rubyrun_thread_stack[Thread.current] ||= []) << line} :
            $@.each {|line| $rubyrun_logger.info "#{line}"}
        end
      }    
      self.send("#{RubyRunGlobals::RUBYRUN_PREFIX}_new", *rubyrun_args, &rubyrun_proc)
    end
  }
end

#is_non_negotiably_excluded?(type, klass, id) ⇒ Boolean

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.

Returns:

  • (Boolean)


70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 70

def is_non_negotiably_excluded?(type, klass, id)    
  return true if is_in?(RUBYRUN_FIREWALL_HASH, klass, id, mode='strict')
  return true if id.id2name[-1,1] == '='  
  if id.id2name[0, RUBYRUN_PREFIX_LENGTH] == RUBYRUN_PREFIX
    $rubyrun_prev_method = id.id2name
    return true
  end
  if ($rubyrun_prev_method ||="").include?(id.id2name)
    $rubyrun_prev_method = nil
    return true
  end
  m_name = return_mid(id)
  if type == 'i'
    klass.instance_methods(false).each {|m|
      return false if m == m_name
    }
    klass.private_instance_methods(false).each {|m|
      return false if m == m_name
    }
    klass.protected_instance_methods(false).each {|m|
      return false if m == m_name
    }
  else
    klass.singleton_methods.each {|m|
      return false if m == m_name
    }
  end
  true
end