rubyrun 0.9.5-x86-mswin32-60

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/bin/Rakefile +11 -0
  4. data/docs/rubyrun-0.9.5.htm +5937 -0
  5. data/docs/rubyrun-0.9.5.pdf +0 -0
  6. data/docs/rubyrun-0.9.5_files/colorschememapping.xml +2 -0
  7. data/docs/rubyrun-0.9.5_files/filelist.xml +29 -0
  8. data/docs/rubyrun-0.9.5_files/header.htm +138 -0
  9. data/docs/rubyrun-0.9.5_files/image001.jpg +0 -0
  10. data/docs/rubyrun-0.9.5_files/image002.jpg +0 -0
  11. data/docs/rubyrun-0.9.5_files/image003.jpg +0 -0
  12. data/docs/rubyrun-0.9.5_files/image004.jpg +0 -0
  13. data/docs/rubyrun-0.9.5_files/image005.jpg +0 -0
  14. data/docs/rubyrun-0.9.5_files/image006.jpg +0 -0
  15. data/docs/rubyrun-0.9.5_files/image007.jpg +0 -0
  16. data/docs/rubyrun-0.9.5_files/image008.jpg +0 -0
  17. data/docs/rubyrun-0.9.5_files/image009.jpg +0 -0
  18. data/docs/rubyrun-0.9.5_files/image010.jpg +0 -0
  19. data/docs/rubyrun-0.9.5_files/image011.jpg +0 -0
  20. data/docs/rubyrun-0.9.5_files/image012.jpg +0 -0
  21. data/docs/rubyrun-0.9.5_files/image013.jpg +0 -0
  22. data/docs/rubyrun-0.9.5_files/image014.jpg +0 -0
  23. data/docs/rubyrun-0.9.5_files/image015.jpg +0 -0
  24. data/docs/rubyrun-0.9.5_files/image016.jpg +0 -0
  25. data/docs/rubyrun-0.9.5_files/image017.png +0 -0
  26. data/docs/rubyrun-0.9.5_files/image018.jpg +0 -0
  27. data/docs/rubyrun-0.9.5_files/image019.jpg +0 -0
  28. data/docs/rubyrun-0.9.5_files/image020.jpg +0 -0
  29. data/docs/rubyrun-0.9.5_files/image021.jpg +0 -0
  30. data/docs/rubyrun-0.9.5_files/image022.png +0 -0
  31. data/docs/rubyrun-0.9.5_files/themedata.thmx +0 -0
  32. data/etc/rubyrun_opts.yml +132 -0
  33. data/ext/extconf.rb +5 -0
  34. data/ext/rubyrunnative__.c +154 -0
  35. data/ext/rubyrunnative__.def +2 -0
  36. data/ext/rubyrunnative__.h +36 -0
  37. data/ext/rubyrunnative__ppc-darwin.bundle +0 -0
  38. data/ext/rubyrunnative__x86-darwin.bundle +0 -0
  39. data/ext/rubyrunnative__x86-linux.so +0 -0
  40. data/ext/rubyrunnative__x86-mswin32.so +0 -0
  41. data/lib/rubyrun/rubyrun.rb +2 -0
  42. data/lib/rubyrun/rubyrun_boot__.rb +79 -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 +52 -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 +313 -0
  50. data/lib/rubyrun/rubyrun_instrumentor__.rb +226 -0
  51. data/lib/rubyrun/rubyrun_monitor__.rb +238 -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 +101 -0
  56. data/lib/rubyrun/rubyrunnative__.so +0 -0
  57. metadata +115 -0
