rubyrun 0.9.0-mswin32

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