rubyrun 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. data/LICENSE +13 -0
  2. data/README +77 -0
  3. data/Rakefile +274 -0
  4. data/bin/confgure +2 -0
  5. data/docs/rubyrun-0.9.0.htm +6344 -0
  6. data/docs/rubyrun-0.9.0.pdf +0 -0
  7. data/docs/rubyrun-0.9.0_files/colorschememapping.xml +2 -0
  8. data/docs/rubyrun-0.9.0_files/filelist.xml +29 -0
  9. data/docs/rubyrun-0.9.0_files/header.htm +141 -0
  10. data/docs/rubyrun-0.9.0_files/image001.jpg +0 -0
  11. data/docs/rubyrun-0.9.0_files/image002.jpg +0 -0
  12. data/docs/rubyrun-0.9.0_files/image003.jpg +0 -0
  13. data/docs/rubyrun-0.9.0_files/image004.jpg +0 -0
  14. data/docs/rubyrun-0.9.0_files/image005.jpg +0 -0
  15. data/docs/rubyrun-0.9.0_files/image006.jpg +0 -0
  16. data/docs/rubyrun-0.9.0_files/image007.jpg +0 -0
  17. data/docs/rubyrun-0.9.0_files/image008.jpg +0 -0
  18. data/docs/rubyrun-0.9.0_files/image009.jpg +0 -0
  19. data/docs/rubyrun-0.9.0_files/image010.jpg +0 -0
  20. data/docs/rubyrun-0.9.0_files/image011.jpg +0 -0
  21. data/docs/rubyrun-0.9.0_files/image012.jpg +0 -0
  22. data/docs/rubyrun-0.9.0_files/image013.jpg +0 -0
  23. data/docs/rubyrun-0.9.0_files/image014.jpg +0 -0
  24. data/docs/rubyrun-0.9.0_files/image015.jpg +0 -0
  25. data/docs/rubyrun-0.9.0_files/image016.jpg +0 -0
  26. data/docs/rubyrun-0.9.0_files/image017.png +0 -0
  27. data/docs/rubyrun-0.9.0_files/image018.jpg +0 -0
  28. data/docs/rubyrun-0.9.0_files/image019.jpg +0 -0
  29. data/docs/rubyrun-0.9.0_files/image020.jpg +0 -0
  30. data/docs/rubyrun-0.9.0_files/image021.jpg +0 -0
  31. data/docs/rubyrun-0.9.0_files/image022.png +0 -0
  32. data/docs/rubyrun-0.9.0_files/themedata.thmx +0 -0
  33. data/etc/rubyrun_opts.yml +132 -0
  34. data/ext/extconf.rb +4 -0
  35. data/ext/rubyrunnative__.bundle +0 -0
  36. data/ext/rubyrunnative__.c +154 -0
  37. data/ext/rubyrunnative__.def +2 -0
  38. data/ext/rubyrunnative__.h +36 -0
  39. data/ext/rubyrunnative__.so +0 -0
  40. data/ext/rubyrunnative__i386.bundle +0 -0
  41. data/ext/rubyrunnative__linux.so +0 -0
  42. data/lib/rubyrun/rubyrun.rb +78 -0
  43. data/lib/rubyrun/rubyrun_buffer_mgr__.rb +49 -0
  44. data/lib/rubyrun/rubyrun_commander__.rb +196 -0
  45. data/lib/rubyrun/rubyrun_dad__.rb +35 -0
  46. data/lib/rubyrun/rubyrun_globals.rb +51 -0
  47. data/lib/rubyrun/rubyrun_html__.rb +136 -0
  48. data/lib/rubyrun/rubyrun_html_writer__.rb +64 -0
  49. data/lib/rubyrun/rubyrun_initializer__.rb +286 -0
  50. data/lib/rubyrun/rubyrun_instrumentor__.rb +226 -0
  51. data/lib/rubyrun/rubyrun_monitor__.rb +237 -0
  52. data/lib/rubyrun/rubyrun_report__.rb +109 -0
  53. data/lib/rubyrun/rubyrun_rss__.rb +97 -0
  54. data/lib/rubyrun/rubyrun_tracer__.rb +79 -0
  55. data/lib/rubyrun/rubyrun_utils__.rb +98 -0
  56. data/lib/rubyrunm.rb +10 -0
  57. metadata +112 -0
