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.
@@ -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 :Query, "rack/bug/panels/sql_panel/query"
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 << Query.new(sql, Time.now - start_time, backtrace)
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 << Query.new(sql, duration, backtrace)
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) { |memo, query| memo + query.time}) * 1_000
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 = Query.new(params["query"], params["time"].to_f)
19
- render_template "panels/explain_sql", :result => query.explain, :query => query.sql, :time => query.time
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 = Query.new(params["query"], params["time"].to_f)
25
- render_template "panels/profile_sql", :result => query.profile, :query => query.sql, :time => query.time
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 = Query.new(params["query"], params["time"].to_f)
31
- render_template "panels/execute_sql", :result => query.execute, :query => query.sql, :time => query.time
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 Query
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 human_time
18
- "%.2fms" % (@time * 1_000)
18
+ def result
19
+ @results ||= execute
20
+ return @results
19
21
  end
20
22
 
21
- def inspectable?
22
- sql.strip =~ /^SELECT /i
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 with_profiling
26
- self.class.execute("SET PROFILING=1")
27
- result = yield
28
- self.class.execute("SET PROFILING=0")
29
- return result
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 explain
33
- self.class.execute "EXPLAIN #{@sql}"
41
+ def human_time
42
+ "%.2fms" % (@time * 1_000)
34
43
  end
35
44
 
36
- def profile
37
- with_profiling do
38
- execute
39
- self.class.execute <<-SQL
40
- SELECT *
41
- FROM information_schema.profiling
42
- WHERE query_id = (SELECT query_id FROM information_schema.profiling ORDER BY query_id DESC LIMIT 1)
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
- def self.execute(sql)
57
- ActiveRecord::Base.connection.execute(sql)
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
- if defined?(ActiveSupport::Notifications)
4
- require 'active_record/base'
5
- ActiveSupport::Notifications.subscribe(/sql.active_record/) do |*args|
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
- if defined?(ActiveSupport::Notifications)
2
+ ActionView::Template.class_eval do
3
3
 
4
- ActiveSupport::Notifications.subscribe /^render.*\.action_view/ do |*args|
5
- event = ActiveSupport::Notifications::Event.new(*args)
6
- Rack::Bug::TemplatesPanel.record_event(event)
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