rack-insight 0.5.0

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.
Files changed (126) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +1 -0
  3. data/.simplecov +4 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG +58 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +82 -0
  8. data/LICENSE +24 -0
  9. data/README.md +189 -0
  10. data/Rakefile +27 -0
  11. data/TODO +7 -0
  12. data/lib/rack-insight.rb +1 -0
  13. data/lib/rack/insight.rb +19 -0
  14. data/lib/rack/insight/app.rb +198 -0
  15. data/lib/rack/insight/config.rb +30 -0
  16. data/lib/rack/insight/database.rb +193 -0
  17. data/lib/rack/insight/enable-button.rb +43 -0
  18. data/lib/rack/insight/filtered_backtrace.rb +45 -0
  19. data/lib/rack/insight/instrumentation.rb +9 -0
  20. data/lib/rack/insight/instrumentation/backstage.rb +10 -0
  21. data/lib/rack/insight/instrumentation/client.rb +20 -0
  22. data/lib/rack/insight/instrumentation/instrument.rb +109 -0
  23. data/lib/rack/insight/instrumentation/package-definition.rb +58 -0
  24. data/lib/rack/insight/instrumentation/probe-definition.rb +20 -0
  25. data/lib/rack/insight/instrumentation/probe.rb +196 -0
  26. data/lib/rack/insight/instrumentation/setup.rb +32 -0
  27. data/lib/rack/insight/logger.rb +53 -0
  28. data/lib/rack/insight/options.rb +116 -0
  29. data/lib/rack/insight/panel.rb +135 -0
  30. data/lib/rack/insight/panel_app.rb +31 -0
  31. data/lib/rack/insight/panels-content.rb +22 -0
  32. data/lib/rack/insight/panels-header.rb +18 -0
  33. data/lib/rack/insight/panels/active_record_panel.rb +46 -0
  34. data/lib/rack/insight/panels/active_resource_panel.rb +48 -0
  35. data/lib/rack/insight/panels/active_resource_panel/query.rb +27 -0
  36. data/lib/rack/insight/panels/cache_panel.rb +68 -0
  37. data/lib/rack/insight/panels/cache_panel/panel_app.rb +46 -0
  38. data/lib/rack/insight/panels/cache_panel/stats.rb +90 -0
  39. data/lib/rack/insight/panels/log_panel.rb +53 -0
  40. data/lib/rack/insight/panels/memory_panel.rb +36 -0
  41. data/lib/rack/insight/panels/mongo_panel.rb +41 -0
  42. data/lib/rack/insight/panels/mongo_panel/mongo_extension.rb +24 -0
  43. data/lib/rack/insight/panels/mongo_panel/stats.rb +46 -0
  44. data/lib/rack/insight/panels/rails_info_panel.rb +19 -0
  45. data/lib/rack/insight/panels/redis_panel.rb +42 -0
  46. data/lib/rack/insight/panels/redis_panel/redis_extension.rb +23 -0
  47. data/lib/rack/insight/panels/redis_panel/stats.rb +50 -0
  48. data/lib/rack/insight/panels/request_variables_panel.rb +70 -0
  49. data/lib/rack/insight/panels/speedtracer_panel.rb +89 -0
  50. data/lib/rack/insight/panels/speedtracer_panel/profiling.rb +29 -0
  51. data/lib/rack/insight/panels/speedtracer_panel/trace-app.rb +52 -0
  52. data/lib/rack/insight/panels/speedtracer_panel/tracer.rb +213 -0
  53. data/lib/rack/insight/panels/sphinx_panel.rb +41 -0
  54. data/lib/rack/insight/panels/sphinx_panel/stats.rb +94 -0
  55. data/lib/rack/insight/panels/sql_panel.rb +53 -0
  56. data/lib/rack/insight/panels/sql_panel/panel_app.rb +37 -0
  57. data/lib/rack/insight/panels/sql_panel/query.rb +94 -0
  58. data/lib/rack/insight/panels/templates_panel.rb +58 -0
  59. data/lib/rack/insight/panels/templates_panel/rendering.rb +81 -0
  60. data/lib/rack/insight/panels/timer_panel.rb +40 -0
  61. data/lib/rack/insight/params_signature.rb +61 -0
  62. data/lib/rack/insight/path-filter.rb +23 -0
  63. data/lib/rack/insight/public/__insight__/bookmarklet.html +10 -0
  64. data/lib/rack/insight/public/__insight__/bookmarklet.js +223 -0
  65. data/lib/rack/insight/public/__insight__/insight.css +235 -0
  66. data/lib/rack/insight/public/__insight__/insight.js +127 -0
  67. data/lib/rack/insight/public/__insight__/jquery-1.3.2.js +4376 -0
  68. data/lib/rack/insight/public/__insight__/jquery.tablesorter.min.js +1 -0
  69. data/lib/rack/insight/public/__insight__/spinner.gif +0 -0
  70. data/lib/rack/insight/rack_static_bug_avoider.rb +16 -0
  71. data/lib/rack/insight/redirect_interceptor.rb +25 -0
  72. data/lib/rack/insight/render.rb +72 -0
  73. data/lib/rack/insight/request-recorder.rb +22 -0
  74. data/lib/rack/insight/rspec_matchers.rb +33 -0
  75. data/lib/rack/insight/toolbar.rb +69 -0
  76. data/lib/rack/insight/version.rb +7 -0
  77. data/lib/rack/insight/views/enable-button.html.erb +21 -0
  78. data/lib/rack/insight/views/error.html.erb +17 -0
  79. data/lib/rack/insight/views/headers_fragment.html.erb +20 -0
  80. data/lib/rack/insight/views/panels/active_record.html.erb +17 -0
  81. data/lib/rack/insight/views/panels/active_resource.html.erb +47 -0
  82. data/lib/rack/insight/views/panels/cache.html.erb +93 -0
  83. data/lib/rack/insight/views/panels/execute_sql.html.erb +32 -0
  84. data/lib/rack/insight/views/panels/explain_sql.html.erb +32 -0
  85. data/lib/rack/insight/views/panels/log.html.erb +21 -0
  86. data/lib/rack/insight/views/panels/mongo.html.erb +32 -0
  87. data/lib/rack/insight/views/panels/profile_sql.html.erb +32 -0
  88. data/lib/rack/insight/views/panels/rails_info.html.erb +19 -0
  89. data/lib/rack/insight/views/panels/redis.html.erb +46 -0
  90. data/lib/rack/insight/views/panels/request_variables.html.erb +25 -0
  91. data/lib/rack/insight/views/panels/speedtracer/serverevent.html.erb +10 -0
  92. data/lib/rack/insight/views/panels/speedtracer/servertrace.html.erb +12 -0
  93. data/lib/rack/insight/views/panels/speedtracer/traces.html.erb +18 -0
  94. data/lib/rack/insight/views/panels/sphinx.html.erb +32 -0
  95. data/lib/rack/insight/views/panels/sql.html.erb +43 -0
  96. data/lib/rack/insight/views/panels/templates.html.erb +6 -0
  97. data/lib/rack/insight/views/panels/timer.html.erb +19 -0
  98. data/lib/rack/insight/views/panels/view_cache.html.erb +19 -0
  99. data/lib/rack/insight/views/redirect.html.erb +16 -0
  100. data/lib/rack/insight/views/request_fragment.html.erb +25 -0
  101. data/lib/rack/insight/views/toolbar.html.erb +29 -0
  102. data/rack-insight.gemspec +40 -0
  103. data/spec/custom_matchers.rb +0 -0
  104. data/spec/fixtures/config.ru +8 -0
  105. data/spec/fixtures/dummy_panel.rb +2 -0
  106. data/spec/fixtures/sample_app.rb +72 -0
  107. data/spec/fixtures/star_trek_panel.rb +1 -0
  108. data/spec/insight_spec.rb +163 -0
  109. data/spec/instrumentation_spec.rb +188 -0
  110. data/spec/rack/insight/config_spec.rb +20 -0
  111. data/spec/rack/insight/panels/active_record_panel_spec.rb +43 -0
  112. data/spec/rack/insight/panels/active_resource_panel_spec.rb +40 -0
  113. data/spec/rack/insight/panels/cache_panel_spec.rb +178 -0
  114. data/spec/rack/insight/panels/log_panel_spec.rb +44 -0
  115. data/spec/rack/insight/panels/memory_panel_spec.rb +21 -0
  116. data/spec/rack/insight/panels/mongo_panel_spec_pending.rb +52 -0
  117. data/spec/rack/insight/panels/rails_info_panel_spec.rb +29 -0
  118. data/spec/rack/insight/panels/redis_panel_spec.rb +67 -0
  119. data/spec/rack/insight/panels/speedtracer_panel_spec.rb +86 -0
  120. data/spec/rack/insight/panels/sql_panel_spec.rb +146 -0
  121. data/spec/rack/insight/panels/templates_panel_spec.rb +86 -0
  122. data/spec/rack/insight/panels/timer_panel_spec.rb +38 -0
  123. data/spec/rcov.opts +1 -0
  124. data/spec/spec.opts +1 -0
  125. data/spec/spec_helper.rb +111 -0
  126. metadata +380 -0
