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.
- data/LICENSE +13 -0
- data/README +77 -0
- data/bin/Rakefile +11 -0
- data/docs/rubyrun-0.9.5.htm +5937 -0
- data/docs/rubyrun-0.9.5.pdf +0 -0
- data/docs/rubyrun-0.9.5_files/colorschememapping.xml +2 -0
- data/docs/rubyrun-0.9.5_files/filelist.xml +29 -0
- data/docs/rubyrun-0.9.5_files/header.htm +138 -0
- data/docs/rubyrun-0.9.5_files/image001.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image002.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image003.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image004.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image005.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image006.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image007.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image008.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image009.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image010.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image011.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image012.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image013.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image014.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image015.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image016.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image017.png +0 -0
- data/docs/rubyrun-0.9.5_files/image018.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image019.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image020.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image021.jpg +0 -0
- data/docs/rubyrun-0.9.5_files/image022.png +0 -0
- data/docs/rubyrun-0.9.5_files/themedata.thmx +0 -0
- data/etc/rubyrun_opts.yml +132 -0
- data/ext/extconf.rb +5 -0
- data/ext/rubyrunnative__.c +154 -0
- data/ext/rubyrunnative__.def +2 -0
- data/ext/rubyrunnative__.h +36 -0
- data/ext/rubyrunnative__ppc-darwin.bundle +0 -0
- data/ext/rubyrunnative__x86-darwin.bundle +0 -0
- data/ext/rubyrunnative__x86-linux.so +0 -0
- data/ext/rubyrunnative__x86-mswin32.so +0 -0
- data/lib/rubyrun/rubyrun.rb +2 -0
- data/lib/rubyrun/rubyrun_boot__.rb +79 -0
- data/lib/rubyrun/rubyrun_buffer_mgr__.rb +49 -0
- data/lib/rubyrun/rubyrun_commander__.rb +196 -0
- data/lib/rubyrun/rubyrun_dad__.rb +35 -0
- data/lib/rubyrun/rubyrun_globals.rb +52 -0
- data/lib/rubyrun/rubyrun_html__.rb +136 -0
- data/lib/rubyrun/rubyrun_html_writer__.rb +64 -0
- data/lib/rubyrun/rubyrun_initializer__.rb +313 -0
- data/lib/rubyrun/rubyrun_instrumentor__.rb +226 -0
- data/lib/rubyrun/rubyrun_monitor__.rb +238 -0
- data/lib/rubyrun/rubyrun_report__.rb +109 -0
- data/lib/rubyrun/rubyrun_rss__.rb +97 -0
- data/lib/rubyrun/rubyrun_tracer__.rb +79 -0
- data/lib/rubyrun/rubyrun_utils__.rb +101 -0
- data/lib/rubyrun/rubyrunnative__.so +0 -0
- 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
|