logical-insight 0.4.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 (100) hide show
  1. data/History.txt +45 -0
  2. data/MIT-LICENSE.txt +19 -0
  3. data/README.md +123 -0
  4. data/Rakefile +24 -0
  5. data/Thorfile +113 -0
  6. data/lib/insight.rb +17 -0
  7. data/lib/insight/app.rb +189 -0
  8. data/lib/insight/database.rb +186 -0
  9. data/lib/insight/enable-button.rb +43 -0
  10. data/lib/insight/filtered_backtrace.rb +45 -0
  11. data/lib/insight/instrumentation.rb +9 -0
  12. data/lib/insight/instrumentation/backstage.rb +10 -0
  13. data/lib/insight/instrumentation/client.rb +20 -0
  14. data/lib/insight/instrumentation/instrument.rb +109 -0
  15. data/lib/insight/instrumentation/package-definition.rb +58 -0
  16. data/lib/insight/instrumentation/probe-definition.rb +20 -0
  17. data/lib/insight/instrumentation/probe.rb +199 -0
  18. data/lib/insight/instrumentation/setup.rb +32 -0
  19. data/lib/insight/logger.rb +55 -0
  20. data/lib/insight/options.rb +102 -0
  21. data/lib/insight/panel.rb +119 -0
  22. data/lib/insight/panel_app.rb +31 -0
  23. data/lib/insight/panels-content.rb +22 -0
  24. data/lib/insight/panels-header.rb +18 -0
  25. data/lib/insight/panels/active_record_panel.rb +46 -0
  26. data/lib/insight/panels/cache_panel.rb +69 -0
  27. data/lib/insight/panels/cache_panel/panel_app.rb +46 -0
  28. data/lib/insight/panels/cache_panel/stats.rb +98 -0
  29. data/lib/insight/panels/log_panel.rb +54 -0
  30. data/lib/insight/panels/memory_panel.rb +32 -0
  31. data/lib/insight/panels/rails_info_panel.rb +19 -0
  32. data/lib/insight/panels/redis_panel.rb +42 -0
  33. data/lib/insight/panels/redis_panel/redis_extension.rb +23 -0
  34. data/lib/insight/panels/redis_panel/stats.rb +50 -0
  35. data/lib/insight/panels/request_variables_panel.rb +70 -0
  36. data/lib/insight/panels/speedtracer_panel.rb +89 -0
  37. data/lib/insight/panels/speedtracer_panel/trace-app.rb +52 -0
  38. data/lib/insight/panels/speedtracer_panel/tracer.rb +212 -0
  39. data/lib/insight/panels/sql_panel.rb +53 -0
  40. data/lib/insight/panels/sql_panel/panel_app.rb +37 -0
  41. data/lib/insight/panels/sql_panel/query.rb +94 -0
  42. data/lib/insight/panels/templates_panel.rb +58 -0
  43. data/lib/insight/panels/templates_panel/rendering.rb +81 -0
  44. data/lib/insight/panels/timer_panel.rb +40 -0
  45. data/lib/insight/params_signature.rb +61 -0
  46. data/lib/insight/public/__insight__/bookmarklet.html +10 -0
  47. data/lib/insight/public/__insight__/bookmarklet.js +223 -0
  48. data/lib/insight/public/__insight__/insight.css +235 -0
  49. data/lib/insight/public/__insight__/insight.js +123 -0
  50. data/lib/insight/public/__insight__/jquery-1.3.2.js +4376 -0
  51. data/lib/insight/public/__insight__/jquery.tablesorter.min.js +1 -0
  52. data/lib/insight/public/__insight__/spinner.gif +0 -0
  53. data/lib/insight/rack_static_bug_avoider.rb +16 -0
  54. data/lib/insight/redirect_interceptor.rb +25 -0
  55. data/lib/insight/render.rb +72 -0
  56. data/lib/insight/request-recorder.rb +23 -0
  57. data/lib/insight/toolbar.rb +63 -0
  58. data/lib/insight/views/enable-button.html.erb +1 -0
  59. data/lib/insight/views/error.html.erb +17 -0
  60. data/lib/insight/views/headers_fragment.html.erb +20 -0
  61. data/lib/insight/views/panels/active_record.html.erb +17 -0
  62. data/lib/insight/views/panels/cache.html.erb +93 -0
  63. data/lib/insight/views/panels/execute_sql.html.erb +32 -0
  64. data/lib/insight/views/panels/explain_sql.html.erb +32 -0
  65. data/lib/insight/views/panels/log.html.erb +21 -0
  66. data/lib/insight/views/panels/profile_sql.html.erb +32 -0
  67. data/lib/insight/views/panels/rails_info.html.erb +19 -0
  68. data/lib/insight/views/panels/redis.html.erb +46 -0
  69. data/lib/insight/views/panels/request_variables.html.erb +25 -0
  70. data/lib/insight/views/panels/speedtracer/serverevent.html.erb +10 -0
  71. data/lib/insight/views/panels/speedtracer/servertrace.html.erb +12 -0
  72. data/lib/insight/views/panels/speedtracer/traces.html.erb +18 -0
  73. data/lib/insight/views/panels/sql.html.erb +43 -0
  74. data/lib/insight/views/panels/templates.html.erb +6 -0
  75. data/lib/insight/views/panels/timer.html.erb +19 -0
  76. data/lib/insight/views/panels/view_cache.html.erb +19 -0
  77. data/lib/insight/views/redirect.html.erb +16 -0
  78. data/lib/insight/views/request_fragment.html.erb +25 -0
  79. data/lib/insight/views/toolbar.html.erb +29 -0
  80. data/lib/logical-insight.rb +1 -0
  81. data/spec/custom_matchers.rb +31 -0
  82. data/spec/fixtures/config.ru +8 -0
  83. data/spec/fixtures/dummy_panel.rb +2 -0
  84. data/spec/fixtures/sample_app.rb +72 -0
  85. data/spec/insight/panels/active_record_panel_spec.rb +42 -0
  86. data/spec/insight/panels/cache_panel_spec.rb +176 -0
  87. data/spec/insight/panels/log_panel_spec.rb +44 -0
  88. data/spec/insight/panels/memory_panel_spec.rb +19 -0
  89. data/spec/insight/panels/mongo_panel_spec_pending.rb +50 -0
  90. data/spec/insight/panels/rails_info_panel_spec.rb +27 -0
  91. data/spec/insight/panels/redis_panel_spec.rb +66 -0
  92. data/spec/insight/panels/sql_panel_spec.rb +145 -0
  93. data/spec/insight/panels/templates_panel_spec.rb +84 -0
  94. data/spec/insight/panels/timer_panel_spec.rb +36 -0
  95. data/spec/insight_spec.rb +141 -0
  96. data/spec/instrumentation_spec.rb +188 -0
  97. data/spec/rcov.opts +1 -0
  98. data/spec/spec.opts +1 -0
  99. data/spec/spec_helper.rb +93 -0
  100. metadata +187 -0
