lrd_rack_bug 0.3.0.4 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/lrd_rack_bug.rb +1 -1
- data/lib/rack/bug.rb +18 -3
- data/lib/rack/bug/autoloading.rb +1 -0
- data/lib/rack/bug/panels/log_panel.rb +9 -0
- data/lib/rack/bug/panels/log_panel/logger_extension.rb +1 -1
- data/lib/rack/bug/panels/speedtracer_panel.rb +113 -0
- data/lib/rack/bug/panels/speedtracer_panel/database.rb +64 -0
- data/lib/rack/bug/panels/speedtracer_panel/instrument.rb +31 -0
- data/lib/rack/bug/panels/speedtracer_panel/instrumentation.rb +102 -0
- data/lib/rack/bug/panels/speedtracer_panel/profiling.rb +29 -0
- data/lib/rack/bug/panels/speedtracer_panel/render.rb +9 -0
- data/lib/rack/bug/panels/speedtracer_panel/trace-app.rb +56 -0
- data/lib/rack/bug/panels/speedtracer_panel/tracer.rb +214 -0
- data/lib/rack/bug/panels/sql_panel.rb +7 -4
- data/lib/rack/bug/panels/sql_panel/panel_app.rb +8 -6
- data/lib/rack/bug/panels/sql_panel/query.rb +55 -23
- data/lib/rack/bug/panels/sql_panel/sql_extension.rb +6 -17
- data/lib/rack/bug/panels/templates_panel/actionview_extension.rb +6 -15
- data/lib/rack/bug/public/__rack_bug__/bug.css +5 -1
- data/lib/rack/bug/views/panels/execute_sql.html.erb +5 -5
- data/lib/rack/bug/views/panels/explain_sql.html.erb +5 -5
- data/lib/rack/bug/views/panels/speedtracer/serverevent.html.erb +10 -0
- data/lib/rack/bug/views/panels/speedtracer/servertrace.html.erb +12 -0
- data/lib/rack/bug/views/panels/speedtracer/traces.html.erb +16 -0
- data/lrd_rack_bug.gemspec +11 -10
- metadata +99 -90
@@ -0,0 +1,214 @@
|
|
1
|
+
class Rack::Bug
|
2
|
+
module SpeedTrace
|
3
|
+
class TraceRecord
|
4
|
+
include Render
|
5
|
+
def initialize(id)
|
6
|
+
@id = id
|
7
|
+
@start = Time.now
|
8
|
+
@children = []
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :start
|
12
|
+
|
13
|
+
def finish
|
14
|
+
@finish ||= Time.now
|
15
|
+
end
|
16
|
+
|
17
|
+
def time_in_children
|
18
|
+
@children.inject(0) do |time, child|
|
19
|
+
time + child.duration
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def duration
|
24
|
+
((@finish - @start) * 1000).to_i
|
25
|
+
end
|
26
|
+
|
27
|
+
def to_json
|
28
|
+
Yajl::Encoder.encode(hash_representation, :pretty => true, :indent => ' ')
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
# all timestamps in SpeedTracer are in milliseconds
|
33
|
+
def range(start, finish)
|
34
|
+
{
|
35
|
+
'duration' => ((finish - start) * 1000).to_i,
|
36
|
+
'start' => [start.to_i, start.usec/1000].join(''),
|
37
|
+
#'end' => [finish.to_i, finish.usec/1000].join('')
|
38
|
+
}
|
39
|
+
end
|
40
|
+
|
41
|
+
def symbolize_hash(hash)
|
42
|
+
hash.each_key do |key|
|
43
|
+
if String === key
|
44
|
+
next if hash.has_key?(key.to_sym)
|
45
|
+
hash[key.to_sym] = hash[key]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ServerEvent < TraceRecord
|
52
|
+
attr_accessor :children
|
53
|
+
attr_reader :name
|
54
|
+
|
55
|
+
def initialize(id, file, line, method, context, arguments)
|
56
|
+
super(id)
|
57
|
+
|
58
|
+
@file = file
|
59
|
+
@line = line
|
60
|
+
@method = method
|
61
|
+
@context = context
|
62
|
+
@arguments = arguments
|
63
|
+
@name = [context, method, "(", arguments, ")"].join("")
|
64
|
+
end
|
65
|
+
|
66
|
+
def hash_representation
|
67
|
+
{
|
68
|
+
'range' => range(@start, @finish),
|
69
|
+
#'id' => @id,
|
70
|
+
'operation' => {
|
71
|
+
# 'sourceCodeLocation' => {
|
72
|
+
# 'className' => @file,
|
73
|
+
# 'methodName' => @method,
|
74
|
+
# 'lineNumber' => @line
|
75
|
+
# },
|
76
|
+
'type' => 'METHOD',
|
77
|
+
'label' => @name
|
78
|
+
},
|
79
|
+
'children' => @children
|
80
|
+
}
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_html
|
84
|
+
render_template('panels/speedtracer/serverevent',
|
85
|
+
{:self_time => duration - time_in_children}.merge(symbolize_hash(hash_representation)))
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
class Tracer < TraceRecord
|
90
|
+
def initialize(id, method, uri)
|
91
|
+
super(id)
|
92
|
+
|
93
|
+
@method = method
|
94
|
+
@uri = uri
|
95
|
+
@event_id = 0
|
96
|
+
@pstack = []
|
97
|
+
end
|
98
|
+
|
99
|
+
#TODO: Threadsafe
|
100
|
+
def run(context="::", called_at = caller[0], args=[], &blk)
|
101
|
+
file, line, method = called_at.split(':')
|
102
|
+
|
103
|
+
method = method.gsub(/^in|[^\w]+/, '') if method
|
104
|
+
|
105
|
+
start_event(file, line, method, context, args)
|
106
|
+
blk.call # execute the provided code block
|
107
|
+
finish_event
|
108
|
+
end
|
109
|
+
|
110
|
+
def short_string(item, max_per_elem = 50)
|
111
|
+
begin
|
112
|
+
string = item.inspect
|
113
|
+
if string.length > max_per_elem
|
114
|
+
case item
|
115
|
+
when NilClass
|
116
|
+
"nil"
|
117
|
+
when Hash
|
118
|
+
"{ " + item.map do |key, value|
|
119
|
+
short_string(key, 15) + "=>" + short_string(value, 30)
|
120
|
+
end.join(", ") + " }"
|
121
|
+
when find_constant("ActionView::Base")
|
122
|
+
tmpl = item.template
|
123
|
+
if tmpl.nil?
|
124
|
+
item.path.inspect
|
125
|
+
else
|
126
|
+
[tmpl.base_path, tmpl.name].join("/")
|
127
|
+
end
|
128
|
+
when find_constant("ActiveRecord::Base")
|
129
|
+
string = "#{item.class.name}(#{item.id})"
|
130
|
+
else
|
131
|
+
string = item.class.name
|
132
|
+
end
|
133
|
+
else
|
134
|
+
string
|
135
|
+
end
|
136
|
+
rescue Exception => ex
|
137
|
+
"..."
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def make_string_of(array)
|
142
|
+
array.map do |item|
|
143
|
+
short_string(item)
|
144
|
+
end.join(",")
|
145
|
+
end
|
146
|
+
|
147
|
+
def start_event(file, line, method, context, arguments)
|
148
|
+
@event_id += 1
|
149
|
+
|
150
|
+
arguments_string = make_string_of(arguments)
|
151
|
+
event = ServerEvent.new(@event_id, file, line, method, context, arguments_string)
|
152
|
+
@pstack.push event
|
153
|
+
end
|
154
|
+
|
155
|
+
def finish_event
|
156
|
+
event = @pstack.pop
|
157
|
+
if event.nil?
|
158
|
+
else
|
159
|
+
event.finish
|
160
|
+
|
161
|
+
|
162
|
+
unless (parent = @pstack.last).nil?
|
163
|
+
parent.children.push event
|
164
|
+
else
|
165
|
+
@children.push event
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def hash_representation
|
171
|
+
finish
|
172
|
+
{ 'trace' => {
|
173
|
+
|
174
|
+
'url' => "/speedtracer?id=#@id",
|
175
|
+
|
176
|
+
'frameStack' => {
|
177
|
+
|
178
|
+
'range' => range(@start, @finish),
|
179
|
+
'operation' => {
|
180
|
+
'type' => 'HTTP',
|
181
|
+
'label' => [@method, @uri].join(' ')
|
182
|
+
},
|
183
|
+
'children' => @children
|
184
|
+
|
185
|
+
}, #end frameStack
|
186
|
+
|
187
|
+
'resources' => {
|
188
|
+
'Application' => '/', #Should get the Rails app name...
|
189
|
+
'Application.endpoint' => '/' #Should get the env path thing
|
190
|
+
}, #From what I can tell, Speed Tracer treats this whole hash as optional
|
191
|
+
|
192
|
+
'range' => range(@start, @finish)
|
193
|
+
}
|
194
|
+
}
|
195
|
+
end
|
196
|
+
|
197
|
+
def to_html
|
198
|
+
hash = hash_representation
|
199
|
+
extra = {:self_time => duration - time_in_children}
|
200
|
+
"<a href='#{hash['trace']['url']}'>Raw JSON</a>\n" +
|
201
|
+
render_template('panels/speedtracer/serverevent', extra.merge(symbolize_hash(hash['trace']['frameStack'])))
|
202
|
+
end
|
203
|
+
|
204
|
+
def finish
|
205
|
+
super()
|
206
|
+
|
207
|
+
until @pstack.empty?
|
208
|
+
finish_event
|
209
|
+
end
|
210
|
+
self
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
end
|
@@ -7,7 +7,7 @@ module Rack
|
|
7
7
|
require "rack/bug/panels/sql_panel/sql_extension"
|
8
8
|
|
9
9
|
autoload :PanelApp, "rack/bug/panels/sql_panel/panel_app"
|
10
|
-
autoload :
|
10
|
+
autoload :QueryResult, "rack/bug/panels/sql_panel/query"
|
11
11
|
|
12
12
|
def panel_app
|
13
13
|
PanelApp.new
|
@@ -18,14 +18,14 @@ module Rack
|
|
18
18
|
|
19
19
|
start_time = Time.now
|
20
20
|
result = block.call
|
21
|
-
queries <<
|
21
|
+
queries << QueryResult.new(sql, Time.now - start_time, backtrace)
|
22
22
|
|
23
23
|
return result
|
24
24
|
end
|
25
25
|
|
26
26
|
def self.record_event(sql, duration, backtrace = [])
|
27
27
|
return unless Rack::Bug.enabled?
|
28
|
-
queries <<
|
28
|
+
queries << QueryResult.new(sql, duration, backtrace)
|
29
29
|
end
|
30
30
|
|
31
31
|
def self.reset
|
@@ -37,7 +37,10 @@ module Rack
|
|
37
37
|
end
|
38
38
|
|
39
39
|
def self.total_time
|
40
|
-
(queries.inject(0)
|
40
|
+
(queries.inject(0) do |memo, query|
|
41
|
+
Rails.logger.debug{ "QTime: #{query.time}" }
|
42
|
+
memo + query.time
|
43
|
+
end) * 1_000
|
41
44
|
end
|
42
45
|
|
43
46
|
def name
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rack/bug/panels/sql_panel/query'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
class Bug
|
3
5
|
class SQLPanel
|
@@ -15,20 +17,20 @@ module Rack
|
|
15
17
|
|
16
18
|
def explain_sql
|
17
19
|
validate_params
|
18
|
-
query =
|
19
|
-
render_template "panels/explain_sql", :
|
20
|
+
query = ExplainResult.new(params["query"], params["time"].to_f)
|
21
|
+
render_template "panels/explain_sql", :query => query
|
20
22
|
end
|
21
23
|
|
22
24
|
def profile_sql
|
23
25
|
validate_params
|
24
|
-
query =
|
25
|
-
render_template "panels/profile_sql", :
|
26
|
+
query = ProfileResult.new(params["query"], params["time"].to_f)
|
27
|
+
render_template "panels/profile_sql", :query => query
|
26
28
|
end
|
27
29
|
|
28
30
|
def execute_sql
|
29
31
|
validate_params
|
30
|
-
query =
|
31
|
-
render_template "panels/execute_sql", :
|
32
|
+
query = QueryResult.new(params["query"], params["time"].to_f)
|
33
|
+
render_template "panels/execute_sql", :query => query
|
32
34
|
end
|
33
35
|
|
34
36
|
end
|
@@ -2,7 +2,7 @@ module Rack
|
|
2
2
|
class Bug
|
3
3
|
class SQLPanel
|
4
4
|
|
5
|
-
class
|
5
|
+
class QueryResult
|
6
6
|
include Rack::Bug::FilteredBacktrace
|
7
7
|
|
8
8
|
attr_reader :sql
|
@@ -12,36 +12,43 @@ module Rack
|
|
12
12
|
@sql = sql
|
13
13
|
@time = time
|
14
14
|
@backtrace = backtrace
|
15
|
+
@results = nil
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
18
|
+
def result
|
19
|
+
@results ||= execute
|
20
|
+
return @results
|
19
21
|
end
|
20
22
|
|
21
|
-
def
|
22
|
-
|
23
|
+
def column_names
|
24
|
+
if result.respond_to?(:fields)
|
25
|
+
return result.fields
|
26
|
+
else
|
27
|
+
return result.fetch_fields.map{|col| col.name}
|
28
|
+
end
|
23
29
|
end
|
24
30
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
31
|
+
def rows
|
32
|
+
if result.respond_to?(:values)
|
33
|
+
result.values
|
34
|
+
else
|
35
|
+
result.map do |row|
|
36
|
+
row
|
37
|
+
end
|
38
|
+
end
|
30
39
|
end
|
31
40
|
|
32
|
-
def
|
33
|
-
|
41
|
+
def human_time
|
42
|
+
"%.2fms" % (@time * 1_000)
|
34
43
|
end
|
35
44
|
|
36
|
-
def
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
SQL
|
44
|
-
end
|
45
|
+
def inspectable?
|
46
|
+
sql.strip =~ /^SELECT /i
|
47
|
+
end
|
48
|
+
|
49
|
+
#Downside is: we re-execute the SQL...
|
50
|
+
def self.execute(sql)
|
51
|
+
ActiveRecord::Base.connection.execute(sql)
|
45
52
|
end
|
46
53
|
|
47
54
|
def execute
|
@@ -52,12 +59,37 @@ module Rack
|
|
52
59
|
hash = Digest::SHA1.hexdigest [secret_key, @sql].join(":")
|
53
60
|
possible_hash == hash
|
54
61
|
end
|
62
|
+
end
|
55
63
|
|
56
|
-
|
57
|
-
|
64
|
+
class ExplainResult < QueryResult
|
65
|
+
def execute
|
66
|
+
self.class.execute "EXPLAIN #{@sql}"
|
58
67
|
end
|
59
68
|
end
|
60
69
|
|
70
|
+
class ProfileResult < QueryResult
|
71
|
+
def with_profiling
|
72
|
+
result = nil
|
73
|
+
begin
|
74
|
+
self.class.execute("SET PROFILING=1")
|
75
|
+
result = yield
|
76
|
+
ensure
|
77
|
+
self.class.execute("SET PROFILING=0")
|
78
|
+
end
|
79
|
+
return result
|
80
|
+
end
|
81
|
+
|
82
|
+
def execute
|
83
|
+
with_profiling do
|
84
|
+
super
|
85
|
+
self.class.execute <<-SQL
|
86
|
+
SELECT *
|
87
|
+
FROM information_schema.profiling
|
88
|
+
WHERE query_id = (SELECT query_id FROM information_schema.profiling ORDER BY query_id DESC LIMIT 1)
|
89
|
+
SQL
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
61
93
|
end
|
62
94
|
end
|
63
95
|
end
|
@@ -1,22 +1,11 @@
|
|
1
1
|
if defined?(ActiveRecord) && defined?(ActiveRecord::ConnectionAdapters)
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
event = ActiveSupport::Notifications::Event.new(*args)
|
7
|
-
Rack::Bug::SQLPanel.record_event(event.payload[:sql], event.duration) # TODO: is there any way to get a backtrace out of here?
|
8
|
-
end
|
9
|
-
else
|
10
|
-
|
11
|
-
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
12
|
-
def log_with_rack_bug(sql, name, &block)
|
13
|
-
Rack::Bug::SQLPanel.record(sql, Kernel.caller) do
|
14
|
-
log_without_rack_bug(sql, name, &block)
|
15
|
-
end
|
2
|
+
ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
|
3
|
+
def log_with_rack_bug(sql, name, &block)
|
4
|
+
Rack::Bug::SQLPanel.record(sql, Kernel.caller) do
|
5
|
+
log_without_rack_bug(sql, name, &block)
|
16
6
|
end
|
17
|
-
|
18
|
-
alias_method_chain :log, :rack_bug
|
19
7
|
end
|
20
|
-
|
8
|
+
|
9
|
+
alias_method_chain :log, :rack_bug
|
21
10
|
end
|
22
11
|
end
|
@@ -1,21 +1,12 @@
|
|
1
1
|
if defined?(ActionView) && defined?(ActionView::Template)
|
2
|
-
|
2
|
+
ActionView::Template.class_eval do
|
3
3
|
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
8
|
-
|
9
|
-
else
|
10
|
-
ActionView::Template.class_eval do
|
11
|
-
|
12
|
-
def render_template_with_rack_bug(*args, &block)
|
13
|
-
Rack::Bug::TemplatesPanel.record(path_without_format_and_extension) do
|
14
|
-
render_template_without_rack_bug(*args, &block)
|
15
|
-
end
|
4
|
+
def render_with_rack_bug(*args, &block)
|
5
|
+
Rack::Bug::TemplatesPanel.record(virtual_path) do
|
6
|
+
render_without_rack_bug(*args, &block)
|
16
7
|
end
|
17
|
-
|
18
|
-
alias_method_chain :render_template, :rack_bug
|
19
8
|
end
|
9
|
+
|
10
|
+
alias_method_chain :render, :rack_bug
|
20
11
|
end
|
21
12
|
end
|