lrd_rack_bug 0.3.0.4 → 0.3.1
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/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
|