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,238 @@
|
|
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
|
+
return unless ($rubyrun_thread_local[tid] || is_action?(klass, mid))
|
105
|
+
$rubyrun_thread_local[tid] ||= {}
|
106
|
+
init_thread_local(tid, request, klass, mid) unless $rubyrun_thread_local[tid][:req]
|
107
|
+
if $rubyrun_thread_local[tid][:req] != request.object_id
|
108
|
+
roll_up_metrics(tid)
|
109
|
+
init_thread_local(tid, request, klass, mid)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Create a place holding global metrics hash for each RAILS application
|
114
|
+
# controller class to accumulate performance metrics by action.
|
115
|
+
# Key elements in the hash are, for instance:
|
116
|
+
# {#{controller} => {#{action} => [dispatch_t, action_t, dbio_t, view_t, uncap_t, dispatch_wait_t]}}
|
117
|
+
def create_metrics_hash(klass)
|
118
|
+
$rubyrun_metrics_hash[klass.to_s.downcase[0..-11]] ||= {}
|
119
|
+
end
|
120
|
+
|
121
|
+
# Report timings to thread local after decomposing it into the right component.
|
122
|
+
# For each action, there are 5 components of Response Time (RT)
|
123
|
+
# :dispatch_t, :#{action}_t, :dbio_t, :view_t, :uncaptured_t
|
124
|
+
def report_rails_timing(klass, mid, t2, t1, tid)
|
125
|
+
t = t2 - t1
|
126
|
+
if is_in_hash?($rubyrun_adapter_hash, klass, mid)
|
127
|
+
$rubyrun_thread_local[tid][:dbio_t] += t
|
128
|
+
elsif is_in_hash?(RUBYRUN_VIEW_HASH, klass, mid)
|
129
|
+
$rubyrun_thread_local[tid][:view_t] << t2 << t1
|
130
|
+
elsif is_in_hash?($rubyrun_outer_dispatch_hash, klass, mid)
|
131
|
+
$rubyrun_thread_local[tid][:outer_dispatch_t] << t
|
132
|
+
elsif is_in_hash?($rubyrun_inner_dispatch_hash, klass, mid)
|
133
|
+
$rubyrun_thread_local[tid][:inner_dispatch_t] << t
|
134
|
+
elsif is_rails_controller?(klass)
|
135
|
+
$rubyrun_thread_local[tid][:action_t] = t
|
136
|
+
$rubyrun_thread_local[tid][:scafold_style] = $rubyrun_thread_local[tid][:view_t].empty? ? true : false
|
137
|
+
elsif is_in?(RUBYRUN_THREAD_END_HASH, klass, mid, 'strict')
|
138
|
+
roll_up_metrics(tid, true)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
private
|
143
|
+
|
144
|
+
# Roll up action metrics to controller_metrics_hash
|
145
|
+
# First, roll up the pieces in thread_local
|
146
|
+
# Then roll up the thread local data to the metrics global hash
|
147
|
+
def roll_up_metrics(tid, thread_end=false)
|
148
|
+
($rubyrun_thread_local[tid].clear; return) if thread_local_incomplete?(tid)
|
149
|
+
dbio_t = $rubyrun_thread_local[tid][:dbio_t]
|
150
|
+
outer_dispatch_t = $rubyrun_thread_local[tid][:outer_dispatch_t].max
|
151
|
+
inner_dispatch_t = $rubyrun_thread_local[tid][:inner_dispatch_t].max
|
152
|
+
dispatch_wait_t = outer_dispatch_t - inner_dispatch_t
|
153
|
+
view_t = $rubyrun_thread_local[tid][:view_t].empty? ? 0 : $rubyrun_thread_local[tid][:view_t].max - $rubyrun_thread_local[tid][:view_t].min
|
154
|
+
action_t = ($rubyrun_thread_local[tid][:action_t])
|
155
|
+
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
|
156
|
+
$rubyrun_thread_local[tid][:uncaptured_t] = uncap_t
|
157
|
+
$rubyrun_thread_local[tid][:dispatch_wait_t] = dispatch_wait_t
|
158
|
+
push_current_buffer([tid, Time.now,
|
159
|
+
$rubyrun_thread_local[tid][:url],
|
160
|
+
$rubyrun_thread_local[tid][:controller],
|
161
|
+
$rubyrun_thread_local[tid][:action],
|
162
|
+
outer_dispatch_t, action_t, dbio_t, view_t, uncap_t, dispatch_wait_t])
|
163
|
+
!thread_end ? $rubyrun_thread_local[tid].clear : $rubyrun_thread_local.delete(tid)
|
164
|
+
end
|
165
|
+
|
166
|
+
# Initialize the thread_local hash
|
167
|
+
def init_thread_local(tid, request, klass, mid)
|
168
|
+
$rubyrun_thread_local[tid][:req] = request.object_id
|
169
|
+
$rubyrun_thread_local[tid][:controller] = klass.to_s.split('Controller')[0].downcase
|
170
|
+
$rubyrun_thread_local[tid][:action] = return_method_name(mid)
|
171
|
+
$rubyrun_thread_local[tid][:url] = request.protocol + request.host_with_port + request.request_uri
|
172
|
+
$rubyrun_thread_local[tid][:dbio_t] = 0
|
173
|
+
$rubyrun_thread_local[tid][:outer_dispatch_t] = []
|
174
|
+
$rubyrun_thread_local[tid][:inner_dispatch_t] = []
|
175
|
+
$rubyrun_thread_local[tid][:action_t] = 0
|
176
|
+
$rubyrun_thread_local[tid][:uncaptured_t] = 0
|
177
|
+
$rubyrun_thread_local[tid][:view_t] = []
|
178
|
+
$rubyrun_thread_local[tid][:dispatch_wait_t] = 0
|
179
|
+
$rubyrun_thread_local[tid][:scafold_style] = false
|
180
|
+
$rubyrun_host_with_port = request.host_with_port
|
181
|
+
end
|
182
|
+
|
183
|
+
# Use the data hash returned by the native function to show
|
184
|
+
# the top frame inside the thread struct (node and orig_func)
|
185
|
+
def get_top_stack(th_data_hash, thread_id)
|
186
|
+
th_data_hash.each {|th, top_stack|
|
187
|
+
if th.to_s.include?(thread_id)
|
188
|
+
return "#{top_stack[0].gsub('rubyrun_', '')}"
|
189
|
+
break
|
190
|
+
end
|
191
|
+
}
|
192
|
+
end
|
193
|
+
|
194
|
+
# If request aborted, thread_local can be corrupted (half filled)
|
195
|
+
# Return true if corrupted else false
|
196
|
+
def thread_local_incomplete? (tid)
|
197
|
+
$rubyrun_thread_local[tid][:controller].nil? ||
|
198
|
+
$rubyrun_thread_local[tid][:action].nil? ||
|
199
|
+
$rubyrun_thread_local[tid][:outer_dispatch_t].empty? ||
|
200
|
+
$rubyrun_thread_local[tid][:inner_dispatch_t].empty?
|
201
|
+
end
|
202
|
+
|
203
|
+
# Sort $rubyrun_metrics_hash by response time in descending order
|
204
|
+
# An array of the following data structure is returned:
|
205
|
+
# metrics[0] controller/action name
|
206
|
+
# metrics[1] Array of performance data
|
207
|
+
# metrics[1][0] resposne time metrics[1][1] action time
|
208
|
+
# metrics[1][2] database IO time metrics[1][3] view time
|
209
|
+
# metrics[1][4] uncaptured time metrics[1][5] wait time
|
210
|
+
# metrics[1][6] request count
|
211
|
+
def sort_performance_metrics
|
212
|
+
results = Hash.new
|
213
|
+
$rubyrun_metrics_hash.each {|controller, action_metrics|
|
214
|
+
next if action_metrics.empty?
|
215
|
+
action_metrics.each {|action, metrics|
|
216
|
+
results["#{controller}/#{action}"] = metrics
|
217
|
+
}
|
218
|
+
}
|
219
|
+
results.sort {|a, b| -1*(a[1]<=>b[1])}
|
220
|
+
end
|
221
|
+
|
222
|
+
# An optimized runtime version of the original is_in? in RubyRunInstrumentor__
|
223
|
+
# This is used during runtime and not instrumentation, hence something of better
|
224
|
+
# performance but less general is required.
|
225
|
+
def is_in_hash?(hash, klass, mid)
|
226
|
+
return false if hash.empty?
|
227
|
+
name = klass.to_s
|
228
|
+
if hash.has_key?(name)
|
229
|
+
return true if hash[name].empty?
|
230
|
+
method_name = return_method_name(mid)
|
231
|
+
hash[name].each {|meth_name|
|
232
|
+
return true if method_name.downcase == meth_name.downcase
|
233
|
+
}
|
234
|
+
end
|
235
|
+
false
|
236
|
+
end
|
237
|
+
|
238
|
+
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 %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
|
@@ -0,0 +1,79 @@
|
|
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
|
+
# RubyRunTracer__ is the module for tracing a line of data #
|
13
|
+
# in the output destination. #
|
14
|
+
# #
|
15
|
+
# The source of the trace can be data from forward-tracing #
|
16
|
+
# or data collected from a stack trace. #
|
17
|
+
# #
|
18
|
+
#---------------------------------------------------------------#
|
19
|
+
module RubyRunTracer__
|
20
|
+
|
21
|
+
require 'rubyrun_globals'
|
22
|
+
require 'rubyrun_utils__'
|
23
|
+
require 'rubyrun_html__'
|
24
|
+
include RubyRunGlobals
|
25
|
+
include RubyRunUtils__
|
26
|
+
include RubyRunHTML__
|
27
|
+
|
28
|
+
# 1. If arguments are required to trace, try using kernel inspect to print it
|
29
|
+
# 2. If obejct is required to trace, try using kernel inspect to print it.
|
30
|
+
# Otherwise print class name, The inspect can cause recursion and blow up
|
31
|
+
# ruby. Rescue only delays the issue hence not used here.
|
32
|
+
# 3. Show the last caller and line
|
33
|
+
def enter_trace(tid, type, obj, invoker, klass, mid, *args)
|
34
|
+
@rubyrun_trace_odd_row = true if @rubyrun_trace_odd_row == nil
|
35
|
+
cur_time = Time.now
|
36
|
+
html_content = sprintf("#{@rubyrun_trace_odd_row ? METHOD_TRACE_ODD_ROW : METHOD_TRACE_EVEN_ROW}",
|
37
|
+
"#{cur_time.strftime("%H:%M:%S")}.#{("%.3f" % cur_time.to_f).split('.')[1]} #{cur_time.strftime("%m/%d/%y")}",
|
38
|
+
get_thread_id,
|
39
|
+
"#{type.split(' ').reverse.first}",
|
40
|
+
"#{type.split(' ').length == 3 ? '#3B9C9C' : (@rubyrun_trace_odd_row ? '#AFDCEC' : 'white')}",
|
41
|
+
"#{type.split(' ').length == 1 ? '' : (type.split(' ').length == 3 ? '*'+type.split(' ').reverse[1]+'s' : type.split(' ').reverse[1]+'s')}",
|
42
|
+
klass.to_s,
|
43
|
+
return_method_name(mid),
|
44
|
+
"#{args.each {|arg| arg.inspect} if $rubyrun_debug_args || is_in_hash?($rubyrun_adapter_hash, klass, mid)}",
|
45
|
+
"#{$rubyrun_debug_obj && obj ? obj.inspect : obj.class if obj}",
|
46
|
+
"#{invoker if invoker}")
|
47
|
+
write_trace(html_content)
|
48
|
+
@rubyrun_trace_odd_row = !@rubyrun_trace_odd_row
|
49
|
+
end
|
50
|
+
|
51
|
+
# Write a trace entry to the trace destination
|
52
|
+
def write_trace(html_content)
|
53
|
+
begin
|
54
|
+
$rubyrun_tracer.info(html_content)
|
55
|
+
rescue Exception => e
|
56
|
+
$rubyrun_logger.warn(e.to_s)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# The stack trace global hash is printed out in the rubyrun log in
|
61
|
+
# thread id order, also showing the top stack of all these
|
62
|
+
# threads before they were interrupted.
|
63
|
+
def back_trace_all(th_data_hash)
|
64
|
+
$rubyrun_thread_dump_reporter.info "----- RubyRun Thread Dump STARTS-----"
|
65
|
+
$rubyrun_thread_stack.each {|th, stack|
|
66
|
+
$rubyrun_thread_dump_reporter.info
|
67
|
+
thread_id = get_thread_id(th)
|
68
|
+
$rubyrun_thread_dump_reporter.info "Thread ID = #{thread_id}"
|
69
|
+
$rubyrun_thread_dump_reporter.info " Last line before interrupt: #{get_top_stack(th_data_hash, thread_id)}"
|
70
|
+
$rubyrun_thread_dump_reporter.info " Stack trace at interrupt"
|
71
|
+
stack.each {|line|
|
72
|
+
$rubyrun_thread_dump_reporter.info "\t#{line}"
|
73
|
+
}
|
74
|
+
}
|
75
|
+
$rubyrun_thread_dump_reporter.info
|
76
|
+
$rubyrun_thread_dump_reporter.info "----- RubyRun Thread Dump ENDS -----"
|
77
|
+
$rubyrun_thread_stack.clear
|
78
|
+
end
|
79
|
+
end
|
@@ -0,0 +1,101 @@
|
|
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
|
+
# RubyRunUtils__ is a module that owns common methods shared #
|
13
|
+
# by all the other RubyRun modules. #
|
14
|
+
# #
|
15
|
+
#---------------------------------------------------------------#
|
16
|
+
module RubyRunUtils__
|
17
|
+
|
18
|
+
require 'rubyrun_globals'
|
19
|
+
include RubyRunGlobals
|
20
|
+
|
21
|
+
# Return a readable thread ID for the current thread of execution
|
22
|
+
def get_thread_id(th=Thread.current)
|
23
|
+
th.inspect.split(/Thread:0x/)[1].split(/ .*?>/)[0]
|
24
|
+
end
|
25
|
+
|
26
|
+
# Retrieve caller details (filename, line number, method name)
|
27
|
+
def get_caller_detail(n=3)
|
28
|
+
caller(0)[n].gsub("#{RUBYRUN_PREFIX}_", '').gsub('//', '/')
|
29
|
+
end
|
30
|
+
|
31
|
+
# Environment variable not defined or defined with nil value is deemed to
|
32
|
+
# be non-existent
|
33
|
+
def env_var_exists?(var)
|
34
|
+
ENV[var].nil? || ENV[var] == '' ? false : true
|
35
|
+
end
|
36
|
+
|
37
|
+
# Error exit
|
38
|
+
def fatal_exit(e)
|
39
|
+
$stderr.print e.to_s + "\n" + e.backtrace.join("\n")
|
40
|
+
exit(-1)
|
41
|
+
end
|
42
|
+
|
43
|
+
# Return true if a Rails Action Controller class
|
44
|
+
# A module or object has no superclass hence the rescue clause
|
45
|
+
def is_rails_controller?(klass)
|
46
|
+
return true if $rubyrun_controller_classes.include?(klass)
|
47
|
+
$rubyrun_rails_env ||= ENV['RAILS_ENV']
|
48
|
+
begin
|
49
|
+
($rubyrun_controller_classes << klass; return true) if $rubyrun_rails_env &&
|
50
|
+
klass.to_s[-10, 10] == 'Controller' && is_application_controller(klass)
|
51
|
+
rescue
|
52
|
+
end
|
53
|
+
false
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return true if this is an action method in a controller class
|
57
|
+
def is_action?(klass, mid)
|
58
|
+
!klass.private_instance_methods(false).include?(return_method_name(mid))
|
59
|
+
end
|
60
|
+
|
61
|
+
# Given a class, it's deemed to be a Rails Action Controller if one of its
|
62
|
+
# ancestors is ApplicationController
|
63
|
+
def is_application_controller(klass)
|
64
|
+
return false unless klass.superclass
|
65
|
+
if klass.superclass == ApplicationController
|
66
|
+
return true
|
67
|
+
else
|
68
|
+
is_application_controller(klass.superclass)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Return false if the passed in hash is empty
|
73
|
+
# Return false if the hash doenst even have the class name as a key
|
74
|
+
# Return true if the hash has the key but the method array is empty
|
75
|
+
# Return true if the method array has a case-insensitive matching name,
|
76
|
+
# matching can be exact or 'include'.
|
77
|
+
# Otherwise return false
|
78
|
+
def is_in?(hash, klass, mid, mode='loose')
|
79
|
+
return false if hash.empty?
|
80
|
+
[klass.to_s, klass.class.to_s, '*'].each {|name|
|
81
|
+
if hash.has_key?(name)
|
82
|
+
return true if hash[name].empty?
|
83
|
+
method_name = return_method_name(mid)
|
84
|
+
hash[name].each {|meth_name|
|
85
|
+
case mode
|
86
|
+
when 'strict'
|
87
|
+
return true if method_name.downcase == meth_name.downcase
|
88
|
+
when 'loose'
|
89
|
+
return true if method_name.downcase.include?(meth_name.downcase)
|
90
|
+
end
|
91
|
+
}
|
92
|
+
end
|
93
|
+
}
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
# Return method name since mid can be an method object ID or a string
|
98
|
+
def return_method_name(mid)
|
99
|
+
mid.kind_of?(String) ? mid : mid.id2name
|
100
|
+
end
|
101
|
+
end
|
Binary file
|