rubyrun 0.9.0-mswin32

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 (96) hide show
  1. data/LICENSE +13 -0
  2. data/README +77 -0
  3. data/Rakefile +241 -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__linux.so +0 -0
  41. data/html/classes/Module.html +174 -0
  42. data/html/classes/Object.html +151 -0
  43. data/html/classes/RubyRunBufferMgr__.html +182 -0
  44. data/html/classes/RubyRunCommander__.html +578 -0
  45. data/html/classes/RubyRunDad__.html +144 -0
  46. data/html/classes/RubyRunGlobals.html +248 -0
  47. data/html/classes/RubyRunHTMLWriter/RubyRunHTMLDevice.html +157 -0
  48. data/html/classes/RubyRunHTMLWriter.html +186 -0
  49. data/html/classes/RubyRunHTML__.html +198 -0
  50. data/html/classes/RubyRunInitializer__.html +821 -0
  51. data/html/classes/RubyRunInstrumentor__.html +576 -0
  52. data/html/classes/RubyRunMonitor__.html +298 -0
  53. data/html/classes/RubyRunRSS.html +302 -0
  54. data/html/classes/RubyRunReport__.html +294 -0
  55. data/html/classes/RubyRunTracer__.html +253 -0
  56. data/html/classes/RubyRunUtils__.html +376 -0
  57. data/html/created.rid +1 -0
  58. data/html/files/LICENSE.html +119 -0
  59. data/html/files/README.html +197 -0
  60. data/html/files/lib/rubyrun/rubyrun_buffer_mgr___rb.html +101 -0
  61. data/html/files/lib/rubyrun/rubyrun_commander___rb.html +101 -0
  62. data/html/files/lib/rubyrun/rubyrun_dad___rb.html +101 -0
  63. data/html/files/lib/rubyrun/rubyrun_globals_rb.html +101 -0
  64. data/html/files/lib/rubyrun/rubyrun_html___rb.html +101 -0
  65. data/html/files/lib/rubyrun/rubyrun_html_writer___rb.html +108 -0
  66. data/html/files/lib/rubyrun/rubyrun_initializer___rb.html +112 -0
  67. data/html/files/lib/rubyrun/rubyrun_instrumentor___rb.html +116 -0
  68. data/html/files/lib/rubyrun/rubyrun_monitor___rb.html +116 -0
  69. data/html/files/lib/rubyrun/rubyrun_rb.html +121 -0
  70. data/html/files/lib/rubyrun/rubyrun_report___rb.html +101 -0
  71. data/html/files/lib/rubyrun/rubyrun_rss___rb.html +108 -0
  72. data/html/files/lib/rubyrun/rubyrun_tracer___rb.html +110 -0
  73. data/html/files/lib/rubyrun/rubyrun_utils___rb.html +108 -0
  74. data/html/files/lib/rubyrunm_rb.html +116 -0
  75. data/html/fr_class_index.html +42 -0
  76. data/html/fr_file_index.html +43 -0
  77. data/html/fr_method_index.html +96 -0
  78. data/html/index.html +24 -0
  79. data/html/rdoc-style.css +208 -0
  80. data/lib/rubyrun/rubyrun.rb +78 -0
  81. data/lib/rubyrun/rubyrun_buffer_mgr__.rb +49 -0
  82. data/lib/rubyrun/rubyrun_commander__.rb +196 -0
  83. data/lib/rubyrun/rubyrun_dad__.rb +35 -0
  84. data/lib/rubyrun/rubyrun_globals.rb +51 -0
  85. data/lib/rubyrun/rubyrun_html__.rb +136 -0
  86. data/lib/rubyrun/rubyrun_html_writer__.rb +64 -0
  87. data/lib/rubyrun/rubyrun_initializer__.rb +286 -0
  88. data/lib/rubyrun/rubyrun_instrumentor__.rb +226 -0
  89. data/lib/rubyrun/rubyrun_monitor__.rb +237 -0
  90. data/lib/rubyrun/rubyrun_report__.rb +109 -0
  91. data/lib/rubyrun/rubyrun_rss__.rb +97 -0
  92. data/lib/rubyrun/rubyrun_tracer__.rb +79 -0
  93. data/lib/rubyrun/rubyrun_utils__.rb +98 -0
  94. data/lib/rubyrun/rubyrunnative__.so +0 -0
  95. data/lib/rubyrunm.rb +10 -0
  96. metadata +149 -0
