rubyrun 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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