@@ -0,0 +1,32 @@
1
+ module Rack::Insight::Instrumentation
2
+ class Setup
3
+ def initialize(app)
4
+ @app = app
5
+ end
6
+
7
+ def setup(env)
8
+ instrument = Instrument.new
9
+
10
+ PackageDefinition.start
11
+ instrument.start(env)
12
+
13
+ env["rack-insight.instrument"] = instrument
14
+ Thread::current["rack-insight.instrument"] = instrument
15
+ end
16
+
17
+ def teardown(env, status, headers, body)
18
+ instrument, env["rack-insight.instrument"] = env["rack-insight.instrument"], nil
19
+ instrument.finish(env, status, headers, body)
20
+ Thread::current["rack-insight.instrument"] = nil
21
+
22
+ env["rack-insight.duration"] = instrument.duration
23
+ end
24
+
25
+ def call(env)
26
+ setup(env)
27
+ status, headers, body = @app.call(env)
28
+ teardown(env, status, headers, body)
29
+ return [status, headers, body]
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,53 @@
1
+ module Rack::Insight
2
+ class Logger
3
+ def initialize(level, path)
4
+ @level = level
5
+ @log_path = path
6
+ @logfile = nil
7
+ end
8
+
9
+ attr_accessor :level
10
+
11
+ DEBUG = 0
12
+ INFO = 1
13
+ WARN = 2
14
+ ERROR = 3
15
+ FATAL = 4
16
+ UNKNOWN = 5
17
+
18
+ def log(severity, message)
19
+ message = message.inspect unless String === message
20
+ return unless severity >= @level
21
+
22
+ if defined? Rails and Rails.respond_to?(:logger) and not Rails.logger.nil?
23
+ Rails.logger.add(severity, "[Rack::Insight]: " + message)
24
+ end
25
+
26
+ logfile.puts(message)
27
+ end
28
+
29
+ def logfile
30
+ @logfile ||= File::open(@log_path, "a+")
31
+ rescue
32
+ $stderr
33
+ end
34
+
35
+ def debug; log(DEBUG, yield) end
36
+ def info; log(INFO, yield) end
37
+ def warn; log(WARN, yield) end
38
+ def error; log(ERROR, yield) end
39
+ def fatal; log(FATAL, yield) end
40
+ def unknown; log(UNKNOWN, yield) end
41
+ end
42
+
43
+ module Logging
44
+ def logger(env = nil)
45
+ if env.nil?
46
+ Thread.current['rack-insight.logger'] ||= Rack::Insight::Logger.new(Logger::DEBUG, "")
47
+ else
48
+ env["rack-insight.logger"] ||= Rack::Insight::Logger.new(Logger::DEBUG, "")
49
+ end
50
+ end
51
+ module_function :logger
52
+ end
53
+ end
@@ -0,0 +1,116 @@
1
+ require 'ipaddr'
2
+
3
+ module Rack::Insight
4
+ module Options
5
+ class << self
6
+ private
7
+ def option_accessor(key)
8
+ define_method(key) { || read_option(key) }
9
+ define_method("#{key}=") { |value| write_option(key, value) }
10
+ define_method("#{key}?") { || !! read_option(key) }
11
+ end
12
+ end
13
+
14
+ option_accessor :secret_key
15
+ option_accessor :ip_masks
16
+ option_accessor :password
17
+ option_accessor :panel_classes
18
+ option_accessor :intercept_redirects
19
+ option_accessor :database_path
20
+
21
+ # The underlying options Hash. During initialization (or outside of a
22
+ # request), this is a default values Hash. During a request, this is the
23
+ # Rack environment Hash. The default values Hash is merged in underneath
24
+ # the Rack environment before each request is processed.
25
+ def options
26
+ @env || @default_options
27
+ end
28
+
29
+ # Set multiple options.
30
+ def options=(hash={})
31
+ hash.each { |key,value| write_option(key, value) }
32
+ end
33
+
34
+ # Set an option. When +option+ is a Symbol, it is set in the Rack
35
+ # Environment as "rack-cache.option". When +option+ is a String, it
36
+ # exactly as specified. The +option+ argument may also be a Hash in
37
+ # which case each key/value pair is merged into the environment as if
38
+ # the #set method were called on each.
39
+ def set(option, value=self, &block)
40
+ if block_given?
41
+ write_option option, block
42
+ elsif value == self
43
+ self.options = option.to_hash
44
+ else
45
+ write_option option, value
46
+ end
47
+ end
48
+
49
+ private
50
+
51
+ def read_option(key)
52
+ key = option_name(key)
53
+ if @env and @env.has_key?(key)
54
+ @env[key]
55
+ else
56
+ @default_options[key]
57
+ end
58
+ end
59
+
60
+ def write_option(key, value)
61
+ @default_options[option_name(key)] = value
62
+ if @env.respond_to? :[]
63
+ @env[option_name(key)] = value
64
+ end
65
+ end
66
+
67
+ def option_name(key)
68
+ case key
69
+ when Symbol ; "rack-insight.#{key}"
70
+ when String ; key
71
+ else raise ArgumentError
72
+ end
73
+ end
74
+
75
+ def process_options
76
+ if(file_list = read_option('rack-insight.panel_files'))
77
+ class_list = read_option('rack-insight.panel_classes') || []
78
+ file_list.each do |file|
79
+ class_list |= Rack::Insight::Panel.from_file(file)
80
+ end
81
+ write_option('rack-insight.panel_classes', class_list)
82
+ end
83
+
84
+ Rack::Insight::Database.database_path = read_option('rack-insight.database_path')
85
+ end
86
+
87
+ def initialize_options(options=nil)
88
+ @default_options = {
89
+ 'rack-insight.ip_masks' => [IPAddr.new("127.0.0.1")],
90
+ 'rack-insight.password' => nil,
91
+ 'rack-insight.verbose' => nil,
92
+ 'rack-insight.secret_key' => nil,
93
+ 'rack-insight.intercept_redirects' => false,
94
+ 'rack-insight.panels' => [],
95
+ 'rack-insight.path_filters' => %w{/assets/},
96
+ 'rack-insight.log_level' => Logger::INFO,
97
+ 'rack-insight.log_path' => "log/rack-insight.log",
98
+ 'rack-insight.database_path' => "rack-insight.sqlite",
99
+ 'rack-insight.panel_files' => %w{
100
+ rails_info_panel
101
+ timer_panel
102
+ request_variables_panel
103
+ sql_panel
104
+ active_record_panel
105
+ cache_panel
106
+ templates_panel
107
+ log_panel
108
+ memory_panel
109
+ }
110
+ }
111
+ self.options = options || {}
112
+ process_options
113
+ end
114
+
115
+ end
116
+ end
@@ -0,0 +1,135 @@
1
+ require "erb"
2
+ require "rack/insight/logger"
3
+ require 'rack/insight/database'
4
+ require 'rack/insight/instrumentation'
5
+ require 'rack/insight/render'
6
+
7
+ module Rack::Insight
8
+
9
+ # Panels are also Rack middleware
10
+ class Panel
11
+ include ERB::Util
12
+ include Rack::Insight::Logging
13
+ include Rack::Insight::Render
14
+ include Rack::Insight::Database::RequestDataClient
15
+ include Rack::Insight::Instrumentation::Client
16
+
17
+ attr_reader :request
18
+
19
+ class << self
20
+ def file_index
21
+ return @file_index ||= Hash.new do |h,k|
22
+ h[k] = []
23
+ end
24
+ end
25
+
26
+ def panel_exclusion
27
+ return @panel_exclusion ||= []
28
+ end
29
+
30
+ def from_file(rel_path)
31
+ old_rel, Thread::current['rack-panel_file'] = Thread::current['rack-panel_file'], rel_path
32
+ Rack::Insight::Config.config[:panel_load_paths].each do |load_path|
33
+ begin
34
+ require File::join(load_path, rel_path)
35
+ rescue LoadError => e
36
+ end
37
+ end
38
+ return (file_index[rel_path] - panel_exclusion)
39
+ ensure
40
+ Thread::current['rack-panel_file'] = old_rel
41
+ end
42
+
43
+ def current_panel_file
44
+ return Thread::current['rack-panel_file'] ||
45
+ begin
46
+ file_name = nil
47
+ matched_line = nil
48
+ caller.each do |line|
49
+ # First make sure we are not matching rack-insight's own panel class, which will be in the caller stack,
50
+ # and which may match some custom load path added (try adding 'rack' as a custom load path!)
51
+ next if line =~ /rack-insight.*\/lib\/rack\/insight\/panel.rb:/
52
+ Rack::Insight::Config.config[:panel_load_paths].each do |load_path|
53
+ regex = %r{^[^:]*#{load_path}/([^:]*)\.rb:}
54
+ md = regex.match line
55
+ file_name = md[1] unless md.nil?
56
+ matched_line = line unless file_name.nil?
57
+ break unless file_name.nil?
58
+ end
59
+ break unless file_name.nil?
60
+ end
61
+ file_name
62
+ end
63
+ end
64
+
65
+ def inherited(sub)
66
+ if filename = current_panel_file
67
+ Panel::file_index[current_panel_file] << sub
68
+ else
69
+ warn "Rack::Insight::Panel inherited by #{sub.name} outside rack-insight's :panel_load_paths. Discarded. Configured panel load paths are: #{Rack::Insight::Config.config[:panel_load_paths].inspect}"
70
+ end
71
+ end
72
+
73
+ def excluded(klass = nil)
74
+ Panel::panel_exclusion << klass || self
75
+ end
76
+
77
+ end
78
+
79
+ def initialize(app)
80
+ if panel_app
81
+ #XXX use mappings
82
+ @app = Rack::Cascade.new([panel_app, app])
83
+ else
84
+ @app = app
85
+ end
86
+ end
87
+
88
+ def call(env)
89
+ @env = env
90
+ logger.debug{ "Before call: #{self.name}" }
91
+ before(env)
92
+ status, headers, body = @app.call(env)
93
+ @request = Rack::Request.new(env)
94
+ logger.debug{ "After call: #{self.name}" }
95
+ after(env, status, headers, body)
96
+ env["rack-insight.panels"] << self
97
+ return [status, headers, body]
98
+ end
99
+
100
+ def panel_app
101
+ nil
102
+ end
103
+
104
+ def self.panel_mappings
105
+ {}
106
+ end
107
+
108
+ def has_content?
109
+ true
110
+ end
111
+
112
+ def name
113
+ "Unnamed panel: #{self.class.name}" #for shame
114
+ end
115
+
116
+ def heading_for_request(number)
117
+ heading rescue "xxx" #XXX: no panel should need this
118
+ end
119
+
120
+ def content_for_request(number)
121
+ content rescue "" #XXX: no panel should need this
122
+ end
123
+
124
+ def before(env)
125
+ end
126
+
127
+ def after(env, status, headers, body)
128
+ end
129
+
130
+ def render(template)
131
+ end
132
+
133
+ end
134
+
135
+ end
@@ -0,0 +1,31 @@
1
+ module Rack::Insight
2
+
3
+ class PanelApp
4
+ include Rack::Insight::Render
5
+
6
+ attr_reader :request
7
+
8
+ def call(env)
9
+ @request = Rack::Request.new(env)
10
+ dispatch
11
+ end
12
+
13
+ def render_template(*args)
14
+ Rack::Response.new([super]).to_a
15
+ end
16
+
17
+ def params
18
+ @request.GET
19
+ end
20
+
21
+ def not_found(message="")
22
+ [404, {}, [message]]
23
+ end
24
+
25
+ def validate_params
26
+ ParamsSignature.new(request).validate!
27
+ end
28
+
29
+ end
30
+
31
+ end
@@ -0,0 +1,22 @@
1
+ module Rack::Insight
2
+ class PanelsContent < PanelApp
3
+ def initialize(insight_app)
4
+ @insight_app = insight_app
5
+ @request_table = Database::RequestTable.new
6
+ end
7
+
8
+ def dispatch
9
+ return not_found("not get") unless @request.get?
10
+ return not_found("id nil") if params['request_id'].nil?
11
+ request = @request_table.select("*", "id = #{params['request_id']}").first
12
+ return not_found("id not found") if request.nil?
13
+ requests = @request_table.to_a.map do |row|
14
+ { :id => row[0], :method => row[1], :path => row[2] }
15
+ end
16
+ render_template("request_fragment",
17
+ :request_id => params['request_id'].to_i,
18
+ :requests => requests,
19
+ :panels => @insight_app.panels)
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,18 @@
1
+ module Rack::Insight
2
+ class PanelsHeader < PanelApp
3
+ def initialize(insight_app)
4
+ @insight_app = insight_app
5
+ @request_table = Database::RequestTable.new
6
+ end
7
+
8
+ def dispatch
9
+ return not_found("not get") unless @request.get?
10
+ return not_found("id nil") if params['request_id'].nil?
11
+ request = @request_table.select("*", "id = #{params['request_id']}").first
12
+ return not_found("id not found") if request.nil?
13
+ render_template("headers_fragment",
14
+ :request_id => params['request_id'].to_i,
15
+ :panels => @insight_app.panels)
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,46 @@
1
+ module Rack::Insight
2
+ class ActiveRecordPanel < Panel
3
+ def initialize(app)
4
+ super
5
+
6
+ table_setup("active_record")
7
+
8
+ probe(self) do
9
+ instrument "ActiveRecord::Base" do
10
+ class_probe :allocate
11
+ end
12
+ end
13
+ end
14
+
15
+ def request_start(env, start)
16
+ @records = Hash.new{ 0 }
17
+ end
18
+
19
+ def after_detect(method_call, timing, results, args)
20
+ @records[method_call.object.base_class.name] += 1
21
+ end
22
+
23
+ def request_finish(env, status, headers, body, timing)
24
+ store(env, @records)
25
+ end
26
+
27
+ def name
28
+ "active_record"
29
+ end
30
+
31
+ def heading_for_request(number)
32
+ record = retrieve(number).first
33
+ total = record.inject(0) do |memo, (key, value)|
34
+ memo + value
35
+ end
36
+ "#{total} AR Objects"
37
+ end
38
+
39
+ def content_for_request(number)
40
+ records = retrieve(number).first.to_a.sort_by { |key, value| value }.reverse
41
+ render_template "panels/active_record", :records => records
42
+ end
43
+
44
+ end
45
+
46
+ end