@@ -0,0 +1,42 @@
1
+ module Insight
2
+
3
+ class RedisPanel < Panel
4
+ require "insight/panels/redis_panel/redis_extension"
5
+
6
+ require "insight/panels/redis_panel/stats"
7
+
8
+ def self.record(redis_command_args, backtrace, &block)
9
+ return block.call unless Insight.enabled?
10
+
11
+ start_time = Time.now
12
+ result = block.call
13
+ total_time = Time.now - start_time
14
+ stats.record_call(total_time * 1_000, redis_command_args, backtrace)
15
+ return result
16
+ end
17
+
18
+ def self.reset
19
+ Thread.current["insight.redis"] = Stats.new
20
+ end
21
+
22
+ def self.stats
23
+ Thread.current["insight.redis"] ||= Stats.new
24
+ end
25
+
26
+ def name
27
+ "redis"
28
+ end
29
+
30
+ def heading
31
+ "Redis: %.2fms (#{self.class.stats.queries.size} calls)" % self.class.stats.time
32
+ end
33
+
34
+ def content
35
+ result = render_template "panels/redis", :stats => self.class.stats
36
+ self.class.reset
37
+ return result
38
+ end
39
+
40
+ end
41
+
42
+ end
@@ -0,0 +1,23 @@
1
+ if defined?(Redis)
2
+ Redis.class_eval do
3
+ if Redis.methods.include?('call_command') # older versions of redis-rb
4
+ def call_command_with_insight(*argv)
5
+ Insight::RedisPanel.record(argv, Kernel.caller) do
6
+ call_command_without_insight(*argv)
7
+ end
8
+ end
9
+
10
+ alias_method_chain :call_command, :insight
11
+
12
+ elsif defined?(Redis::Client) # newer versions of redis-rb
13
+
14
+ Redis::Client.class_eval do
15
+ def call_with_insight(*argv)
16
+ end
17
+ end
18
+
19
+ alias_method_chain :call, :insight
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ module Insight
2
+ class RedisPanel
3
+
4
+ class Stats
5
+ class Query
6
+ include Insight::FilteredBacktrace
7
+
8
+ attr_reader :time
9
+ attr_reader :command
10
+
11
+ def initialize(time, command_args, backtrace)
12
+ @time = time
13
+ @command = command_args.inspect
14
+ @backtrace = backtrace
15
+ end
16
+
17
+ def display_time
18
+ "%.2fms" % time
19
+ end
20
+ end
21
+
22
+ attr_reader :calls
23
+ attr_reader :queries
24
+
25
+ def initialize
26
+ @queries = []
27
+ @calls = 0
28
+ @time = 0.0
29
+ end
30
+
31
+ def record_call(time, command_args, backtrace)
32
+ @queries << Query.new(time, command_args, backtrace)
33
+ @calls += 1
34
+ @time += time
35
+ end
36
+
37
+ def display_time
38
+ "%.2fms" % time
39
+ end
40
+
41
+ def time
42
+ @queries.inject(0) do |memo, query|
43
+ memo + query.time
44
+ end
45
+ end
46
+
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,70 @@
1
+ module Insight
2
+ class RequestVariablesPanel < Panel
3
+ def initialize(app)
4
+ super
5
+
6
+ table_setup("request_variables")
7
+ end
8
+
9
+ def name
10
+ "request_variables"
11
+ end
12
+
13
+ def after(env,status,headers,body)
14
+ sections = {}
15
+ sections["GET"] = sort(@request.GET) if @request.GET.any?
16
+ sections["POST"] = sort(@request.POST) if @request.POST.any?
17
+ sections["Session"] = sort(@request.env["rack.session"]) if @request.env["rack.session"] && @request.env["rack.session"].any?
18
+ sections["Cookies"] = sort(@request.env["rack.request.cookie_hash"]) if @request.env["rack.request.cookie_hash"] && @request.env["rack.request.cookie_hash"].any?
19
+ server, rack = split_and_filter_env(@env)
20
+ sections["SERVER VARIABLES"] = sort(server)
21
+ sections["Rack ENV"] = sort(rack)
22
+
23
+ # require 'pp'
24
+ # ::File.open("sections.dump", "w") do |file|
25
+ # PP.pp(sections, file)
26
+ # end
27
+ store(env, sections)
28
+ end
29
+
30
+ def heading
31
+ "Rack Env"
32
+ end
33
+
34
+ def content_for_request(number)
35
+ sections = retrieve(number).first
36
+
37
+ render_template "panels/request_variables", :sections => sections
38
+ end
39
+
40
+ private
41
+ def sort(hash)
42
+ scrub(hash.sort_by { |k, v| k.to_s })
43
+ end
44
+
45
+ def scrub(enum)
46
+ enum.map do |k,v|
47
+ if Hash === v
48
+ [k, v.inspect]
49
+ else
50
+ [k, v.to_s]
51
+ end
52
+ end
53
+ end
54
+
55
+ def split_and_filter_env(env)
56
+ server, rack = {}, {}
57
+ env.each do |k,v|
58
+ if k.index("rack.") == 0
59
+ rack[k] = v
60
+ elsif k.index("insight") == 0 or k.index("insight") == 0
61
+ #don't output the insight variables - especially secret_key
62
+ else
63
+ server[k] = v
64
+ end
65
+ end
66
+ return server, rack
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,89 @@
1
+ begin
2
+ require 'yajl'
3
+ rescue LoadError
4
+ #Means no Chrome Speedtracer...
5
+ end
6
+ require 'uuid'
7
+ require 'insight/panels/speedtracer_panel/trace-app'
8
+ require 'insight/panels/speedtracer_panel/tracer'
9
+
10
+ module Insight
11
+ module SpeedTracer
12
+ class Panel < ::Insight::Panel
13
+
14
+ def initialize(app)
15
+ @app = app
16
+ @uuid = UUID.new
17
+ table_setup("speedtracer", "uuid")
18
+ key_sql_template("'%s'")
19
+
20
+ @tracer = Tracer.new(@table)
21
+ probe(@tracer) do
22
+ instrument("ActiveSupport::Notifications") do
23
+ class_probe :instrument
24
+ end
25
+
26
+ instrument("ActionView::Rendering") do
27
+ instance_probe :render
28
+ end
29
+
30
+ instrument("ActionView::Helpers::RecordTagHelper") do
31
+ instance_probe :content_tag_for
32
+ end
33
+
34
+ instrument("ActionView::Partials::PartialRenderer") do
35
+ instance_probe :render, :find_template, :render_collection, :collection_with_template, :collection_without_template, :partial_path, :collection_paths
36
+ end
37
+
38
+ instrument("ActionView::Template") do
39
+ instance_probe :render, :compile
40
+ end
41
+
42
+ instrument("ActiveRecord::Base") do
43
+ class_probe :find, :find_by_sql, :all, :first, :last, :count, :delete_all
44
+ instance_probe :save, :save!, :destroy, :delete
45
+ end
46
+
47
+ instrument("ActionController::Base") do
48
+ instance_probe :process, :render
49
+ end
50
+ end
51
+
52
+ super
53
+ end
54
+
55
+
56
+ def call(env)
57
+ env['insight.speedtracer-id'] = @uuid.generate
58
+
59
+ status, headers, body = @app.call(env)
60
+
61
+ store(env, env['insight.speedtracer-id'], env['insight.speedtracer-record'])
62
+ headers['X-TraceUrl'] = '__insight__/speedtracer?id=' + env['insight.speedtracer-id']
63
+ return [status, headers, body]
64
+ end
65
+
66
+ def self.panel_mappings
67
+ { "speedtracer" => TraceApp.new }
68
+ end
69
+
70
+ def name
71
+ "speedtracer"
72
+ end
73
+
74
+ def heading
75
+ "#{table_length} traces"
76
+ end
77
+
78
+ def content_for_request(request_id)
79
+ trace = retrieve(request_id).first
80
+ return "" if trace.nil?
81
+ advice = []
82
+ if not defined?(Yajl)
83
+ advice << "yajl-ruby not installed - Speedtracer server events won't be available"
84
+ end
85
+ render_template "panels/speedtracer/traces", :trace => trace, :advice => advice
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,52 @@
1
+ module Insight
2
+ module SpeedTracer
3
+ class TraceApp
4
+ include Database::RequestDataClient
5
+
6
+ CONTENT_TYPE = 'application/json;charset=UTF-8'.freeze
7
+
8
+ FourOhFour = [404, {"Content-Type" => "text/html"}, "App tracker doesn't know that path or id"].freeze
9
+
10
+ def initialize
11
+ table_setup("speedtracer", "uuid")
12
+ key_sql_template = "'%s'"
13
+ end
14
+
15
+ def call(env)
16
+ resp = Rack::Response.new('', 200)
17
+ resp['Content-Type'] = CONTENT_TYPE
18
+
19
+ case env['REQUEST_METHOD']
20
+ when 'HEAD' then
21
+ # SpeedTracer dispatches HEAD requests to verify the
22
+ # tracer endpoint when it detects the X-TraceUrl
23
+ # header for the first time. After the initial load
24
+ # the verification is cached by the extension.
25
+ #
26
+ # By default, we'll return 200.
27
+
28
+ when 'GET' then
29
+ # GET requests for specific trace are generated by
30
+ # the extension when the user expands the network
31
+ # resource tab. Hence, server-side tracer data is
32
+ # request on-demand, and we need to store it for
33
+ # some time.
34
+ #
35
+
36
+ qs = Rack::Utils.parse_query(env['QUERY_STRING'])
37
+ if qs['id'] && (trace = @table.retrieve("uuid = '#{qs['id']}'"))
38
+ resp.write trace.to_json
39
+ else
40
+ # Invalid request or missing request trace id
41
+ return FourOhFour
42
+ end
43
+ else
44
+ # SpeedTracer should only issue GET & HEAD requests
45
+ resp.status = 400
46
+ end
47
+
48
+ return resp.finish
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,212 @@
1
+ module Insight
2
+ module SpeedTracer
3
+ class Tracer
4
+ def initialize(table)
5
+ @pstack = []
6
+ @table = table
7
+ @event_id = 0
8
+ end
9
+
10
+ def request_start(env, start)
11
+ id, method, uri = env.values_at("insight.speedtracer-id", "REQUEST_METHOD", "PATH_INFO")
12
+ @pstack.push RequestRecord.new(id, method, uri)
13
+ end
14
+
15
+ def request_finish(env, status, headers, body, timing)
16
+ env["insight.speedtracer-record"] = @pstack.pop
17
+ end
18
+
19
+ def before_detect(method_call, arguments)
20
+ @event_id += 1
21
+
22
+ arguments_string = make_string_of(arguments)
23
+ #XXX ServerEvent use method call...
24
+ event = ServerEvent.new(method_call, arguments_string)
25
+ @pstack.push event
26
+ end
27
+
28
+ def after_detect(method_call, timing, arguments, result)
29
+ event = @pstack.pop
30
+ if event.nil?
31
+ else
32
+ event.finish
33
+
34
+ unless (parent = @pstack.last).nil?
35
+ parent.children.push event
36
+ else
37
+ @children.push event
38
+ end
39
+ end
40
+ end
41
+
42
+ def make_string_of(array)
43
+ array.map do |item|
44
+ short_string(item)
45
+ end.join(",")
46
+ end
47
+
48
+ def short_string(item, max_per_elem = 50)
49
+ begin
50
+ string = item.inspect
51
+ if string.length > max_per_elem
52
+ case item
53
+ when NilClass
54
+ "nil"
55
+ when Hash
56
+ "{ " + item.map do |key, value|
57
+ short_string(key, 15) + "=>" + short_string(value, 30)
58
+ end.join(", ") + " }"
59
+ when find_constant("ActionView::Base")
60
+ tmpl = item.template
61
+ if tmpl.nil?
62
+ item.path.inspect
63
+ else
64
+ [tmpl.base_path, tmpl.name].join("/")
65
+ end
66
+ when find_constant("ActiveRecord::Base")
67
+ string = "#{item.class.name}(#{item.id})"
68
+ else
69
+ string = item.class.name
70
+ end
71
+ else
72
+ string
73
+ end
74
+ rescue Exception => ex
75
+ "..."
76
+ end
77
+ end
78
+
79
+ end
80
+
81
+ class TraceRecord
82
+ include Render
83
+ def initialize
84
+ @start = Time.now
85
+ @children = []
86
+ end
87
+
88
+ attr_accessor :children
89
+ attr_reader :start
90
+
91
+ def finish
92
+ @finish ||= Time.now
93
+ end
94
+
95
+ def time_in_children
96
+ @children.inject(0) do |time, child|
97
+ time + child.duration
98
+ end
99
+ end
100
+
101
+ def duration
102
+ ((@finish - @start) * 1000).to_i
103
+ end
104
+
105
+ def to_json
106
+ Yajl::Encoder.encode(hash_representation, :pretty => true, :indent => ' ')
107
+ end
108
+
109
+ private
110
+ # all timestamps in SpeedTracer are in milliseconds
111
+ def range(start, finish)
112
+ {
113
+ 'duration' => ((finish - start) * 1000).to_i,
114
+ 'start' => [start.to_i, start.usec/1000].join(''),
115
+ #'end' => [finish.to_i, finish.usec/1000].join('')
116
+ }
117
+ end
118
+
119
+ def symbolize_hash(hash)
120
+ symbolled_hash = {}
121
+ hash.each_key do |key|
122
+ if String === key
123
+ next if hash.has_key?(key.to_sym)
124
+ symbolled_hash[key.to_sym] = hash[key]
125
+ end
126
+ end
127
+ hash.merge!(symbolled_hash)
128
+ end
129
+ end
130
+
131
+ class ServerEvent < TraceRecord
132
+ attr_reader :name
133
+
134
+ def initialize(method_call, arguments)
135
+ super()
136
+ @arguments = arguments
137
+ @name = "#{method_call.context}#{method_call.kind == :instance ? "#" : "::"}#{method_call.method}(#{arguments})"
138
+ end
139
+
140
+ def hash_representation
141
+ {
142
+ 'range' => range(@start, @finish),
143
+ 'operation' => {
144
+ # 'sourceCodeLocation' => {
145
+ # 'className' => @file,
146
+ # 'methodName' => @method,
147
+ # 'lineNumber' => @line
148
+ # },
149
+ 'type' => 'METHOD',
150
+ 'label' => @name
151
+ },
152
+ 'children' => @children
153
+ }
154
+ end
155
+
156
+ def to_html
157
+ render_template('panels/speedtracer/serverevent',
158
+ {:self_time => duration - time_in_children}.merge(symbolize_hash(hash_representation)))
159
+ end
160
+ end
161
+
162
+
163
+ class RequestRecord < TraceRecord
164
+ def initialize(id, method, uri)
165
+ super()
166
+
167
+ @id = id
168
+ @method = method
169
+ @uri = uri
170
+ @event_id = 0
171
+ end
172
+
173
+ def uuid
174
+ @id
175
+ end
176
+
177
+ def hash_representation
178
+ finish
179
+ { 'trace' => {
180
+
181
+ 'url' => "/__insight__/speedtracer?id=#@id",
182
+
183
+ 'frameStack' => {
184
+
185
+ 'range' => range(@start, @finish),
186
+ 'operation' => {
187
+ 'type' => 'HTTP',
188
+ 'label' => [@method, @uri].join(' ')
189
+ },
190
+ 'children' => @children
191
+
192
+ }, #end frameStack
193
+
194
+ 'resources' => {
195
+ 'Application' => '/', #Should get the Rails app name...
196
+ 'Application.endpoint' => '/' #Should get the env path thing
197
+ }, #From what I can tell, Speed Tracer treats this whole hash as optional
198
+
199
+ 'range' => range(@start, @finish)
200
+ }
201
+ }
202
+ end
203
+
204
+ def to_html
205
+ hash = hash_representation
206
+ extra = {:self_time => duration - time_in_children}
207
+ "<a href='#{hash['trace']['url']}'>Raw JSON</a>\n" +
208
+ render_template('panels/speedtracer/serverevent', extra.merge(symbolize_hash(hash['trace']['frameStack'])))
209
+ end
210
+ end
211
+ end
212
+ end