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