@@ -0,0 +1,313 @@
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 'ftools'
26
+ require 'rubyrun_globals'
27
+ require 'rubyrun_utils__'
28
+
29
+ include RubyRunGlobals
30
+ include RubyRunUtils__
31
+
32
+ # 1. Get all directories, logs and properties set up correctly
33
+ # 2. Scan files in APP_PATHS to create an initial INCLUDE_HASH
34
+ # and an EXCLUDE_HASH
35
+ def init_rubyrun
36
+ ready_rubyrun_env
37
+ discover_targets
38
+ end
39
+
40
+ # 1. Extract the working directory from ENV VAR and recursively create all the
41
+ # subdirectories if they dont exist
42
+ # 2. Create the log and trace folers
43
+ # 3. Initialize the loggers
44
+ # 4. Load the properties from either the current working directory or
45
+ # rubyrun working directory
46
+ # 5. Spawn a separate thread to for monitoring and commands
47
+ def ready_rubyrun_env
48
+ begin
49
+ $rubyrun_working_dir = Dir.getwd + '/' + RUBYRUN_WORKING_DIR_NAME
50
+ create_working_dir($rubyrun_working_dir) unless File.exists?($rubyrun_working_dir)
51
+ create_options_file($rubyrun_working_dir) 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
+ # Create the working directory, which will be 'rubyrun' under the current directory
67
+ def create_working_dir(path)
68
+ begin
69
+ Dir.mkdir(path)
70
+ rescue Exception => e
71
+ raise RuntimeError, "Unable to create working directory #{path}"
72
+ end
73
+ end
74
+
75
+ # Create the options file
76
+ def create_options_file(path)
77
+ begin
78
+ yml_source = __FILE__.gsub('\\','/').split('/')[0..-4].join('/') + '/' + RUBYRUN_ETC_DIR + RUBYRUN_OPTS_FILE
79
+ opts_file = File.new(path + RUBYRUN_OPTS_FILE, "w")
80
+ File.open(yml_source).each { |line|
81
+ if line.include?('APP_PATHS: []')
82
+ File.exists?("#{Dir.getwd+'/app/controllers'}") ? opts_file.puts("APP_PATHS: ['#{Dir.getwd+'/app/controllers'}']") : opts_file.puts("APP_PATHS: ['#{Dir.getwd}']")
83
+ else
84
+ opts_file.puts(line)
85
+ end
86
+ }
87
+ opts_file.close
88
+ rescue Exception => e
89
+ raise RuntimeError, "Unable to create options file in #{path}"
90
+ end
91
+ end
92
+
93
+ # Make sub-directories (folders)
94
+ def make_folder(rubyrun_folders)
95
+ rubyrun_folders.each {|rubyrun_folder|
96
+ begin
97
+ Dir.mkdir(rubyrun_folder) unless File.exist?(rubyrun_folder)
98
+ rescue Exception => e
99
+ fatal_exit(e)
100
+ end
101
+ }
102
+ end
103
+
104
+ # Initialize rubyrun logger and create its own log format
105
+ def ready_logfile(logname)
106
+ $rubyrun_logger = Logger.new(@rubyrun_log_folder + '/' + logname + '_' + $$.to_s + '.log', shift_age = 10, shift_size = 4096000)
107
+ $rubyrun_logger.level = Logger::INFO
108
+ class << $rubyrun_logger
109
+ include RubyRunUtils__
110
+ def format_message (severity, timestamp, progname, msg)
111
+ "[#{timestamp.strftime("%Y-%m-%d %H:%M:%S")}.#{("%.3f" % timestamp.to_f).split('.')[1]}] #{get_thread_id} #{msg}\n"
112
+ end
113
+ end
114
+ end
115
+
116
+ # Property file is a yml file.
117
+ # Load the properties into $config hash
118
+ def load_config_props(config_file)
119
+ begin
120
+ $rubyrun_config = YAML.load_file(config_file)
121
+ $rubyrun_logger.info "Properties found in #{config_file}:"
122
+ RUBYRUN_PROP_DEFAULTS.each {|prop, def_value|
123
+ $rubyrun_config[prop] = def_value unless config_prop_exists?(prop)
124
+ $rubyrun_logger.info "#{prop} = #{$rubyrun_config[prop].inspect}"
125
+ }
126
+ $rubyrun_logger.info "***** APP_PATHS is nil. Applications will not be instrumented. *****" \
127
+ if $rubyrun_config['APP_PATHS'].empty?
128
+ rescue Exception => e
129
+ fatal_exit(e)
130
+ end
131
+ $rubyrun_debug_args = $rubyrun_config['DEBUG_ARGS']
132
+ $rubyrun_debug_obj = $rubyrun_config['DEBUG_OBJ']
133
+ $rubyrun_dad = $rubyrun_config['DAD']
134
+ $rubyrun_report_timer = $rubyrun_config['REPORT_TIMER']
135
+ $rubyrun_report_shift_age = $rubyrun_config['REPORT_SHIFT_AGE']
136
+ $rubyrun_trace_hash = $rubyrun_config['TRACE_HASH']
137
+ $rubyrun_adapter_hash = $rubyrun_config['DB_ADAPTER_HASH']
138
+ validate_opts
139
+ end
140
+
141
+ # Validate the range of REPORT_TIMER and REPORT_SHIFT_AGE
142
+ # Use default values if out of acceptable range
143
+ def validate_opts
144
+ if $rubyrun_report_timer > 3600 || $rubyrun_report_timer < 60
145
+ $rubyrun_report_timer = RUBYRUN_PROP_DEFAULTS['REPORT_TIMER']
146
+ $rubyrun_logger.warn "REPORT_TIMER value must be between 60 and 3600. #{$rubyrun_report_timer}is used."
147
+ end
148
+ if $rubyrun_report_shift_age > 120 || $rubyrun_report_shift_age < 1
149
+ $rubyrun_report_shift_age = RUBYRUN_PROP_DEFAULTS['REPORT_SHIFT_AGE']
150
+ $rubyrun_logger.warn "REPORT_SHIFT_AGE value must be between 1 and 120. #{$rubyrun_report_shift_age}is used."
151
+ end
152
+ end
153
+
154
+ # If the property is not defined or defined but with nil value, it
155
+ # is deemed to be non-existent
156
+ def config_prop_exists?(prop)
157
+ $rubyrun_config.has_key?(prop) && !$rubyrun_config[prop].nil? ? true : false
158
+ end
159
+
160
+ # Set up global variables from the property file rubyrun_config.yml
161
+ # For APP_PATHS property, expand any subdirectories and look for
162
+ # .rb files recursively
163
+ def discover_targets
164
+ identify_candidates
165
+ $rubyrun_include_hash.merge!($rubyrun_config['INCLUDE_HASH'])
166
+ ['DB_ADAPTER_HASH','OUTER_DISPATCH_HASH','INNER_DISPATCH_HASH'].each { |hash_key|
167
+ $rubyrun_include_hash.merge!($rubyrun_config[hash_key]) { |k,o,n| o.concat(n) } if config_prop_exists?(hash_key)
168
+ }
169
+ [RUBYRUN_OUTER_DISPATCH_HASH,RUBYRUN_INNER_DISPATCH_HASH,RUBYRUN_THREAD_END_HASH,RUBYRUN_VIEW_HASH].each {|hash|
170
+ $rubyrun_include_hash.merge!(hash) { |k,o,n| o.concat(n) }
171
+ }
172
+ $rubyrun_exclude_hash.merge!($rubyrun_config['EXCLUDE_HASH'])
173
+ $rubyrun_outer_dispatch_hash = config_prop_exists?('OUTER_DISPATCH_HASH') ? RUBYRUN_OUTER_DISPATCH_HASH.merge($rubyrun_config['OUTER_DISPATCH_HASH']) : RUBYRUN_OUTER_DISPATCH_HASH
174
+ $rubyrun_inner_dispatch_hash = config_prop_exists?('INNER_DISPATCH_HASH') ? RUBYRUN_INNER_DISPATCH_HASH.merge($rubyrun_config['INNER_DISPATCH_HASH']) : RUBYRUN_INNER_DISPATCH_HASH
175
+ $rubyrun_logger.info "Final INCLUDE_HASH = #{$rubyrun_include_hash.inspect}"
176
+ $rubyrun_logger.info "Final EXCLUDE_HASH = #{$rubyrun_exclude_hash.inspect}"
177
+ end
178
+
179
+ # If the directory content has changed (new files, modified files,
180
+ # new APP_PATHS) a new scan will be required, otherwise the last scan
181
+ # results will simply be reused. When a new scan is finished,
182
+ # the results are serialized.
183
+ #
184
+ # Digest technique is used to detect changes in directory content.
185
+ def identify_candidates
186
+ $rubyrun_config['APP_PATHS'].each {|element|
187
+ element = Dir.getwd if element == '.'
188
+ expand_folder(element)
189
+ }
190
+ unless $rubyrun_file_date_hash.empty?
191
+ dir_signature = generate_hash($rubyrun_file_date_hash)
192
+ exclude_array = []
193
+ if directory_changed?(dir_signature)
194
+ $rubyrun_file_date_hash.each {|f, date|
195
+ scan_module_class(f)
196
+ scan_super(f).each {|name| exclude_array << name}
197
+ }
198
+ $rubyrun_exclude_hash['*'] = exclude_array.uniq if exclude_array.length > 0
199
+ serialize_scan_history(dir_signature)
200
+ end
201
+ end
202
+ deserialize_scan_history
203
+ $rubyrun_file_date_hash.clear if $rubyrun_file_date_hash
204
+ end
205
+
206
+ # For each directory, expand into a list of filenames and its modifed time.
207
+ def expand_folder(folder)
208
+ ($rubyrun_logger.warn("WARN: APP_PATHS not found: #{folder}") ; return) unless File.exists?(folder)
209
+ if File.file?(folder)
210
+ return if File.extname(folder) != '.rb'
211
+ path = File.expand_path(folder)
212
+ $rubyrun_file_date_hash[path] = File.mtime(path).to_i
213
+ else
214
+ Dir.entries(folder).each {|entry|
215
+ next if entry =~ /^\.+/
216
+ path = File.expand_path(entry, folder)
217
+ case
218
+ when File.directory?(path)
219
+ expand_folder(path)
220
+ else
221
+ next if File.extname(entry) != '.rb'
222
+ $rubyrun_file_date_hash[path] = File.mtime(path).to_i
223
+ end
224
+ }
225
+ end
226
+ end
227
+
228
+ # Compare the digest calculated from the current APP_PATHS directory
229
+ # contents to the last serialized one. Return true(changed) or false
230
+ # (unchanged)
231
+ def directory_changed?(dir_signature)
232
+ f = get_dir_hash_file
233
+ dir_sig = File.exists?(f) ? File.open(f) {|f| Marshal.load(f)} : nil
234
+ dir_signature != dir_sig
235
+ end
236
+
237
+ # Calcualte the digest from the directory contents of APP_PATHS
238
+ def generate_hash(hash)
239
+ Digest::MD5.hexdigest(hash.to_s)
240
+ end
241
+
242
+ # Use Marshal to serialize scan results (the include and exclude hashes)
243
+ def serialize_scan_history(dir_signature)
244
+ File.open(get_include_hash_file, 'w') {|f| Marshal.dump($rubyrun_include_hash, f)}
245
+ File.open(get_exclude_hash_file, 'w') {|f| Marshal.dump($rubyrun_exclude_hash, f)}
246
+ File.open(get_dir_hash_file, 'w') {|f| Marshal.dump(dir_signature, f)}
247
+ end
248
+
249
+ # Use Marshal to de-serialize the include and exclude hashes
250
+ def deserialize_scan_history
251
+ f = get_include_hash_file
252
+ $rubyrun_include_hash = File.exists?(f) ? File.open(f) {|f| Marshal.load(f)} : {}
253
+ f = get_exclude_hash_file
254
+ $rubyrun_exclude_hash = File.exists?(f) ? File.open(f) {|f| Marshal.load(f)} : {}
255
+ end
256
+
257
+ # Regular expression that detects the class or module names of a given file
258
+ def scan_module_class(path)
259
+ $rubyrun_logger.info "Candidate modules/classes for instrumentation in #{path}:"
260
+ open(path).grep(/^\s*(class|module)\s+([[:upper:]][A-Za-z0-9_:]*)/m) {
261
+ $rubyrun_logger.info "\t #{$2}"
262
+ $rubyrun_include_hash[$2] = []
263
+ }
264
+ end
265
+
266
+ # Line up all the method names in a file by line #
267
+ # Line up all the line numbers that contain the keyworkd super
268
+ # Collate the two arrays to determine which method has super in it
269
+ # Put these method names in EXCLUDE_HASH and skip instrumentation
270
+ def scan_super(path)
271
+ $rubyrun_logger.info "Method(s) to be excluded from instrumentation in #{path}:"
272
+ method_name_array, method_lineno_array, super_array, exclude_array = [],[],[],[]
273
+ open(path) {|f|
274
+ f.readlines.each_with_index {|code, lineno|
275
+ code.scan(/^\s*def\s+(.*)\(+.*\)+/m)
276
+ (method_name_array << $1; method_lineno_array << lineno) if $1
277
+ code.scan(/^\s*(super)/m)
278
+ super_array << lineno if $1
279
+ }
280
+ }
281
+ method_name_array.reverse!
282
+ method_lineno_array.reverse!
283
+ super_array.each {|lineno|
284
+ method_lineno_array.each_with_index {|linenum, i|
285
+ unless lineno < linenum
286
+ $rubyrun_logger.info "\t #{method_name_array[i]}"
287
+ m = method_name_array[i].split('.')
288
+ exclude_array << (m.length > 1 ? m[1] : m[0])
289
+ break
290
+ end
291
+ }
292
+ }
293
+ exclude_array
294
+ end
295
+
296
+ # Return the target file name that stores the serialized INCLUDE_HASH
297
+ def get_include_hash_file
298
+ @rubyrun_signature_folder + '/' + RUBYRUN_INCLUDE_HASH_FILE
299
+ end
300
+
301
+ # return the target file name that stores the serialized EXCLUDE_HASH
302
+ def get_exclude_hash_file
303
+ @rubyrun_signature_folder + '/' + RUBYRUN_EXCLUDE_HASH_FILE
304
+ end
305
+
306
+ # Return the target file name that stores the serialized directory contents digest
307
+ # This file has to be application dependent, and we use the current directory of the
308
+ # running process to represent the application directory.
309
+ def get_dir_hash_file
310
+ @rubyrun_signature_folder + '/' + RUBYRUN_DIR_HASH_FILE + '_' + Digest::MD5.hexdigest(Dir.getwd)
311
+ end
312
+
313
+ 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)
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)
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