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,198 @@
1
+ require 'rack'
2
+ require "digest/sha1"
3
+ require "rack/insight/config"
4
+ require "rack/insight/logger"
5
+ require "rack/insight/filtered_backtrace"
6
+ require "rack/insight/options"
7
+ require "rack/insight/panel"
8
+ require "rack/insight/panel_app"
9
+ require "rack/insight/params_signature"
10
+ require "rack/insight/rack_static_bug_avoider"
11
+ require "rack/insight/redirect_interceptor"
12
+ require "rack/insight/render"
13
+ require "rack/insight/toolbar"
14
+ require "rack/insight/enable-button"
15
+ require "rack/insight/path-filter"
16
+ require 'rack/insight/request-recorder'
17
+ require 'rack/insight/instrumentation/setup'
18
+ require 'rack/insight/panels-content'
19
+ require 'rack/insight/panels-header'
20
+
21
+ module Rack::Insight
22
+ class App
23
+ include Options
24
+ INSIGHT_ROOT = "/__insight__"
25
+ INSIGHT_REGEX = %r{^#{INSIGHT_ROOT}}
26
+
27
+ VERSION = "0.4.4"
28
+
29
+ class SecurityError < StandardError
30
+ end
31
+
32
+ def initialize(app, options = {}, &block)
33
+ initialize_options options
34
+ @base_app = app
35
+ @panels = []
36
+ instance_eval(&block) if block_given?
37
+
38
+ @logger = Logger.new(read_option(:log_level), read_option(:log_path))
39
+ Thread.current['rack-insight.logger'] = @logger
40
+ build_normal_stack
41
+ build_debug_stack
42
+ if options[:on_initialize]
43
+ options[:on_initialize].call(self)
44
+ end
45
+ end
46
+ attr_reader :logger
47
+ attr_accessor :panels
48
+
49
+ def call(env)
50
+ @original_request = Rack::Request.new(env)
51
+ if insight_active?
52
+ @env = env
53
+ self.options = @default_options
54
+
55
+ env['rack-insight.logger'] = @logger
56
+ Thread.current['rack-insight.logger'] = @logger
57
+
58
+ Rack::Insight.enable
59
+ env["rack-insight.panels"] = []
60
+ @debug_stack.call(env)
61
+ else
62
+ @normal_stack.call(env)
63
+ end
64
+ end
65
+
66
+
67
+ def reset(new_options=nil)
68
+ @env = nil
69
+ initialize_options(new_options)
70
+
71
+ Rack::Insight::Instrumentation::ClassProbe::all_probes.each do |probe|
72
+ probe.clear_collectors
73
+ end
74
+ Rack::Insight::Instrumentation::InstanceProbe::all_probes.each do |probe|
75
+ probe.clear_collectors
76
+ end
77
+ Rack::Insight::Instrumentation::PackageDefinition.clear_collectors
78
+
79
+ build_debug_stack
80
+ end
81
+
82
+ private
83
+
84
+ def insight_active?
85
+ return (toolbar_requested? && ip_authorized? && password_authorized?)
86
+ end
87
+
88
+ def build_normal_stack
89
+ builder = Rack::Builder.new
90
+ builder.use EnableButton, self
91
+ builder.run Rack::Cascade.new([ asset_mapped(Rack::Builder.new), @base_app ])
92
+ @normal_stack = builder.to_app
93
+ end
94
+
95
+ def build_debug_stack
96
+ @panels.clear
97
+ builder = Rack::Builder.new
98
+ builder.use Toolbar, self
99
+ builder.run Rack::Cascade.new([panel_mappings, shortcut_stack(@base_app), collection_stack(@base_app)])
100
+
101
+ @debug_stack = builder.to_app
102
+ end
103
+
104
+ def panel_mappings
105
+ classes = read_option(:panel_classes)
106
+ root = INSIGHT_ROOT
107
+ insight = self
108
+ builder = Rack::Builder.new do
109
+ classes.each do |panel_class|
110
+ panel_class.panel_mappings.each do |path, app|
111
+ map [root, path].join("/") do
112
+ run app
113
+ end
114
+ end
115
+ end
116
+ map root + "/panels_content" do
117
+ run PanelsContent.new(insight)
118
+ end
119
+ map root + "/panels_header" do
120
+ run PanelsHeader.new(insight)
121
+ end
122
+ end
123
+ return asset_mapped(builder)
124
+ end
125
+
126
+ def shortcut_stack(app)
127
+ Rack::Builder.app do
128
+ use PathFilter
129
+ run app
130
+ end
131
+ end
132
+
133
+ def collection_stack(app)
134
+ classes = read_option(:panel_classes)
135
+ insight_id = self.object_id
136
+ panels = self.panels
137
+
138
+ #Builder makes it impossible to access the panels
139
+
140
+ app = Instrumentation::Setup.new(app)
141
+ app = RedirectInterceptor.new(app)
142
+ #Reversed? Does it matter?
143
+ app = classes.inject(app) do |app, panel_class|
144
+ panel = panel_class.new(app)
145
+ panels << panel
146
+ panel
147
+ end
148
+ app = RequestRecorder.new(app)
149
+ return app
150
+ end
151
+
152
+ def asset_mapped(builder)
153
+ path = public_path
154
+ builder.map INSIGHT_ROOT do
155
+ run Rack::File.new(path)
156
+ end
157
+ builder.to_app
158
+ end
159
+
160
+ def public_path
161
+ ::File.expand_path("../../insight/public/__insight__", __FILE__)
162
+ end
163
+
164
+ def toolbar_requested?
165
+ @original_request.cookies["rack-insight_enabled"]
166
+ end
167
+
168
+ def ip_authorized?
169
+ return true unless options["rack-insight.ip_masks"]
170
+
171
+ logger.debug{ "Checking #{@original_request.ip} against ip_masks" }
172
+ ip = IPAddr.new(@original_request.ip)
173
+
174
+ mask = options["rack-insight.ip_masks"].find do |ip_mask|
175
+ ip_mask.include?(ip)
176
+ end
177
+ if mask
178
+ logger.debug{ "Matched #{mask}" }
179
+ return true
180
+ else
181
+ logger.debug{ "Matched no masks" }
182
+ return false
183
+ end
184
+ end
185
+
186
+ def password_authorized?
187
+ return true unless options["rack-insight.password"]
188
+
189
+ logger.debug{"Checking password"}
190
+
191
+ expected_sha = Digest::SHA1.hexdigest ["rack-insight", options["rack-insight.password"]].join(":")
192
+ actual_sha = @original_request.cookies["rack-insight_password"]
193
+
194
+ logger.debug{"Password result: #{actual_sha == expected_sha}"}
195
+ actual_sha == expected_sha
196
+ end
197
+ end
198
+ end
@@ -0,0 +1,30 @@
1
+ module Rack::Insight
2
+ class Config
3
+ class << self
4
+ attr_accessor :config
5
+ end
6
+
7
+ DEFAULTS = {
8
+ # You can augment or replace the default set of panel load paths.
9
+ # These are the paths where rack-insight will look for panels.
10
+ # A rack-insight extension gem could place panels in:
11
+ # lib/foo/bar/
12
+ # Since gems' lib/ is automatically shifted onto Ruby load path, this will make the custom panels discoverable:
13
+ # Rack::Insight::Config.configure do |config|
14
+ # config[:panel_load_paths] << File::join('foo', 'bar')
15
+ # end
16
+ :panel_load_paths => [File::join('rack', 'insight', 'panels')]
17
+ }
18
+
19
+ #cattr_reader :config
20
+ #cattr_writer :config
21
+
22
+ @config ||= DEFAULTS
23
+ def self.configure &block
24
+ yield @config
25
+ unless config[:panel_load_paths].kind_of?(Array)
26
+ raise "Rack::Insight::Config.config[:panel_load_paths] is invalid: Expected kind of Array but got #{config[:panel_load_paths].class}"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,193 @@
1
+ #require 'rack-insight'
2
+ require 'sqlite3'
3
+ require 'base64'
4
+
5
+ module Rack::Insight
6
+ class Database
7
+ module RequestDataClient
8
+ def key_sql_template(sql)
9
+ @key_sql_template = sql
10
+ end
11
+
12
+ def table_setup(name, *keys)
13
+ @table = DataTable.new(name, *keys)
14
+ if keys.empty?
15
+ @key_sql_template = ""
16
+ end
17
+ end
18
+
19
+ def store(env, *keys_and_value)
20
+ return if env.nil?
21
+ request_id = env["rack-insight.request-id"]
22
+ return if request_id.nil?
23
+
24
+ value = keys_and_value[-1]
25
+ keys = keys_and_value[0...-1]
26
+
27
+ @table.store(request_id, value, @key_sql_template % keys)
28
+ end
29
+
30
+ def retrieve(request_id)
31
+ @table.for_request(request_id)
32
+ end
33
+ alias retreive retrieve #JDL cannot spell
34
+
35
+ def table_length
36
+ @table.length
37
+ end
38
+ end
39
+
40
+ class << self
41
+ include Logging
42
+
43
+ def database_path=(value)
44
+ @database_path = value || "rack-insight.sqlite"
45
+ end
46
+
47
+ def database_path
48
+ @database_path
49
+ end
50
+
51
+ def db
52
+ @db ||= open_database
53
+ end
54
+
55
+ def reset
56
+ @db = nil
57
+ end
58
+
59
+ def open_database
60
+ @db = SQLite3::Database.new(database_path)
61
+ @db.execute("pragma foreign_keys = on")
62
+ @db
63
+ rescue Object => ex
64
+ msg = "Issue while loading SQLite DB:" + [ex.class, ex.message, ex.backtrace[0..4]].inspect
65
+ logger.error{ msg }
66
+
67
+ return {}
68
+ end
69
+
70
+ if defined?(PhusionPassenger)
71
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
72
+ Rack::Insight::Database::open_database if forked
73
+ end
74
+ end
75
+ end
76
+
77
+ class Table
78
+ include Logging
79
+
80
+ def db
81
+ Rack::Insight::Database.db
82
+ end
83
+
84
+ def create_keys_clause
85
+ "#{@keys.map{|key| "#{key} varchar"}.join(", ")}"
86
+ end
87
+
88
+ def create_sql
89
+ "create table #@table_name ( id integer primary key, #{create_keys_clause} )"
90
+ end
91
+
92
+ def execute(*args)
93
+ logger.info{ ins_args = args.inspect; "(#{[ins_args.length,120].min}/#{ins_args.length})" + ins_args[0..120] }
94
+ db.execute(*args)
95
+ end
96
+
97
+ def initialize(table_name, *keys)
98
+ @table_name = table_name
99
+ @keys = keys
100
+ if(execute("select * from sqlite_master where name = ?", table_name).empty?)
101
+ logger.warn{ "Initializing a table called #{table_name}" }
102
+ execute(create_sql)
103
+ end
104
+ end
105
+
106
+ def select(which_sql, condition_sql)
107
+ execute("select #{which_sql} from #@table_name where #{condition_sql}")
108
+ end
109
+
110
+ def fields_sql
111
+ "#{@keys.join(",")}"
112
+ end
113
+
114
+ def insert(values_sql)
115
+ execute("insert into #@table_name(#{fields_sql}) values (#{values_sql})")
116
+ end
117
+
118
+ def keys(name)
119
+ execute("select #{name} from #@table_name").flatten
120
+ end
121
+
122
+ def length(where = "1 = 1")
123
+ execute("select count(1) from #@table_name where #{where}").first.first
124
+ end
125
+
126
+ def to_a
127
+ execute("select * from #@table_name")
128
+ end
129
+ end
130
+
131
+ class RequestTable < Table
132
+ def initialize()
133
+ super("requests", "method", "url", "date")
134
+ end
135
+
136
+ def store(method, url)
137
+ result = insert("'#{method}', '#{url}', #{Time.now.to_i}")
138
+ db.last_insert_row_id
139
+ end
140
+
141
+ def last_request_id
142
+ execute("select max(id) from #@table_name").first.first
143
+ end
144
+
145
+ def sweep
146
+ execute("delete from #@table_name where date < #{Time.now.to_i - (60 * 60 * 12)}")
147
+ end
148
+ end
149
+
150
+ require 'yaml'
151
+ class DataTable < Table
152
+ def initialize(name, *keys)
153
+ super(name, *(%w{request_id} + keys + %w{value}))
154
+ end
155
+
156
+ def create_keys_clause
157
+ non_request_keys = @keys - %w"request_id"
158
+ sql = non_request_keys.map{|key| "#{key} varchar"}.join(", ")
159
+ sql += ", request_id references requests(id) on delete cascade"
160
+ sql
161
+ end
162
+
163
+ def store(request_id, value, keys_sql = "")
164
+ sql = "'#{encode_value(value)}'"
165
+ sql = keys_sql + ", " + sql unless keys_sql.empty?
166
+ sql = "#{request_id}, #{sql}"
167
+ insert(sql)
168
+ end
169
+
170
+ def encode_value(value)
171
+ Base64.encode64(YAML.dump(value))
172
+ end
173
+
174
+ def decode_value(value)
175
+ YAML.load(Base64.decode64(value))
176
+ end
177
+
178
+ def retrieve(key_sql)
179
+ select("value", key_sql).map{|value| decode_value(value.first)}
180
+ end
181
+
182
+ def for_request(id)
183
+ retrieve("request_id = #{id}")
184
+ end
185
+
186
+ def to_a
187
+ super.map do |row|
188
+ row[-1] = decode_value(row[-1])
189
+ end
190
+ end
191
+ end
192
+ end
193
+ end
@@ -0,0 +1,43 @@
1
+ module Rack::Insight
2
+ class EnableButton
3
+ include Render
4
+
5
+ MIME_TYPES = ["text/html", "application/xhtml+xml"]
6
+
7
+ def initialize(app, insight)
8
+ @app = app
9
+ @insight = insight
10
+ end
11
+
12
+ def call(env)
13
+ @env = env
14
+ status, headers, body = @app.call(@env)
15
+
16
+ response = Rack::Response.new(body, status, headers)
17
+
18
+ inject_button(response) if okay_to_modify?(env, response)
19
+
20
+ return response.to_a
21
+ end
22
+
23
+ def okay_to_modify?(env, response)
24
+ req = Rack::Request.new(env)
25
+ content_type, charset = response.content_type.split(";")
26
+
27
+ response.ok? && MIME_TYPES.include?(content_type) && !req.xhr?
28
+ end
29
+
30
+ def inject_button(response)
31
+ full_body = response.body.join
32
+ full_body.sub! /<\/body>/, render + "</body>"
33
+
34
+ response["Content-Length"] = full_body.bytesize.to_s
35
+
36
+ response.body = [full_body]
37
+ end
38
+
39
+ def render
40
+ render_template("enable-button")
41
+ end
42
+ end
43
+ end