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,20 @@
1
+ module Insight::Instrumentation
2
+ class ProbeDefinition
3
+ def initialize(package, target_name)
4
+ @package = package
5
+ @target_name = target_name
6
+ end
7
+
8
+ def instance_probe(*method_names)
9
+ if probes = @package.get_instance_probe(@target_name)
10
+ probes.probe(@package.collector, *method_names)
11
+ end
12
+ end
13
+
14
+ def class_probe(*method_names)
15
+ if probes = @package.get_class_probe(@target_name)
16
+ probes.probe(@package.collector, *method_names)
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,199 @@
1
+ module Insight
2
+ module Instrumentation
3
+ class Probe
4
+ module Interpose
5
+ end
6
+
7
+ @@class_list = nil
8
+
9
+ module ProbeRunner
10
+ include Backstage
11
+ include Logging
12
+
13
+ def probe_run(object, context = "::", kind=:instance, args=[], called_at=caller[1], method_name = nil)
14
+ if Thread.current['instrumented_backstage']
15
+ #warn "probe_run while backstage: #{context}, #{kind},
16
+ ##{method_name}" unless method_name.to_sym == :add
17
+ return yield
18
+ end
19
+ instrument = Thread.current['insight.instrument']
20
+ result = nil
21
+ if instrument.nil?
22
+ backstage do
23
+ # Rails.logger.debug{"No instrument in thread - #{context} /
24
+ # #{called_at}"}
25
+ result = yield
26
+ end
27
+ else
28
+ instrument.run(object, context, kind, called_at, method_name, args){ result = yield }
29
+ end
30
+ result
31
+ end
32
+ extend self
33
+ end
34
+
35
+ class << self
36
+ include Logging
37
+
38
+ def class_list
39
+ @@class_list ||= begin
40
+ classes = []
41
+ ObjectSpace.each_object(Class) do |klass|
42
+ classes << klass
43
+ end
44
+ classes
45
+ end
46
+ end
47
+
48
+ def get_probe_chain(name)
49
+ const = const_from_name(name)
50
+ chain = []
51
+ const.ancestors.each do |mod|
52
+ if probes.has_key?(mod.name)
53
+ chain << probes[mod.name]
54
+ end
55
+ end
56
+ chain
57
+ end
58
+
59
+ def const_from_name(name)
60
+ parts = name.split("::")
61
+ const = parts.inject(Kernel) do |namespace, part|
62
+ namespace.const_get(part)
63
+ end
64
+ end
65
+
66
+ def probes
67
+ @probes ||= Hash.new do |h,k|
68
+ begin
69
+ h[k] = self.new(const_from_name(k))
70
+ rescue NameError
71
+ logger.info{ "Cannot find constant: #{k}" }
72
+ end
73
+ end
74
+ end
75
+
76
+ def all_probes
77
+ probes.values
78
+ end
79
+
80
+ def probe_for(const)
81
+ probes[const]
82
+ end
83
+ end
84
+
85
+ def initialize(const)
86
+ @const = const
87
+ @probed = {}
88
+ @collectors = Hash.new{|h,k| h[k] = []}
89
+ @probe_orders = []
90
+ end
91
+
92
+ def collectors(key)
93
+ @collectors[key.to_sym]
94
+ end
95
+
96
+ def all_collectors
97
+ @collectors.values
98
+ end
99
+
100
+ def clear_collectors
101
+ @collectors.clear
102
+ end
103
+
104
+ def probe(collector, *methods)
105
+ methods.each do |name|
106
+ unless @collectors[name.to_sym].include?(collector)
107
+ @collectors[name.to_sym] << collector
108
+ end
109
+ @probe_orders << name
110
+ end
111
+ end
112
+
113
+ def descendants
114
+ @descendants ||= self.class.class_list.find_all do |klass|
115
+ klass.ancestors.include?(@const)
116
+ end
117
+ end
118
+
119
+ def local_method_defs(klass)
120
+ klass.instance_methods(false)
121
+ end
122
+
123
+ def descendants_that_define(method_name)
124
+ log{{ :descendants => descendants }}
125
+ descendants.find_all do |klass|
126
+ (@const != klass and local_method_defs(klass).include?(method_name))
127
+ end
128
+ end
129
+
130
+ include Logging
131
+ def log &block
132
+ logger.debug &block
133
+ #$stderr.puts block.call.inspect
134
+ end
135
+
136
+ def fulfill_probe_orders
137
+ log{{:probes_for => @const.name, :type => self.class}}
138
+ @probe_orders.each do |method_name|
139
+ log{{ :method => method_name }}
140
+ build_tracing_wrappers(@const, method_name)
141
+ descendants_that_define(method_name).each do |klass|
142
+ log{{ :subclass => klass.name }}
143
+ build_tracing_wrappers(klass, method_name)
144
+ end
145
+ end
146
+ @probe_orders.clear
147
+ end
148
+
149
+ def get_method(klass, method_name)
150
+ klass.instance_method(method_name)
151
+ end
152
+
153
+ def define_trace_method(target, method)
154
+ target.class_exec() do
155
+ define_method(method.name) do |*args, &block|
156
+ ProbeRunner::probe_run(self, method.owner.name, :instance, args, caller(0)[0], method.name) do
157
+ method.bind(self).call(*args, &block)
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ def build_tracing_wrappers(target, method_name)
164
+ return if @probed.has_key?([target,method_name])
165
+ @probed[[target,method_name]] = true
166
+
167
+ meth = get_method(target, method_name)
168
+
169
+ log{ {:tracing => meth } }
170
+ define_trace_method(target, meth)
171
+ rescue NameError => ne
172
+ log{ {:not_tracing => NameError } }
173
+ end
174
+ end
175
+
176
+ class ClassProbe < Probe
177
+ def local_method_defs(klass)
178
+ klass.singleton_methods(false)
179
+ end
180
+
181
+ def get_method(klass, method_name)
182
+ (class << klass; self; end).instance_method(method_name)
183
+ end
184
+
185
+ def define_trace_method(target, method)
186
+ (class << target; self; end).class_exec() do
187
+ define_method(method.name) do |*args, &block|
188
+ ProbeRunner::probe_run(self, target.name, :class, args, caller(0)[0], method.name) do
189
+ method.bind(self).call(*args, &block)
190
+ end
191
+ end
192
+ end
193
+ end
194
+ end
195
+
196
+ class InstanceProbe < Probe
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,32 @@
1
+ module 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["insight.instrument"] = instrument
14
+ Thread::current["insight.instrument"] = instrument
15
+ end
16
+
17
+ def teardown(env, status, headers, body)
18
+ instrument, env["insight.instrument"] = env["insight.instrument"], nil
19
+ instrument.finish(env, status, headers, body)
20
+ Thread::current["insight.instrument"] = nil
21
+
22
+ env["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,55 @@
1
+ module 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
23
+ Rails.respond_to? :logger
24
+ not Rails.logger.nil?
25
+ Rails.logger.add(severity, "[Insight]: " + message)
26
+ end
27
+
28
+ logfile.puts(message)
29
+ end
30
+
31
+ def logfile
32
+ @logfile ||= File::open(@log_path, "a+")
33
+ rescue
34
+ $stderr
35
+ end
36
+
37
+ def debug; log(DEBUG, yield) end
38
+ def info; log(INFO, yield) end
39
+ def warn; log(WARN, yield) end
40
+ def error; log(ERROR, yield) end
41
+ def fatal; log(FATAL, yield) end
42
+ def unknown; log(UNKNOWN, yield) end
43
+ end
44
+
45
+ module Logging
46
+ def logger(env = nil)
47
+ if env.nil?
48
+ Thread.current['insight.logger'] ||= Logger.new(Logger::DEBUG, "")
49
+ else
50
+ env["insight.logger"]
51
+ end
52
+ end
53
+ module_function :logger
54
+ end
55
+ end
@@ -0,0 +1,102 @@
1
+ require 'ipaddr'
2
+ module Insight
3
+ module Options
4
+ class << self
5
+ private
6
+ def option_accessor(key)
7
+ define_method(key) { || read_option(key) }
8
+ define_method("#{key}=") { |value| write_option(key, value) }
9
+ define_method("#{key}?") { || !! read_option(key) }
10
+ end
11
+ end
12
+
13
+ option_accessor :secret_key
14
+ option_accessor :ip_masks
15
+ option_accessor :password
16
+ option_accessor :panel_classes
17
+ option_accessor :intercept_redirects
18
+
19
+ # The underlying options Hash. During initialization (or outside of a
20
+ # request), this is a default values Hash. During a request, this is the
21
+ # Rack environment Hash. The default values Hash is merged in underneath
22
+ # the Rack environment before each request is processed.
23
+ def options
24
+ @env || @default_options
25
+ end
26
+
27
+ # Set multiple options.
28
+ def options=(hash={})
29
+ hash.each { |key,value| write_option(key, value) }
30
+ end
31
+
32
+ # Set an option. When +option+ is a Symbol, it is set in the Rack
33
+ # Environment as "rack-cache.option". When +option+ is a String, it
34
+ # exactly as specified. The +option+ argument may also be a Hash in
35
+ # which case each key/value pair is merged into the environment as if
36
+ # the #set method were called on each.
37
+ def set(option, value=self, &block)
38
+ if block_given?
39
+ write_option option, block
40
+ elsif value == self
41
+ self.options = option.to_hash
42
+ else
43
+ write_option option, value
44
+ end
45
+ end
46
+
47
+ private
48
+
49
+ def read_option(key)
50
+ options[option_name(key)]
51
+ end
52
+
53
+ def write_option(key, value)
54
+ options[option_name(key)] = value
55
+ end
56
+
57
+ def option_name(key)
58
+ case key
59
+ when Symbol ; "insight.#{key}"
60
+ when String ; key
61
+ else raise ArgumentError
62
+ end
63
+ end
64
+
65
+ def process_options
66
+ if(file_list = read_option('insight.panel_files'))
67
+ class_list = read_option('insight.panel_classes') || []
68
+ file_list.each do |file|
69
+ class_list |= Insight::Panel.from_file(file)
70
+ end
71
+ write_option('insight.panel_classes', class_list)
72
+ end
73
+ end
74
+
75
+ def initialize_options(options=nil)
76
+ @default_options = {
77
+ 'insight.ip_masks' => [IPAddr.new("127.0.0.1")],
78
+ 'insight.password' => nil,
79
+ 'insight.verbose' => nil,
80
+ 'insight.secret_key' => nil,
81
+ 'insight.intercept_redirects' => false,
82
+ 'insight.panels' => [],
83
+ 'insight.log_level' => Logger::INFO,
84
+ 'insight.log_path' => "log/insight.log",
85
+ 'insight.panel_files' => %w{
86
+ rails_info_panel
87
+ timer_panel
88
+ request_variables_panel
89
+ sql_panel
90
+ active_record_panel
91
+ cache_panel
92
+ templates_panel
93
+ log_panel
94
+ memory_panel
95
+ }
96
+ }
97
+ self.options = options || {}
98
+ process_options
99
+ end
100
+
101
+ end
102
+ end
@@ -0,0 +1,119 @@
1
+ require "erb"
2
+ require 'insight/database'
3
+ require 'insight/instrumentation'
4
+ require 'insight/render'
5
+
6
+ module Insight
7
+
8
+ # Panels are also Rack middleware
9
+ class Panel
10
+ include Render
11
+ include ERB::Util
12
+ include Database::RequestDataClient
13
+ include Logging
14
+ include Instrumentation::Client
15
+
16
+ attr_reader :request
17
+
18
+ class << self
19
+ def file_index
20
+ return @file_index ||= Hash.new do |h,k|
21
+ h[k] = []
22
+ end
23
+ end
24
+
25
+ def panel_exclusion
26
+ return @panel_exclusion ||= []
27
+ end
28
+
29
+ def from_file(rel_path)
30
+ old_rel, Thread::current['panel_file'] = Thread::current['panel_file'], rel_path
31
+ require File::join('insight', 'panels', rel_path)
32
+ return (file_index[rel_path] - panel_exclusion)
33
+ ensure
34
+ Thread::current['panel_file'] = old_rel
35
+ end
36
+
37
+ def current_panel_file
38
+ return Thread::current['panel_file'] ||
39
+ begin
40
+ file_name = nil
41
+ caller.each do |line|
42
+ md = %r{^[^:]*insight/panels/([^:]*)\.rb:}.match line
43
+ unless md.nil?
44
+ file_name = md[1]
45
+ end
46
+ end
47
+ file_name
48
+ end
49
+ end
50
+
51
+ def inherited(sub)
52
+ if filename = current_panel_file
53
+ Panel::file_index[current_panel_file] << sub
54
+ else
55
+ warn "Insight::Panel inherited by #{sub.name} outside of an insight/panels/* file. Discarded"
56
+ end
57
+ end
58
+
59
+ def excluded(klass = nil)
60
+ Panel::panel_exclusion << klass || self
61
+ end
62
+
63
+ end
64
+
65
+ def initialize(app)
66
+ if panel_app
67
+ #XXX use mappings
68
+ @app = Rack::Cascade.new([panel_app, app])
69
+ else
70
+ @app = app
71
+ end
72
+ end
73
+
74
+ def call(env)
75
+ @env = env
76
+ before(env)
77
+ status, headers, body = @app.call(env)
78
+ @request = Rack::Request.new(env)
79
+ after(env, status, headers, body)
80
+ env["insight.panels"] << self
81
+ return [status, headers, body]
82
+ end
83
+
84
+ def panel_app
85
+ nil
86
+ end
87
+
88
+ def self.panel_mappings
89
+ {}
90
+ end
91
+
92
+ def has_content?
93
+ true
94
+ end
95
+
96
+ def name
97
+ "Unnamed panel: #{__FILE__}" #for shame
98
+ end
99
+
100
+ def heading_for_request(number)
101
+ heading rescue "xxx" #XXX: no panel should need this
102
+ end
103
+
104
+ def content_for_request(number)
105
+ content rescue "" #XXX: no panel should need this
106
+ end
107
+
108
+ def before(env)
109
+ end
110
+
111
+ def after(env, status, headers, body)
112
+ end
113
+
114
+ def render(template)
115
+ end
116
+
117
+ end
118
+
119
+ end