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
-
#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.
-
#insert_code_to_instance_method(klass, mid) ⇒ Object
To instrument an instance method of a class, a method proxy is used:.
-
#insert_code_to_singleton_method(klass, mid) ⇒ Object
Same as insert_code_to_instance_method.
-
#instrument_it?(type, klass, id) ⇒ Boolean
Invoked by the traps set up in rubyrun.rb.
-
#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).
-
#instrument_thread_new ⇒ Object
Instrument Thread.new by wrapping target proc with a begin-rescue clause around the application block.
-
#is_non_negotiably_excluded?(type, klass, id) ⇒ Boolean
Never instrument the following classes/methods to avoid recursion 1.
Methods included from RubyRunDad__
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_method_name
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
-
Create a equivalent thread local storage to store request performance
metrics but only if this class is a Rails Active Controller class
-
Trace pre and post execution of the original method which has been aliased
The original method is invoked via ‘yield’
-
When a method ends, report the timings to the response time component but
only if a thread local exists
184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 |
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 184 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) 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] && (t2 - t1) > 0 result end |
#insert_code_to_instance_method(klass, mid) ⇒ Object
To instrument an instance method of a class, a method proxy is used:
-
Alias the method to one with a prefix rubyrun_ (i.e., copy the method and
create another one with a new name)
-
Create a new method with the original name. This is the method proxy.
-
Preserve the intended visibility of the original method in the proxy
-
This proxy method essentially adds pre and post wrapper code to
the original code. This wrapper code is embodied in collect_method_data
-
All these must be done in the context of the class
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 |
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 140 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 if klass.private_instance_methods(false).include?(alias_name) private mid elsif klass.protected_instance_methods(false).include?(alias_name) protected mid end } end |
#insert_code_to_singleton_method(klass, mid) ⇒ Object
Same as insert_code_to_instance_method
158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 |
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 158 def insert_code_to_singleton_method(klass, mid) (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}, '#{mid}', *args) {self.send("#{alias_name}", *args, &blk)} end BODY2 if self.private_instance_methods(false).include?(alias_name) private mid elsif self.protected_instance_methods(false).include?(alias_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.
-
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
-
Through additional INCLUDE_HASH in rubyrun_opts a stream of
class => methods hash entries provide further candidates for instrumentation.
-
Thru EXCLUDE_HASH the exclusion logic is then applied to reduce the scope
of instrumentation.
-
Some classes and methods are never instrumented regarldess. These
are identifed in constants FIREWALL_HASH.
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
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 |
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 112 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_new ⇒ Object
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
210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/rubyrun/rubyrun_instrumentor__.rb', line 210 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. == 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
-
Exclude classes and methods that the instrumentation code uses
-
Exclude method=
-
Exclude method aliased by rubyrun instrumentation code
-
Exclude method re-defined by rubyrun instrumentation code
-
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.
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 |
# 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) 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 if type == 'i' klass.instance_methods(false).each {|m| return false if m == id.id2name } klass.private_instance_methods(false).each {|m| return false if m == id.id2name } klass.protected_instance_methods(false).each {|m| return false if m == id.id2name } else klass.singleton_methods.each {|m| return false if m == id.id2name } end true end |