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.
@@ -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