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