@@ -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
@@ -0,0 +1,237 @@
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
+ # RubyRunMonitor__ is responsible for keeping track of #
13
+ # the response time of a Rails Request and its decomposition. #
14
+ # #
15
+ # For instance, a typical WEBrick/RAILS servlet dispatch #
16
+ # response time components generally break down look roughly #
17
+ # like this: #
18
+ # #
19
+ # SVS DI ACT DB DB DB VI VI #
20
+ # -+-----+----+----+--+--+--+--+--+----+--+----+-+--+---+----+ #
21
+ # DB' DB' DB' ACT' VI' VI' DI' SVS'#
22
+ # Legend: #
23
+ # SVS = DispatchServlet.service #
24
+ # DI = DispatchServlet.handle_dispatch #
25
+ # ACT = controller.action #
26
+ # DB = ActiveRecord::ConnectionAdapters::*Adapter.execute #
27
+ # VI = ActionView.Base:render_template #
28
+ # All apostrophe means "end of event" #
29
+ # #
30
+ # Notes: #
31
+ # 1. mutex held from SVS to SVS' #
32
+ # 2. Total dispatch time with mutex = SVS' - SVS #
33
+ # 3. Total DBIO time = Sum(DB' - DB) #
34
+ # 4. Action Time = ACT' - ACT #
35
+ # 5. View time = Sum(VI' - VI) #
36
+ # 6. View rendering includes template + layout #
37
+ # #
38
+ # Another key function is to act as a command agent, responding#
39
+ # to command such as displaying thread status, terminating #
40
+ # threads with stack tracke and showing object heap info. #
41
+ # #
42
+ #---------------------------------------------------------------#
43
+ module RubyRunMonitor__
44
+
45
+ require 'rubyrun_globals'
46
+ require 'rubyrun_utils__'
47
+ require 'rubyrun_tracer__'
48
+ require 'rubyrun_rss__'
49
+ require 'rubyrun_buffer_mgr__'
50
+ require 'rubyrun_html__'
51
+ require 'rubyrun_report__'
52
+ require 'rubyrun_commander__'
53
+ begin
54
+ require 'rubyrunnative__'
55
+ $rubyrun_native = true
56
+ rescue Exception
57
+ $rubyrun_native = false
58
+ end
59
+ include RubyRunGlobals
60
+ include RubyRunUtils__
61
+ include RubyRunTracer__
62
+ include RubyRunBufferMgr__
63
+ include RubyRunHTML__
64
+ include RubyRunReport__
65
+ include RubyRunCommander__
66
+
67
+ # In response to the presence of a 'cmd_status', 'cmd_soft_kill', 'cmd_hard_kill'
68
+ # or 'cmd_object_map' in the work directory, the monitor thread will either
69
+ # display thread status, interrupt the threads in different manner, or show object instances
70
+ # in memory
71
+ def start_thread_monitor
72
+ $rubyrun_logger.info "----- RubyRun Thread Monitor started -----"
73
+ monitor_thr = Thread.new {
74
+ cycle = $rubyrun_report_timer / RUBYRUN_MONITOR_TIMER
75
+ sleep_count = 0
76
+ loop do
77
+ sleep RUBYRUN_MONITOR_TIMER
78
+ monitor_thr.exit if exit_monitor?
79
+ Thread.new {
80
+ begin
81
+ sleep_count += 1
82
+ sleep_count == cycle ? (dump_reports(true); sleep_count = 0) : dump_reports
83
+ dump_thread_status if thread_status?
84
+ dump_object_map if object_map?
85
+ kill_threads(monitor_thr) if soft_kill? || hard_kill?
86
+ rescue Exception => e
87
+ $stderr.print e.to_s + "\n" + e.backtrace.join("\n")
88
+ exit(-1)
89
+ end
90
+ }
91
+ end
92
+ }
93
+ end
94
+
95
+ # Simulate a thread local storage by using a private hash keyed on thread id.
96
+ # Key elements in the hash are, for instance:
97
+ # {#{tid} => {:req=>request.object_id, :controller=>name, :action=> name,
98
+ # :action_t=>t, :dbio=>t, :dispatch_t=>t, :view_t=>t, :uncaptured_t=>t,
99
+ # :dispatch_wait_t=>t}}
100
+ # Thread local is used to store performance metrics of a RAILS request.
101
+ # When the same thread serves a different request, the current thread local
102
+ # data needs to be rolled up and re-initialized.
103
+ def create_thread_local(tid, request, klass, mid)
104
+ $rubyrun_thread_local[tid] ||= {}
105
+ init_thread_local(tid, request, klass, mid) unless $rubyrun_thread_local[tid][:req]
106
+ if $rubyrun_thread_local[tid][:req] != request.object_id
107
+ roll_up_metrics(tid)
108
+ init_thread_local(tid, request, klass, mid)
109
+ end
110
+ end
111
+
112
+ # Create a place holding global metrics hash for each RAILS application
113
+ # controller class to accumulate performance metrics by action.
114
+ # Key elements in the hash are, for instance:
115
+ # {#{controller} => {#{action} => [dispatch_t, action_t, dbio_t, view_t, uncap_t, dispatch_wait_t]}}
116
+ def create_metrics_hash(klass)
117
+ $rubyrun_metrics_hash[klass.to_s.downcase[0..-11]] ||= {}
118
+ end
119
+
120
+ # Report timings to thread local after decomposing it into the right component.
121
+ # For each action, there are 5 components of Response Time (RT)
122
+ # :dispatch_t, :#{action}_t, :dbio_t, :view_t, :uncaptured_t
123
+ def report_rails_timing(klass, mid, t2, t1, tid)
124
+ t = t2 - t1
125
+ if is_in_hash?($rubyrun_adapter_hash, klass, mid)
126
+ $rubyrun_thread_local[tid][:dbio_t] += t
127
+ elsif is_in_hash?(RUBYRUN_VIEW_HASH, klass, mid)
128
+ $rubyrun_thread_local[tid][:view_t] << t2 << t1
129
+ elsif is_in_hash?($rubyrun_outer_dispatch_hash, klass, mid)
130
+ $rubyrun_thread_local[tid][:outer_dispatch_t] << t
131
+ elsif is_in_hash?($rubyrun_inner_dispatch_hash, klass, mid)
132
+ $rubyrun_thread_local[tid][:inner_dispatch_t] << t
133
+ elsif is_rails_controller?(klass, mid)
134
+ $rubyrun_thread_local[tid][:action_t] = t
135
+ $rubyrun_thread_local[tid][:scafold_style] = $rubyrun_thread_local[tid][:view_t].empty? ? true : false
136
+ elsif is_in?(RUBYRUN_THREAD_END_HASH, klass, mid, 'strict')
137
+ roll_up_metrics(tid, true)
138
+ end
139
+ end
140
+
141
+ private
142
+
143
+ # Roll up action metrics to controller_metrics_hash
144
+ # First, roll up the pieces in thread_local
145
+ # Then roll up the thread local data to the metrics global hash
146
+ def roll_up_metrics(tid, thread_end=false)
147
+ ($rubyrun_thread_local[tid].clear; return) if thread_local_incomplete?(tid)
148
+ dbio_t = $rubyrun_thread_local[tid][:dbio_t]
149
+ outer_dispatch_t = $rubyrun_thread_local[tid][:outer_dispatch_t].max
150
+ inner_dispatch_t = $rubyrun_thread_local[tid][:inner_dispatch_t].max
151
+ dispatch_wait_t = outer_dispatch_t - inner_dispatch_t
152
+ view_t = $rubyrun_thread_local[tid][:view_t].empty? ? 0 : $rubyrun_thread_local[tid][:view_t].max - $rubyrun_thread_local[tid][:view_t].min
153
+ action_t = ($rubyrun_thread_local[tid][:action_t])
154
+ uncap_t = $rubyrun_thread_local[tid][:scafold_style] ? outer_dispatch_t - view_t - action_t - dispatch_wait_t : outer_dispatch_t - action_t - dispatch_wait_t
155
+ $rubyrun_thread_local[tid][:uncaptured_t] = uncap_t
156
+ $rubyrun_thread_local[tid][:dispatch_wait_t] = dispatch_wait_t
157
+ push_current_buffer([tid, Time.now,
158
+ $rubyrun_thread_local[tid][:url],
159
+ $rubyrun_thread_local[tid][:controller],
160
+ $rubyrun_thread_local[tid][:action],
161
+ outer_dispatch_t, action_t, dbio_t, view_t, uncap_t, dispatch_wait_t])
162
+ !thread_end ? $rubyrun_thread_local[tid].clear : $rubyrun_thread_local.delete(tid)
163
+ end
164
+
165
+ # Initialize the thread_local hash
166
+ def init_thread_local(tid, request, klass, mid)
167
+ $rubyrun_thread_local[tid][:req] = request.object_id
168
+ $rubyrun_thread_local[tid][:controller] = klass.to_s.split('Controller')[0].downcase
169
+ $rubyrun_thread_local[tid][:action] = return_method_name(mid)
170
+ $rubyrun_thread_local[tid][:url] = request.protocol + request.host_with_port + request.request_uri
171
+ $rubyrun_thread_local[tid][:dbio_t] = 0
172
+ $rubyrun_thread_local[tid][:outer_dispatch_t] = []
173
+ $rubyrun_thread_local[tid][:inner_dispatch_t] = []
174
+ $rubyrun_thread_local[tid][:action_t] = 0
175
+ $rubyrun_thread_local[tid][:uncaptured_t] = 0
176
+ $rubyrun_thread_local[tid][:view_t] = []
177
+ $rubyrun_thread_local[tid][:dispatch_wait_t] = 0
178
+ $rubyrun_thread_local[tid][:scafold_style] = false
179
+ $rubyrun_host_with_port = request.host_with_port
180
+ end
181
+
182
+ # Use the data hash returned by the native function to show
183
+ # the top frame inside the thread struct (node and orig_func)
184
+ def get_top_stack(th_data_hash, thread_id)
185
+ th_data_hash.each {|th, top_stack|
186
+ if th.to_s.include?(thread_id)
187
+ return "#{top_stack[0].gsub('rubyrun_', '')}"
188
+ break
189
+ end
190
+ }
191
+ end
192
+
193
+ # If request aborted, thread_local can be corrupted (half filled)
194
+ # Return true if corrupted else false
195
+ def thread_local_incomplete? (tid)
196
+ $rubyrun_thread_local[tid][:controller].nil? ||
197
+ $rubyrun_thread_local[tid][:action].nil? ||
198
+ $rubyrun_thread_local[tid][:outer_dispatch_t].empty? ||
199
+ $rubyrun_thread_local[tid][:inner_dispatch_t].empty?
200
+ end
201
+
202
+ # Sort $rubyrun_metrics_hash by response time in descending order
203
+ # An array of the following data structure is returned:
204
+ # metrics[0] controller/action name
205
+ # metrics[1] Array of performance data
206
+ # metrics[1][0] resposne time metrics[1][1] action time
207
+ # metrics[1][2] database IO time metrics[1][3] view time
208
+ # metrics[1][4] uncaptured time metrics[1][5] wait time
209
+ # metrics[1][6] request count
210
+ def sort_performance_metrics
211
+ results = Hash.new
212
+ $rubyrun_metrics_hash.each {|controller, action_metrics|
213
+ next if action_metrics.empty?
214
+ action_metrics.each {|action, metrics|
215
+ results["#{controller}/#{action}"] = metrics
216
+ }
217
+ }
218
+ results.sort {|a, b| -1*(a[1]<=>b[1])}
219
+ end
220
+
221
+ # An optimized runtime version of the original is_in? in RubyRunInstrumentor__
222
+ # This is used during runtime and not instrumentation, hence something of better
223
+ # performance but less general is required.
224
+ def is_in_hash?(hash, klass, mid)
225
+ return false if hash.empty?
226
+ name = klass.to_s
227
+ if hash.has_key?(name)
228
+ return true if hash[name].empty?
229
+ method_name = return_method_name(mid)
230
+ hash[name].each {|meth_name|
231
+ return true if method_name.downcase == meth_name.downcase
232
+ }
233
+ end
234
+ false
235
+ end
236
+
237
+ end
@@ -0,0 +1,109 @@
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/7/08 #
10
+ #---------------------------------------------------------------#
11
+ # #
12
+ # RubyRunReport__ is responsible for generating report data in #
13
+ # RSS or CSV format depending on the type of report. #
14
+ # #
15
+ #---------------------------------------------------------------#
16
+ module RubyRunReport__
17
+
18
+ # Add entries to transaction log CSV file
19
+ # metrics structure
20
+ # metrics[0] Thread ID metrics[1] Timestamp of the request
21
+ # metrics[2] URL metrics[3] Controller name
22
+ # metrics[4] Action name metrics[5] Response time
23
+ # metrics[6] Action time metrics[7] Database IO time
24
+ # metrics[8] View time metrics[9] Uncaptured time
25
+ # metrics[10] Dispatch wait time
26
+ # CSV format is URL, thread ID, timestamp, response time, action time,
27
+ # database io time, view time, dispatch wait time, uncaptured time
28
+ def add_txn_log_csv_item(buffer)
29
+ return if buffer.length == 0
30
+ $rubyrun_txn_log_reporter.info "\n----- Transaction Log at #{Time.now.ctime} -----"
31
+ buffer.each { |metrics|
32
+ $rubyrun_txn_log_reporter.info "#{metrics[2]},#{metrics[0]},#{metrics[1].strftime("%H:%M:%S")}.#{("%.3f" % metrics[1].to_f).split('.')[1]} #{metrics[1].strftime("%m/%d/%y")},#{sprintf("%0.3f", metrics[5])},#{sprintf("%0.3f", metrics[6])},#{sprintf("%0.3f", metrics[7])},#{sprintf("%0.3f", metrics[8])},#{sprintf("%0.3f", metrics[10])},#{sprintf("%0.3f", metrics[9])}"
33
+ }
34
+ end
35
+
36
+ # Add an item to performance summary RSS channel
37
+ def add_perf_summary_rss_item(req_count)
38
+ return unless $rubyrun_host_with_port # Server started but no request yet
39
+ # First, Throughput Summary
40
+ $rubyrun_throughput.shift if $rubyrun_throughput.length == $rubyrun_report_shift_age
41
+ index = $rubyrun_throughput.length
42
+ $rubyrun_throughput[index] = [Time.now, req_count.to_f/$rubyrun_report_timer*60]
43
+ bar_content = ''
44
+ label_content = ''
45
+ max = ($rubyrun_throughput.max {|a,b| a[1] <=> b[1]})[1]
46
+ max = 1 if max == 0 # Avoid divided by zero below
47
+ $rubyrun_throughput.reverse!
48
+ $rubyrun_report_shift_age.times { |i|
49
+ if $rubyrun_throughput[i]
50
+ bar_content += sprintf(THROUGHPUT_BAR_TABLE, sprintf('%0.0f',$rubyrun_throughput[i][1]), ($rubyrun_throughput[i][1]*250/max).to_i)
51
+ label_content += sprintf(THROUGHPUT_LABEL_TABLE, $rubyrun_throughput[i][0].strftime("%H:%M %b&nbsp;%d"))
52
+ else
53
+ bar_content += sprintf(THROUGHPUT_BAR_TABLE, '', 0)
54
+ label_content += sprintf(THROUGHPUT_LABEL_TABLE, '')
55
+ end
56
+ }
57
+ $rubyrun_throughput.reverse!
58
+ html_content = THROUGHPUT_HTML.sub(/%THROUGHPUT_BAR_TABLE%/, bar_content)
59
+ html_content.sub!(/%THROUGHPUT_LABEL_TABLE%/, label_content)
60
+ html_content.sub!(/%APPS_NAME%/,Rails::Configuration.new.root_path.split('/').last)
61
+ html_content.sub!(/%TIMESTAMP%/,Time.now.strftime("%H:%M:%S %m/%d/%Y"))
62
+ # Second, Top Slowest Requests
63
+ results = sort_performance_metrics
64
+ table_content = ''
65
+ 10.times { |i|
66
+ break unless results[i]
67
+ table_content += sprintf(TOP_SLOWEST_REQUESTS_TABLE, results[i][0],
68
+ (150*results[i][1][0]/results[0][1][0]).round, results[i][1][0])
69
+ }
70
+ (html_content << TOP_SLOWEST_REQUESTS_HTML).sub!(/%TOP_SLOWEST_REQUESTS_TABLE%/,table_content)
71
+ # Third, Request Performance Breakdown
72
+ odd = true
73
+ table_content = ''
74
+ results.each { |metrics|
75
+ table_content += sprintf("#{odd ? REQ_PERF_BREAKDOWN_TABLE_ODD : REQ_PERF_BREAKDOWN_TABLE_EVEN}", metrics[0], metrics[1][6], metrics[1][0], metrics[1][1], (metrics[1][1]/metrics[1][0]*100).round, metrics[1][2], (metrics[1][2]/metrics[1][0]*100).round, metrics[1][3], (metrics[1][3]/metrics[1][0]*100).round, metrics[1][5], (metrics[1][5]/metrics[1][0]*100).round, metrics[1][4])
76
+ odd = odd ? false : true
77
+ }
78
+ (html_content << REQ_PERF_BREAKDOWN_HTML).sub!(/%REQ_PERF_BREAKDOWN_TABLE%/,table_content);
79
+ $rubyrun_perf_summary_rss.add_item(RubyRunRSS::RUBYRUN_RSS_PERF_SUMMARY_ITEM_TITLE,
80
+ RubyRunRSS::RUBYRUN_RSS_PERF_SUMMARY_ITEM_DESCRIPTION, html_content)
81
+ end
82
+
83
+ # Create the folder for keeping RSS XML file and HTML files
84
+ # Create the RSS channel
85
+ def create_rss_channels
86
+ rss_folder = $rubyrun_config['RSS_PATH'] ? $rubyrun_config['RSS_PATH'] \
87
+ : "#{Rails::Configuration.new.root_path}/public/#{RubyRunRSS::RUBYRUN_RSS_FOLDER}"
88
+ begin
89
+ Dir.mkdir(rss_folder) unless File.exist?(rss_folder)
90
+ rescue Exception
91
+ rss_folder = @rubyrun_report_folder
92
+ end
93
+ $rubyrun_perf_summary_rss = RubyRunRSS.new \
94
+ RubyRunRSS::RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_TITLE,
95
+ RubyRunRSS::RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_DESCRIPTION,
96
+ rss_folder,
97
+ RubyRunRSS::RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_FILENAME,
98
+ RubyRunRSS::RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_ITEM_FILENAME \
99
+ unless $rubyrun_perf_summary_rss
100
+ $rubyrun_throughput = Array.new
101
+ end
102
+
103
+ # Create the CSV files
104
+ def create_csv_files
105
+ $rubyrun_txn_log_reporter = Logger.new("#{@rubyrun_report_folder}/#{File.basename($0, ".*")}_#{$$.to_s}_txn_log.csv", shift_age = 10, shift_size = 4096000)
106
+ $rubyrun_txn_log_reporter.info "#\n# Format: [URL],[thread ID],[timestamp],[response time],[action time],[database IO time],[view time],[dispatch delay time],[uncaptured time]\n#"
107
+ end
108
+
109
+ end
@@ -0,0 +1,97 @@
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
+ # Dynamic Application Discovery #
9
+ # #
10
+ # Last Updated: 6/03/08 #
11
+ #---------------------------------------------------------------#
12
+ # #
13
+ # RubyRunRSS provides the interfaces for creating RSS channel #
14
+ # and adding items to it. #
15
+ # #
16
+ #---------------------------------------------------------------#
17
+
18
+ require 'rss/maker'
19
+
20
+ class RubyRunRSS
21
+ RUBYRUN_RSS_VERSION = '2.0'
22
+ RUBYRUN_RSS_CHANNEL_URL = 'http://www.rubysophic.com'
23
+ RUBYRUN_RSS_IMAGE_TITLE = 'Learn more about RubyRun from Rubysophic'
24
+ RUBYRUN_RSS_IMAGE_URL = 'http://www.rubysophic.com/images/logo.jpg'
25
+ RUBYRUN_RSS_FOLDER = 'rubyrun_rss'
26
+ RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_TITLE = 'RubyRun: %s Performance Summary'
27
+ RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_ITEM_FILENAME = 'perf_summary_item'
28
+ RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_DESCRIPTION = 'RubyRun delivers up-to-the-minute performance summary of your application.'
29
+ RUBYRUN_RSS_PERF_SUMMARY_CHANNEL_FILENAME = 'perf_summary.xml'
30
+ RUBYRUN_RSS_PERF_SUMMARY_ITEM_TITLE = 'Performance summary at %s'
31
+ RUBYRUN_RSS_PERF_SUMMARY_ITEM_DESCRIPTION = 'Performance summary of %s at %s'
32
+
33
+ # Constructor of the class.
34
+ # RSS XML file and HTML files will be kept in the directory as specified
35
+ def initialize (title, description, directory, rss_filename, html_filename)
36
+ @title = title
37
+ @description = description
38
+ @directory = directory
39
+ @rss_filename = rss_filename
40
+ @html_filename = html_filename
41
+ @apps_name = Rails::Configuration.new.root_path.split('/').last
42
+ @rss_xml_destination = "#{@directory}/#{@rss_filename}"
43
+ create_channel_content unless File::exists?(@rss_xml_destination)
44
+ @rss = load_rss_content
45
+ end
46
+
47
+ # Create the RSS channel
48
+ def create_channel_content
49
+ content = RSS::Maker.make(RUBYRUN_RSS_VERSION) do |m|
50
+ m.channel.title = sprintf(@title, @apps_name)
51
+ m.channel.link = RUBYRUN_RSS_CHANNEL_URL
52
+ m.channel.description = @description
53
+ m.items.do_sort = true # sort items by date
54
+ m.image.title = RUBYRUN_RSS_IMAGE_TITLE
55
+ m.image.width = 140
56
+ m.image.height = 25
57
+ m.image.url = RUBYRUN_RSS_IMAGE_URL
58
+ end
59
+ File.open(@rss_xml_destination,"w") do |f|
60
+ f.write(content)
61
+ end
62
+ content.to_s
63
+ end
64
+
65
+ # Add an item to the RSS channel
66
+ def add_item (title, description, html_content)
67
+ filename = "#{@html_filename}_#{Time.now.to_i}#{rand(1000000)}.html"
68
+ File.open("#{@directory}/#{filename}", 'w') { |file| file.puts html_content }
69
+ item = RSS::Rss::Channel::Item.new
70
+ item.title = sprintf(title, Time.now.strftime("%H:%M:%S"))
71
+ item.description = sprintf(description, @apps_name, Time.now.strftime("%H:%M:%S"))
72
+ item.link = "http://#{$rubyrun_host_with_port}/#{RUBYRUN_RSS_FOLDER}/#{filename}"
73
+ remove_old_item(@rss) if @rss.items.length == $rubyrun_report_shift_age
74
+ @rss.items << item
75
+ File.open(@rss_xml_destination,"w") do |f|
76
+ f.write(@rss)
77
+ end
78
+ end
79
+
80
+ private
81
+
82
+ # Load the existing RSS XML file. Create a new channel if the channel is blank
83
+ def load_rss_content
84
+ content = "" # raw content of rss feed will be loaded here
85
+ open(@rss_xml_destination) do |s| content = s.read end
86
+ content = create_channel_content if content == ''
87
+ RSS::Parser.parse(content, false)
88
+ end
89
+
90
+ # Remove the last item in the RSS channel
91
+ def remove_old_item(rss)
92
+ item = rss.items.shift
93
+ filename = "#{@directory}/#{item.link.split('/').last}"
94
+ File.delete(filename) if File.exists?(filename)
95
+ end
96
+
97
+ end