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 CHANGED
@@ -1 +1 @@
1
- require File.expand_path('../rack/bug', __FILE__)
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.0"
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
- RackStaticBugAvoider.new(app, Rack::Static.new(app, :urls => ["/__rack_bug__"], :root => public_path))
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
@@ -22,4 +22,5 @@ class Rack::Bug
22
22
  autoload :TemplatesPanel, "rack/bug/panels/templates_panel"
23
23
  autoload :TimerPanel, "rack/bug/panels/timer_panel"
24
24
  autoload :SphinxPanel, "rack/bug/panels/sphinx_panel"
25
+ autoload :SpeedTracerPanel, "rack/bug/panels/speedtracer_panel"
25
26
  end
@@ -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
@@ -21,4 +21,4 @@ end
21
21
 
22
22
  if logger
23
23
  logger.class.send :include, Rack::Bug::LoggerExtension
24
- end
24
+ 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,9 @@
1
+ module Rack::Bug::SpeedTrace
2
+ module Render
3
+ include ::Rack::Bug::Render
4
+
5
+ def compiled_source(filename)
6
+ ::ERB.new(::File.read(::File.dirname(__FILE__) + "/../views/#{filename}.html.erb"), nil, "-").src
7
+ end
8
+ end
9
+ 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