rubyrun 0.9.5-x86-linux

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/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