@@ -0,0 +1,286 @@
1
+ #---------------------------------------------------------------#
2
+ # #
3
+ # (C) Copyright Rubysophic Inc. 2007-2008 #
4
+ # All rights reserved. #
5
+ # #
6
+ # Use, duplication or disclosure of the code is not permitted #
7
+ # unless licensed. #
8
+ # #
9
+ # Last Updated: 7/09/08 #
10
+ #---------------------------------------------------------------#
11
+ # #
12
+ # RubyRunInitializer__ sets up the environment for RubyRun. #
13
+ # The major task is to identify the application classes and #
14
+ # modules that are potential candidates for instrumentation. #
15
+ # #
16
+ # Also as at this release methods with super keyword is not #
17
+ # supported. These methods will need to be identified upfront. #
18
+ # #
19
+ #---------------------------------------------------------------#
20
+ module RubyRunInitializer__
21
+
22
+ require 'logger'
23
+ require 'yaml'
24
+ require 'digest/md5'
25
+ require 'rubyrun_globals'
26
+ require 'rubyrun_utils__'
27
+ include RubyRunGlobals
28
+ include RubyRunUtils__
29
+
30
+ # 1. Get all directories, logs and properties set up correctly
31
+ # 2. Scan files in APP_PATHS to create an initial INCLUDE_HASH
32
+ # and an EXCLUDE_HASH
33
+ def init_rubyrun
34
+ ready_rubyrun_env
35
+ discover_targets
36
+ end
37
+
38
+ # 1. Extract the working directory from ENV VAR and recursively create all the
39
+ # subdirectories if they dont exist
40
+ # 2. Create the log and trace folers
41
+ # 3. Initialize the loggers
42
+ # 4. Load the properties from either the current working directory or
43
+ # rubyrun working directory
44
+ # 5. Spawn a separate thread to for monitoring and commands
45
+ def ready_rubyrun_env
46
+ begin
47
+ raise RuntimeError, "environment variable #{RUBYRUN_WORKING_DIR} not set", caller \
48
+ unless env_var_exists?(RUBYRUN_WORKING_DIR)
49
+ rubyrun_working_dir = ENV[RUBYRUN_WORKING_DIR]
50
+ raise RuntimeError, "Missing #{RUBYRUN_OPTS_FILE} in #{rubyrun_working_dir}", caller \
51
+ unless File.exists?(rubyrun_working_dir + RUBYRUN_OPTS_FILE)
52
+ rescue Exception => e
53
+ fatal_exit(e)
54
+ end
55
+ *rubyrun_folders = rubyrun_working_dir + RUBYRUN_LOG, rubyrun_working_dir + RUBYRUN_REPORT, rubyrun_working_dir + RUBYRUN_SIGNATURE
56
+ make_folder(rubyrun_folders)
57
+ @rubyrun_log_folder, @rubyrun_report_folder, @rubyrun_signature_folder = *rubyrun_folders
58
+ logname = File.basename($0, ".*")
59
+ ready_logfile(logname)
60
+ system_wide_opts = rubyrun_working_dir + RUBYRUN_OPTS_FILE
61
+ app_wide_opts = Dir.getwd + '/' + RUBYRUN_OPTS_FILE
62
+ File.exists?(app_wide_opts) ? load_config_props(app_wide_opts) : load_config_props(system_wide_opts)
63
+ $rubyrun_tracer = RubyRunHTMLWriter.new(@rubyrun_report_folder + '/' + logname + '_' + $$.to_s + '_trace.html', METHOD_TRACE_HEADER, shift_age = 10, shift_size = 4096000) unless $rubyrun_trace_hash.empty?
64
+ end
65
+
66
+ # Make sub-directories (folders)
67
+ def make_folder(rubyrun_folders)
68
+ rubyrun_folders.each {|rubyrun_folder|
69
+ begin
70
+ Dir.mkdir(rubyrun_folder) unless File.exist?(rubyrun_folder)
71
+ rescue Exception => e
72
+ fatal_exit(e)
73
+ end
74
+ }
75
+ end
76
+
77
+ # Initialize rubyrun logger and create its own log format
78
+ def ready_logfile(logname)
79
+ $rubyrun_logger = Logger.new(@rubyrun_log_folder + '/' + logname + '_' + $$.to_s + '.log', shift_age = 10, shift_size = 4096000)
80
+ $rubyrun_logger.level = Logger::INFO
81
+ class << $rubyrun_logger
82
+ include RubyRunUtils__
83
+ def format_message (severity, timestamp, progname, msg)
84
+ "[#{timestamp.strftime("%Y-%m-%d %H:%M:%S")}.#{("%.3f" % timestamp.to_f).split('.')[1]}] #{get_thread_id} #{msg}\n"
85
+ end
86
+ end
87
+ end
88
+
89
+ # Property file is a yml file.
90
+ # Load the properties into $config hash
91
+ def load_config_props(config_file)
92
+ begin
93
+ $rubyrun_config = YAML.load_file(config_file)
94
+ $rubyrun_logger.info "Properties found in #{config_file}:"
95
+ RUBYRUN_PROP_DEFAULTS.each {|prop, def_value|
96
+ $rubyrun_config[prop] = def_value unless config_prop_exists?(prop)
97
+ $rubyrun_logger.info "#{prop} = #{$rubyrun_config[prop].inspect}"
98
+ }
99
+ $rubyrun_logger.info "***** APP_PATHS is nil. Applications will not be instrumented. *****" \
100
+ if $rubyrun_config['APP_PATHS'].empty?
101
+ rescue Exception => e
102
+ fatal_exit(e)
103
+ end
104
+ $rubyrun_debug_args = $rubyrun_config['DEBUG_ARGS']
105
+ $rubyrun_debug_obj = $rubyrun_config['DEBUG_OBJ']
106
+ $rubyrun_dad = $rubyrun_config['DAD']
107
+ $rubyrun_report_timer = $rubyrun_config['REPORT_TIMER']
108
+ $rubyrun_report_shift_age = $rubyrun_config['REPORT_SHIFT_AGE']
109
+ $rubyrun_trace_hash = $rubyrun_config['TRACE_HASH']
110
+ $rubyrun_adapter_hash = $rubyrun_config['DB_ADAPTER_HASH']
111
+ validate_opts
112
+ end
113
+
114
+ # Validate the range of REPORT_TIMER and REPORT_SHIFT_AGE
115
+ # Use default values if out of acceptable range
116
+ def validate_opts
117
+ if $rubyrun_report_timer > 3600 || $rubyrun_report_timer < 60
118
+ $rubyrun_report_timer = RUBYRUN_PROP_DEFAULTS['REPORT_TIMER']
119
+ $rubyrun_logger.warn "REPORT_TIMER value must be between 60 and 3600. #{$rubyrun_report_timer}is used."
120
+ end
121
+ if $rubyrun_report_shift_age > 120 || $rubyrun_report_shift_age < 1
122
+ $rubyrun_report_shift_age = RUBYRUN_PROP_DEFAULTS['REPORT_SHIFT_AGE']
123
+ $rubyrun_logger.warn "REPORT_SHIFT_AGE value must be between 1 and 120. #{$rubyrun_report_shift_age}is used."
124
+ end
125
+ end
126
+
127
+ # If the property is not defined or defined but with nil value, it
128
+ # is deemed to be non-existent
129
+ def config_prop_exists?(prop)
130
+ $rubyrun_config.has_key?(prop) && !$rubyrun_config[prop].nil? ? true : false
131
+ end
132
+
133
+ # Set up global variables from the property file rubyrun_config.yml
134
+ # For APP_PATHS property, expand any subdirectories and look for
135
+ # .rb files recursively
136
+ def discover_targets
137
+ identify_candidates
138
+ $rubyrun_include_hash.merge!($rubyrun_config['INCLUDE_HASH'])
139
+ ['DB_ADAPTER_HASH','OUTER_DISPATCH_HASH','INNER_DISPATCH_HASH'].each { |hash_key|
140
+ $rubyrun_include_hash.merge!($rubyrun_config[hash_key]) { |k,o,n| o.concat(n) } if config_prop_exists?(hash_key)
141
+ }
142
+ [RUBYRUN_OUTER_DISPATCH_HASH,RUBYRUN_INNER_DISPATCH_HASH,RUBYRUN_THREAD_END_HASH,RUBYRUN_VIEW_HASH].each {|hash|
143
+ $rubyrun_include_hash.merge!(hash) { |k,o,n| o.concat(n) }
144
+ }
145
+ $rubyrun_exclude_hash.merge!($rubyrun_config['EXCLUDE_HASH'])
146
+ $rubyrun_outer_dispatch_hash = config_prop_exists?('OUTER_DISPATCH_HASH') ? RUBYRUN_OUTER_DISPATCH_HASH.merge($rubyrun_config['OUTER_DISPATCH_HASH']) : RUBYRUN_OUTER_DISPATCH_HASH
147
+ $rubyrun_inner_dispatch_hash = config_prop_exists?('INNER_DISPATCH_HASH') ? RUBYRUN_INNER_DISPATCH_HASH.merge($rubyrun_config['INNER_DISPATCH_HASH']) : RUBYRUN_INNER_DISPATCH_HASH
148
+ $rubyrun_logger.info "Final INCLUDE_HASH = #{$rubyrun_include_hash.inspect}"
149
+ $rubyrun_logger.info "Final EXCLUDE_HASH = #{$rubyrun_exclude_hash.inspect}"
150
+ end
151
+
152
+ # If the directory content has changed (new files, modified files,
153
+ # new APP_PATHS) a new scan will be required, otherwise the last scan
154
+ # results will simply be reused. When a new scan is finished,
155
+ # the results are serialized.
156
+ #
157
+ # Digest technique is used to detect changes in directory content.
158
+ def identify_candidates
159
+ $rubyrun_config['APP_PATHS'].each {|element|
160
+ element = Dir.getwd if element == '.'
161
+ expand_folder(element)
162
+ }
163
+ unless $rubyrun_file_date_hash.empty?
164
+ dir_signature = generate_hash($rubyrun_file_date_hash)
165
+ exclude_array = []
166
+ if directory_changed?(dir_signature)
167
+ $rubyrun_file_date_hash.each {|f, date|
168
+ scan_module_class(f)
169
+ scan_super(f).each {|name| exclude_array << name}
170
+ }
171
+ $rubyrun_exclude_hash['*'] = exclude_array.uniq if exclude_array.length > 0
172
+ serialize_scan_history(dir_signature)
173
+ end
174
+ end
175
+ deserialize_scan_history
176
+ $rubyrun_file_date_hash.clear if $rubyrun_file_date_hash
177
+ end
178
+
179
+ # For each directory, expand into a list of filenames and its modifed time.
180
+ def expand_folder(folder)
181
+ ($rubyrun_logger.warn("WARN: APP_PATHS not found: #{folder}") ; return) unless File.exists?(folder)
182
+ if File.file?(folder)
183
+ return if File.extname(folder) != '.rb'
184
+ path = File.expand_path(folder)
185
+ $rubyrun_file_date_hash[path] = File.mtime(path).to_i
186
+ else
187
+ Dir.entries(folder).each {|entry|
188
+ next if entry =~ /^\.+/
189
+ path = File.expand_path(entry, folder)
190
+ case
191
+ when File.directory?(path)
192
+ expand_folder(path)
193
+ else
194
+ next if File.extname(entry) != '.rb'
195
+ $rubyrun_file_date_hash[path] = File.mtime(path).to_i
196
+ end
197
+ }
198
+ end
199
+ end
200
+
201
+ # Compare the digest calculated from the current APP_PATHS directory
202
+ # contents to the last serialized one. Return true(changed) or false
203
+ # (unchanged)
204
+ def directory_changed?(dir_signature)
205
+ f = get_dir_hash_file
206
+ dir_sig = File.exists?(f) ? File.open(f) {|f| Marshal.load(f)} : nil
207
+ dir_signature != dir_sig
208
+ end
209
+
210
+ # Calcualte the digest from the directory contents of APP_PATHS
211
+ def generate_hash(hash)
212
+ Digest::MD5.hexdigest(hash.to_s)
213
+ end
214
+
215
+ # Use Marshal to serialize scan results (the include and exclude hashes)
216
+ def serialize_scan_history(dir_signature)
217
+ File.open(get_include_hash_file, 'w') {|f| Marshal.dump($rubyrun_include_hash, f)}
218
+ File.open(get_exclude_hash_file, 'w') {|f| Marshal.dump($rubyrun_exclude_hash, f)}
219
+ File.open(get_dir_hash_file, 'w') {|f| Marshal.dump(dir_signature, f)}
220
+ end
221
+
222
+ # Use Marshal to de-serialize the include and exclude hashes
223
+ def deserialize_scan_history
224
+ f = get_include_hash_file
225
+ $rubyrun_include_hash = File.exists?(f) ? File.open(f) {|f| Marshal.load(f)} : {}
226
+ f = get_exclude_hash_file
227
+ $rubyrun_exclude_hash = File.exists?(f) ? File.open(f) {|f| Marshal.load(f)} : {}
228
+ end
229
+
230
+ # Regular expression that detects the class or module names of a given file
231
+ def scan_module_class(path)
232
+ $rubyrun_logger.info "Candidate modules/classes for instrumentation in #{path}:"
233
+ open(path).grep(/^\s*(class|module)\s+([[:upper:]][A-Za-z0-9_:]*)/m) {
234
+ $rubyrun_logger.info "\t #{$2}"
235
+ $rubyrun_include_hash[$2] = []
236
+ }
237
+ end
238
+
239
+ # Line up all the method names in a file by line #
240
+ # Line up all the line numbers that contain the keyworkd super
241
+ # Collate the two arrays to determine which method has super in it
242
+ # Put these method names in EXCLUDE_HASH and skip instrumentation
243
+ def scan_super(path)
244
+ $rubyrun_logger.info "Method(s) to be excluded from instrumentation in #{path}:"
245
+ method_name_array, method_lineno_array, super_array, exclude_array = [],[],[],[]
246
+ open(path) {|f|
247
+ f.readlines.each_with_index {|code, lineno|
248
+ code.scan(/^\s*def\s+(.*)\(+.*\)+/m)
249
+ (method_name_array << $1; method_lineno_array << lineno) if $1
250
+ code.scan(/^\s*(super)/m)
251
+ super_array << lineno if $1
252
+ }
253
+ }
254
+ method_name_array.reverse!
255
+ method_lineno_array.reverse!
256
+ super_array.each {|lineno|
257
+ method_lineno_array.each_with_index {|linenum, i|
258
+ unless lineno < linenum
259
+ $rubyrun_logger.info "\t #{method_name_array[i]}"
260
+ m = method_name_array[i].split('.')
261
+ exclude_array << (m.length > 1 ? m[1] : m[0])
262
+ break
263
+ end
264
+ }
265
+ }
266
+ exclude_array
267
+ end
268
+
269
+ # Return the target file name that stores the serialized INCLUDE_HASH
270
+ def get_include_hash_file
271
+ @rubyrun_signature_folder + '/' + RUBYRUN_INCLUDE_HASH_FILE
272
+ end
273
+
274
+ # return the target file name that stores the serialized EXCLUDE_HASH
275
+ def get_exclude_hash_file
276
+ @rubyrun_signature_folder + '/' + RUBYRUN_EXCLUDE_HASH_FILE
277
+ end
278
+
279
+ # Return the target file name that stores the serialized directory contents digest
280
+ # This file has to be application dependent, and we use the current directory of the
281
+ # running process to represent the application directory.
282
+ def get_dir_hash_file
283
+ @rubyrun_signature_folder + '/' + RUBYRUN_DIR_HASH_FILE + '_' + Digest::MD5.hexdigest(Dir.getwd)
284
+ end
285
+
286
+ end
@@ -0,0 +1,226 @@
1
+ #---------------------------------------------------------------#
2
+ # #
3
+ # (C) Copyright Rubysophic Inc. 2007-2008 #
4
+ # All rights reserved. #
5
+ # #
6
+ # Use, duplication or disclosure of the code is not permitted #
7
+ # unless licensed. #
8
+ # #
9
+ # Last Updated: 7/09/08 #
10
+ #---------------------------------------------------------------#
11
+ # #
12
+ # RubyRunInstrumentor__ module is responsible for performing #
13
+ # instrumentation on ruby methods via metaprogramming. These #
14
+ # methods belong to the candidate classes/modules discovered #
15
+ # earlier in RubyRunInitializer__ plus others which are #
16
+ # explicitly requested in rubyrun_opts via the use of #
17
+ # INCLUDE_HASH. #
18
+ # #
19
+ #---------------------------------------------------------------#
20
+ module RubyRunInstrumentor__
21
+
22
+ require 'logger'
23
+ require 'yaml'
24
+ require 'rubyrun_globals'
25
+ require 'rubyrun_utils__'
26
+ require 'rubyrun_monitor__'
27
+ require 'rubyrun_tracer__'
28
+ require 'rubyrun_dad__'
29
+ require 'rubyrun_html__'
30
+ require 'rubyrun_html_writer__'
31
+ include RubyRunGlobals
32
+ include RubyRunUtils__
33
+ include RubyRunMonitor__
34
+ include RubyRunTracer__
35
+ include RubyRunDad__
36
+ include RubyRunHTML__
37
+
38
+ # Invoked by the traps set up in rubyrun.rb. This indicates that
39
+ # a file has been required/loaded such that the methods within
40
+ # are being added to the process. Each method being added will
41
+ # go through the following process to determine if it should be intrumented.
42
+ #
43
+ # 1. Through APP_PATHS a stream of class names whose methods are
44
+ # identified as candiates for instrumnetation in RubyRunIntializer__.
45
+ # This process forms the initial INCLUDE_HASH which states ALL methods
46
+ # belonging to these classes/methods should be instrumented
47
+ # 2. Through additional INCLUDE_HASH in rubyrun_opts a stream of
48
+ # class => methods hash entries provide further candidates for instrumentation.
49
+ # 3. Thru EXCLUDE_HASH the exclusion logic is then applied to reduce the scope
50
+ # of instrumentation.
51
+ # 4. Some classes and methods are never instrumented regarldess. These
52
+ # are identifed in constants FIREWALL_HASH.
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
60
+
61
+ # Never instrument the following classes/methods to avoid recursion
62
+ # 1. Exclude classes and methods that the instrumentation code uses
63
+ # 2. Exclude method=
64
+ # 3. Exclude method aliased by rubyrun instrumentation code
65
+ # 4. Exclude method re-defined by rubyrun instrumentation code
66
+ # 5. Exclude inherited instances, private, protected, and singleton methods.
67
+ # The way this works is that if m is one of these non-inherited instance
68
+ # methods or singleton methods then it should NOT be excluded. Otherwise
69
+ # it is assumed it is an inherited one and hence excluded.
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
98
+
99
+ # First layer of code performing instrummentation on a class.method
100
+ # The injecting code is different depending on whether the
101
+ # method is an instance method, or singleton(static method of a class,
102
+ # specific method added to an object).
103
+ #
104
+ # If this class is a Rails active controller class, create a hash
105
+ # entry if it does not already exist. This hash is used to keep track
106
+ # of performance metrics by action by controller.
107
+ #
108
+ # If we fail to instrument for whatever reason, log the
109
+ # errors and leave the method alone.
110
+ #
111
+ # Also, create metrics hash for a RAILS controller class if it doesn't exist
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
130
+
131
+ # To instrument an instance method of a class, a method proxy is used:
132
+ #
133
+ # 1. Alias the method to one with a prefix rubyrun_ (i.e., copy the method and
134
+ # create another one with a new name)
135
+ # 2. Create a new method with the original name. This is the method proxy.
136
+ # 3. Preserve the intended visibility of the original method in the proxy
137
+ # 4. This proxy method essentially adds pre and post wrapper code to
138
+ # the original code. This wrapper code is embodied in collect_method_data
139
+ # 5. All these must be done in the context of the class
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 <<-CODE1
144
+ def #{mid.id2name} (*args, &blk)
145
+ RubyRunInstrumentor__.collect_method_data(self, #{klass}, '#{mid}', *args) {self.send("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}", *args, &blk)}
146
+ end
147
+ CODE1
148
+ if klass.private_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}")
149
+ private mid
150
+ elsif klass.protected_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}")
151
+ protected mid
152
+ end
153
+ }
154
+ end
155
+
156
+ # Same as insert_code_to_instance_method
157
+ def insert_code_to_singleton_method(klass, mid)
158
+ (class << klass; self; end).class_eval {
159
+ alias_method "#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}", mid.id2name
160
+ eval <<-EOF2
161
+ def #{mid.id2name} (*args, &blk)
162
+ RubyRunInstrumentor__.collect_method_data(self, #{klass}, '#{mid}', *args) {self.send("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}", *args, &blk)}
163
+ end
164
+ EOF2
165
+ if self.private_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}")
166
+ private mid
167
+ elsif self.protected_instance_methods(false).include?("#{RubyRunGlobals::RUBYRUN_PREFIX}_#{mid.id2name}")
168
+ protected mid
169
+ end
170
+ }
171
+ end
172
+
173
+ # This is the piece of code that actually executed under the application
174
+ # thread during runtime and is not executed during instrumentation time
175
+ #
176
+ # 1. Create a equivalent thread local storage to store request performance
177
+ # metrics but only if this class is a Rails Active Controller class
178
+ # 2. Trace pre and post execution of the original method which has been aliased
179
+ # The original method is invoked via 'yield'
180
+ # 3. When a method ends, report the timings to the response time component but
181
+ # only if a thread local exists
182
+ def collect_method_data(obj, klass, mid, *args)
183
+ tid = get_thread_id
184
+ create_thread_local(tid, obj.request, klass, mid) if is_rails_controller?(klass, mid)
185
+ rubyrun_trace = is_in?($rubyrun_trace_hash, klass, mid)
186
+ if rubyrun_trace
187
+ invoker = get_caller_detail
188
+ enter_trace(tid, " Entry", obj, invoker, klass, mid, *args)
189
+ end
190
+ t1 = Time.new
191
+ result = yield
192
+ t2 = Time.new
193
+ if rubyrun_trace
194
+ (t2 - t1) >= RUBYRUN_HIGHLIGHT_THRESHOLD ? (type = "* #{sprintf("%6.2f", t2-t1)} Exit ") : (type = " #{sprintf("%6.2f", t2-t1)} Exit ")
195
+ enter_trace(tid, type, nil, nil, klass, mid, nil)
196
+ end
197
+ report_rails_timing(klass, mid, t2, t1, tid) if $rubyrun_thread_local[tid] && (t2 - t1) > 0
198
+ result
199
+ end
200
+
201
+ # Instrument Thread.new by wrapping target proc with a
202
+ # begin-rescue clause around the application block.
203
+ # When the thread monitor shoot the thread via thr.raise
204
+ # the rescue clause will catch the interrupt and collect the
205
+ # stack entries in $@ and store them in a global hash, later
206
+ # on printed in rubyrun log by thread id. If the thread dies
207
+ # naturally, print the stack trace on the rubyrun log
208
+ def instrument_thread_new
209
+ (class << Thread; self; end).class_eval {
210
+ alias_method "#{RubyRunGlobals::RUBYRUN_PREFIX}_new", "new"
211
+ def new(*rubyrun_args, &rubyrun_apps_block)
212
+ rubyrun_proc = lambda {
213
+ begin
214
+ rubyrun_apps_block.call(*rubyrun_args)
215
+ rescue Exception => e
216
+ e.message == RUBYRUN_KILL_3_STRING ?
217
+ $@.each {|line| ($rubyrun_thread_stack[Thread.current] ||= []) << line} :
218
+ $@.each {|line| $rubyrun_logger.info "#{line}"}
219
+ end
220
+ }
221
+ self.send("#{RubyRunGlobals::RUBYRUN_PREFIX}_new", *rubyrun_args, &rubyrun_proc)
222
+ end
223
+ }
224
+ end
225
+
226
+ end