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
data/lib/lrd_rack_bug.rb
CHANGED
@@ -1 +1 @@
|
|
1
|
-
require
|
1
|
+
require 'rack/bug'
|
data/lib/rack/bug.rb
CHANGED
@@ -7,7 +7,7 @@ require "rack/bug/autoloading"
|
|
7
7
|
class Rack::Bug
|
8
8
|
include Options
|
9
9
|
|
10
|
-
VERSION = "0.3.
|
10
|
+
VERSION = "0.3.1"
|
11
11
|
|
12
12
|
class SecurityError < StandardError
|
13
13
|
end
|
@@ -25,8 +25,8 @@ class Rack::Bug
|
|
25
25
|
end
|
26
26
|
|
27
27
|
def initialize(app, options = {}, &block)
|
28
|
-
@app = asset_server(app)
|
29
28
|
initialize_options options
|
29
|
+
@app = asset_server(app)
|
30
30
|
instance_eval(&block) if block_given?
|
31
31
|
|
32
32
|
@toolbar = Toolbar.new(RedirectInterceptor.new(@app))
|
@@ -52,7 +52,22 @@ private
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def asset_server(app)
|
55
|
-
|
55
|
+
builder = Rack::Builder.new
|
56
|
+
|
57
|
+
read_option(:panel_classes).each do |panel_class|
|
58
|
+
begin
|
59
|
+
middleware = panel_class.const_get("Middleware")
|
60
|
+
puts "Using middleware for #{panel_class.name}"
|
61
|
+
builder.use middleware
|
62
|
+
rescue NameError
|
63
|
+
#I guess no Middleware for you then.
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
builder.run app
|
68
|
+
app = builder.to_app
|
69
|
+
static_app = Rack::Static.new(app, :urls => ["/__rack_bug__"], :root => public_path)
|
70
|
+
return RackStaticBugAvoider.new(app, static_app)
|
56
71
|
end
|
57
72
|
|
58
73
|
def public_path
|
data/lib/rack/bug/autoloading.rb
CHANGED
@@ -36,6 +36,15 @@ module Rack
|
|
36
36
|
Thread.current["rack.bug.logs"] ||= []
|
37
37
|
end
|
38
38
|
|
39
|
+
def initialize(app)
|
40
|
+
if Rails.logger and not Rails.logger.class.include?(Rack::Bug::LoggerExtension)
|
41
|
+
Rails.logger.class.class_eval do
|
42
|
+
include Rack::Bug::LoggerExtension
|
43
|
+
end
|
44
|
+
end
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
39
48
|
def name
|
40
49
|
"log"
|
41
50
|
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'rack/bug'
|
2
|
+
begin
|
3
|
+
require 'yajl'
|
4
|
+
rescue LoadError
|
5
|
+
#Means no Chrome Speedtracer...
|
6
|
+
end
|
7
|
+
require 'uuid'
|
8
|
+
|
9
|
+
require 'rack/bug/panels/speedtracer_panel/trace-app'
|
10
|
+
require 'rack/bug/panels/speedtracer_panel/tracer'
|
11
|
+
|
12
|
+
class Rack::Bug
|
13
|
+
class SpeedTracerPanel < Panel
|
14
|
+
class Middleware
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
@uuid = UUID.new
|
18
|
+
end
|
19
|
+
|
20
|
+
def database
|
21
|
+
SpeedTracerPanel.database
|
22
|
+
end
|
23
|
+
|
24
|
+
def call(env)
|
25
|
+
if %r{^/__rack_bug__/} =~ env["REQUEST_URI"]
|
26
|
+
@app.call(env)
|
27
|
+
else
|
28
|
+
env['st.id'] = @uuid.generate
|
29
|
+
|
30
|
+
tracer = SpeedTrace::Tracer.new(env['st.id'],
|
31
|
+
env['REQUEST_METHOD'],
|
32
|
+
env['REQUEST_URI'])
|
33
|
+
env['st.tracer'] = tracer
|
34
|
+
Thread::current['st.tracer'] = tracer
|
35
|
+
|
36
|
+
status, headers, body = @app.call(env)
|
37
|
+
|
38
|
+
env['st.tracer'].finish
|
39
|
+
database[env['st.id']] = env['st.tracer']
|
40
|
+
headers['X-TraceUrl'] = '/speedtracer?id=' + env['st.id']
|
41
|
+
return [status, headers, body]
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class << self
|
47
|
+
def database
|
48
|
+
@db ||= make_database
|
49
|
+
end
|
50
|
+
|
51
|
+
def make_database
|
52
|
+
# begin
|
53
|
+
require 'rack/bug/panels/speedtracer_panel/database'
|
54
|
+
return Database.new("speedtracer")
|
55
|
+
# rescue Object => ex
|
56
|
+
# msg = "Speedtracer issue while loading SQLite DB:" + [ex.class, ex.message, ex.backtrace[0..4]].inspect
|
57
|
+
# if Rails.logger
|
58
|
+
# Rails.logger.debug msg
|
59
|
+
# else
|
60
|
+
# puts msg
|
61
|
+
# end
|
62
|
+
#
|
63
|
+
# return {}
|
64
|
+
# end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
def database
|
71
|
+
self.class.database
|
72
|
+
end
|
73
|
+
|
74
|
+
def initialize(app)
|
75
|
+
@app = app
|
76
|
+
super
|
77
|
+
end
|
78
|
+
|
79
|
+
def panel_app
|
80
|
+
return SpeedTrace::TraceApp.new(database)
|
81
|
+
end
|
82
|
+
|
83
|
+
def name
|
84
|
+
"speedtracer"
|
85
|
+
end
|
86
|
+
|
87
|
+
def heading
|
88
|
+
"#{database.keys.length} traces"
|
89
|
+
end
|
90
|
+
|
91
|
+
def content
|
92
|
+
traces = database.to_a.sort do |one, two|
|
93
|
+
two[1].start <=> one[1].start
|
94
|
+
end
|
95
|
+
advice = []
|
96
|
+
if not defined?(Yajl)
|
97
|
+
advice << "yajl-ruby not installed - Speedtracer server events won't be available"
|
98
|
+
end
|
99
|
+
if not defined?(SQLite3)
|
100
|
+
advice << "sqlite3 not installed - Speedtracer will behave oddly if run behind a forking webserver"
|
101
|
+
end
|
102
|
+
render_template "panels/speedtracer/traces", :traces => traces, :advice => advice
|
103
|
+
end
|
104
|
+
|
105
|
+
def before(env)
|
106
|
+
end
|
107
|
+
|
108
|
+
def after(env, status, headers, body)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
require 'rack/bug/panels/speedtracer_panel/instrument'
|
@@ -0,0 +1,64 @@
|
|
1
|
+
require 'sqlite3'
|
2
|
+
require 'rack/bug'
|
3
|
+
|
4
|
+
|
5
|
+
class Rack::Bug::SpeedTracerPanel
|
6
|
+
class Database
|
7
|
+
class << self
|
8
|
+
def db
|
9
|
+
if @db.nil?
|
10
|
+
open_database
|
11
|
+
end
|
12
|
+
return @db
|
13
|
+
end
|
14
|
+
|
15
|
+
def open_database
|
16
|
+
@db = SQLite3::Database.new("rack_bug.sqlite")
|
17
|
+
end
|
18
|
+
|
19
|
+
if defined?(PhusionPassenger)
|
20
|
+
PhusionPassenger.on_event(:starting_worker_process) do |forked|
|
21
|
+
open_database if forked
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def db
|
27
|
+
self.class.db
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(table_name)
|
31
|
+
@table_name = table_name
|
32
|
+
if(db.execute("select * from sqlite_master where name = ?", table_name).empty?)
|
33
|
+
db.execute("create table #@table_name ( key varchar primary key, value varchar )")
|
34
|
+
end
|
35
|
+
if Rails.logger
|
36
|
+
Rails.logger.debug{ "Initializing a table called #{table_name}" }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def [](key)
|
41
|
+
rows = db.execute("select value from #@table_name where key = ?", key.to_s)
|
42
|
+
if rows.empty?
|
43
|
+
return nil
|
44
|
+
else
|
45
|
+
Marshal.load(rows.first.first)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def []=(key, value)
|
50
|
+
db.execute("insert or replace into #@table_name ( key, value ) values ( ?, ? )", key.to_s, Marshal.dump(value))
|
51
|
+
end
|
52
|
+
|
53
|
+
def keys
|
54
|
+
db.execute("select key from #@table_name").flatten
|
55
|
+
end
|
56
|
+
|
57
|
+
def to_a
|
58
|
+
db.execute("select key, value from #@table_name").map do |key, value|
|
59
|
+
[key, Marshal.load(value)]
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require "rack/bug/panels/speedtracer_panel/instrumentation"
|
2
|
+
|
3
|
+
# Figure out what kind of app we're in front of. For right now, we'll assume
|
4
|
+
# the answer is "Rails 3"
|
5
|
+
#
|
6
|
+
# TODO: Quickly so that it does drive me nuts: what I want to do here is have a
|
7
|
+
# class responsible for per-framework intrumentation, and an auto-instrument
|
8
|
+
# class method: run through registered subclasses, looking for one that says "I
|
9
|
+
# recognize this" and then run it. First one wins. Should have a nice "I need
|
10
|
+
# to come before..." method.
|
11
|
+
|
12
|
+
|
13
|
+
Rack::Bug::SpeedTracerPanel::Instrumentation::connect do |inst|
|
14
|
+
inst.instrument("ActionView::Rendering") do |avt|
|
15
|
+
avt.trace_methods :render
|
16
|
+
end
|
17
|
+
|
18
|
+
inst.instrument("ActionView::Partials::PartialRenderer") do |avppr|
|
19
|
+
avppr.trace_methods :render
|
20
|
+
end
|
21
|
+
|
22
|
+
inst.instrument("ActiveRecord::Base") do |ar_b|
|
23
|
+
ar_b.trace_class_methods :find, :all, :first, :last, :count, :delete_all
|
24
|
+
ar_b.trace_methods :save, :save!, :destroy, :delete
|
25
|
+
end
|
26
|
+
|
27
|
+
inst.instrument("ActionController::Base") do |ac_b|
|
28
|
+
ac_b.trace_methods :process, :render
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
@@ -0,0 +1,102 @@
|
|
1
|
+
require 'rack/bug/panels/speedtracer_panel'
|
2
|
+
|
3
|
+
class Rack::Bug::SpeedTracerPanel
|
4
|
+
class Instrumentation
|
5
|
+
@instruments = Hash.new do |h,k|
|
6
|
+
h[k] = Instrument.new(k)
|
7
|
+
end
|
8
|
+
|
9
|
+
class << self
|
10
|
+
def get_instrument(const)
|
11
|
+
return @instruments[const]
|
12
|
+
end
|
13
|
+
|
14
|
+
def connect
|
15
|
+
yield(self.new)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def instrument(name)
|
20
|
+
parts = name.split("::")
|
21
|
+
begin
|
22
|
+
const = parts.inject(Kernel) do |namespace, part|
|
23
|
+
namespace.const_get(part)
|
24
|
+
end
|
25
|
+
rescue NameError => ex
|
26
|
+
warn "Couldn't find #{name}"
|
27
|
+
return nil
|
28
|
+
end
|
29
|
+
instrument = self.class.get_instrument(const)
|
30
|
+
yield(instrument) if block_given?
|
31
|
+
return instrument
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
class Instrument
|
38
|
+
def initialize(const)
|
39
|
+
@const = const
|
40
|
+
@traced = {}
|
41
|
+
@unsafe_names = (@const.public_instance_methods(true) +
|
42
|
+
@const.protected_instance_methods(true) +
|
43
|
+
@const.private_instance_methods(true)).sort.uniq
|
44
|
+
end
|
45
|
+
|
46
|
+
def trace_class_methods(*methods_names)
|
47
|
+
build_tracing_wrappers((class << @const; self; end), '#{self.name}::', *methods_names)
|
48
|
+
end
|
49
|
+
|
50
|
+
def trace_methods(*methods)
|
51
|
+
build_tracing_wrappers(@const, '#{self.class.name}#', *methods)
|
52
|
+
end
|
53
|
+
|
54
|
+
module TraceRunner
|
55
|
+
def trace_run(context = "::", called_at=caller[1], args=[])
|
56
|
+
tracer = Thread.current['st.tracer']
|
57
|
+
result = nil
|
58
|
+
if tracer.nil?
|
59
|
+
Rails.logger.debug{"No tracer in thread - #{context} / #{called_at}"}
|
60
|
+
result = yield
|
61
|
+
else
|
62
|
+
tracer.run(context, called_at, args){ result = yield }
|
63
|
+
end
|
64
|
+
result
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def safe_method_names(method_names)
|
69
|
+
method_names.map do |name|
|
70
|
+
name = name.to_s
|
71
|
+
prefix = "0"
|
72
|
+
hidden_name = ["_", prefix, name].join("_")
|
73
|
+
while @unsafe_names.include?(hidden_name)
|
74
|
+
prefix = prefix.next
|
75
|
+
hidden_name = ["_", prefix, name].join("_")
|
76
|
+
end
|
77
|
+
|
78
|
+
@unsafe_names << hidden_name
|
79
|
+
[name, hidden_name]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def build_tracing_wrappers(target, context, *methods)
|
84
|
+
safe_method_names(methods).each do |method_name, old_method|
|
85
|
+
next if @traced.has_key?(method_name)
|
86
|
+
@traced[method_name] = true
|
87
|
+
|
88
|
+
|
89
|
+
#TODO: nicer chaining
|
90
|
+
target.class_eval <<-EOC, __FILE__, __LINE__
|
91
|
+
alias #{old_method} #{method_name}
|
92
|
+
include TraceRunner
|
93
|
+
def #{method_name}(*args, &block)
|
94
|
+
|
95
|
+
trace_run("#{context}", caller(0)[0], args) do
|
96
|
+
#{old_method}(*args, &block)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
EOC
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rack/bug/speedtracer'
|
2
|
+
|
3
|
+
module Rack::Bug
|
4
|
+
#Variant of the Speed Tracer Panel that performs a nearly complete profile of
|
5
|
+
#all code called during a request. Note that this will slow overall response
|
6
|
+
#time by several orders of magnitude, and may return more data than
|
7
|
+
#SpeedTracer is prepared to display
|
8
|
+
class ProfilingSpeedTracer < SpeedTracer
|
9
|
+
def before(env)
|
10
|
+
super
|
11
|
+
tracer = env['st.tracer']
|
12
|
+
Kernel::set_trace_func proc {|event, file, line, name, binding,classname|
|
13
|
+
case event
|
14
|
+
when "c-call", "call"
|
15
|
+
methodname = classname ? "" : classname
|
16
|
+
methodname += name.to_s
|
17
|
+
tracer.start_event(file, line, name, classname || "", "")
|
18
|
+
when "c-return", "return"
|
19
|
+
tracer.finish_event
|
20
|
+
end
|
21
|
+
}
|
22
|
+
end
|
23
|
+
|
24
|
+
def after(env, status, headers, body)
|
25
|
+
Kernel::set_trace_func(nil)
|
26
|
+
super
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
class Rack::Bug
|
2
|
+
module SpeedTrace
|
3
|
+
class TraceApp
|
4
|
+
TRACER_PATH = /^\/speedtracer/.freeze
|
5
|
+
CONTENT_TYPE = 'application/json;charset=UTF-8'.freeze
|
6
|
+
|
7
|
+
FourOhFour = [404, {"Content-Type" => "text/html"}, "App tracker doesn't know that path or id"].freeze
|
8
|
+
|
9
|
+
def initialize(db)
|
10
|
+
@db = db
|
11
|
+
end
|
12
|
+
|
13
|
+
def call(env)
|
14
|
+
Rails.logger.debug{ "#{env['PATH_INFO']} ?= #{TRACER_PATH}" }
|
15
|
+
return FourOhFour unless env['PATH_INFO'].match(TRACER_PATH)
|
16
|
+
|
17
|
+
resp = Rack::Response.new('', 200)
|
18
|
+
resp['Content-Type'] = CONTENT_TYPE
|
19
|
+
|
20
|
+
Rails.logger.debug{ env['REQUEST_METHOD'] }
|
21
|
+
|
22
|
+
case env['REQUEST_METHOD']
|
23
|
+
when 'HEAD' then
|
24
|
+
# SpeedTracer dispatches HEAD requests to verify the
|
25
|
+
# tracer endpoint when it detects the X-TraceUrl
|
26
|
+
# header for the first time. After the initial load
|
27
|
+
# the verification is cached by the extension.
|
28
|
+
#
|
29
|
+
# By default, we'll return 200.
|
30
|
+
|
31
|
+
when 'GET' then
|
32
|
+
# GET requests for specific trace are generated by
|
33
|
+
# the extension when the user expands the network
|
34
|
+
# resource tab. Hence, server-side tracer data is
|
35
|
+
# request on-demand, and we need to store it for
|
36
|
+
# some time.
|
37
|
+
#
|
38
|
+
|
39
|
+
qs = Rack::Utils.parse_query(env['QUERY_STRING'])
|
40
|
+
Rails.logger.debug{ { qs['id'] => @db.keys }.inspect }
|
41
|
+
if qs['id'] && @db[qs['id']]
|
42
|
+
resp.write @db[qs['id']].to_json
|
43
|
+
else
|
44
|
+
# Invalid request or missing request trace id
|
45
|
+
return FourOhFour
|
46
|
+
end
|
47
|
+
else
|
48
|
+
# SpeedTracer should only issue GET & HEAD requests
|
49
|
+
resp.status = 400
|
50
|
+
end
|
51
|
+
|
52
|
+
return resp.finish
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|