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,186 @@
1
+ require 'insight'
2
+ require 'sqlite3'
3
+ require 'base64'
4
+
5
+ module 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["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
+ def db
43
+ @db ||= open_database
44
+ end
45
+
46
+ def reset
47
+ @db = nil
48
+ end
49
+
50
+ def open_database
51
+ @db = SQLite3::Database.new("insight.sqlite")
52
+ @db.execute("pragma foreign_keys = on")
53
+ @db
54
+ rescue Object => ex
55
+ msg = "Issue while loading SQLite DB:" + [ex.class, ex.message, ex.backtrace[0..4]].inspect
56
+ logger.debug{ msg }
57
+
58
+ return {}
59
+ end
60
+
61
+ if defined?(PhusionPassenger)
62
+ PhusionPassenger.on_event(:starting_worker_process) do |forked|
63
+ open_database if forked
64
+ end
65
+ end
66
+ end
67
+
68
+ class Table
69
+ include Logging
70
+
71
+ def db
72
+ Insight::Database.db
73
+ end
74
+
75
+ def create_keys_clause
76
+ "#{@keys.map{|key| "#{key} varchar"}.join(", ")}"
77
+ end
78
+
79
+ def create_sql
80
+ "create table #@table_name ( id integer primary key, #{create_keys_clause} )"
81
+ end
82
+
83
+ def execute(*args)
84
+ logger.debug{ ins_args = args.inspect; "(#{[ins_args.length,120].min}/#{ins_args.length})" + ins_args[0..120] }
85
+ db.execute(*args)
86
+ end
87
+
88
+ def initialize(table_name, *keys)
89
+ @table_name = table_name
90
+ @keys = keys
91
+ if(execute("select * from sqlite_master where name = ?", table_name).empty?)
92
+ execute(create_sql)
93
+
94
+ logger.debug{ "Initializing a table called #{table_name}" }
95
+ end
96
+ end
97
+
98
+ def select(which_sql, condition_sql)
99
+ execute("select #{which_sql} from #@table_name where #{condition_sql}")
100
+ end
101
+
102
+ def fields_sql
103
+ "#{@keys.join(",")}"
104
+ end
105
+
106
+ def insert(values_sql)
107
+ execute("insert into #@table_name(#{fields_sql}) values (#{values_sql})")
108
+ end
109
+
110
+ def keys(name)
111
+ execute("select #{name} from #@table_name").flatten
112
+ end
113
+
114
+ def length(where = "1 = 1")
115
+ execute("select count(1) from #@table_name where #{where}").first.first
116
+ end
117
+
118
+ def to_a
119
+ execute("select * from #@table_name")
120
+ end
121
+ end
122
+
123
+ class RequestTable < Table
124
+ def initialize()
125
+ super("requests", "method", "url", "date")
126
+ end
127
+
128
+ def store(method, url)
129
+ result = insert("'#{method}', '#{url}', #{Time.now.to_i}")
130
+ db.last_insert_row_id
131
+ end
132
+
133
+ def last_request_id
134
+ execute("select max(id) from #@table_name").first.first
135
+ end
136
+
137
+ def sweep
138
+ execute("delete from #@table_name where date < #{Time.now.to_i - (60 * 60 * 12)}")
139
+ end
140
+ end
141
+
142
+ require 'yaml'
143
+ class DataTable < Table
144
+ def initialize(name, *keys)
145
+ super(name, *(%w{request_id} + keys + %w{value}))
146
+ end
147
+
148
+ def create_keys_clause
149
+ non_request_keys = @keys - %w"request_id"
150
+ sql = non_request_keys.map{|key| "#{key} varchar"}.join(", ")
151
+ sql += ", request_id references requests(id) on delete cascade"
152
+ sql
153
+ end
154
+
155
+ def store(request_id, value, keys_sql = "")
156
+ sql = "'#{encode_value(value)}'"
157
+ sql = keys_sql + ", " + sql unless keys_sql.empty?
158
+ sql = "#{request_id}, #{sql}"
159
+ insert(sql)
160
+ end
161
+
162
+ def encode_value(value)
163
+ Base64.encode64(YAML.dump(value))
164
+ end
165
+
166
+ def decode_value(value)
167
+ YAML.load(Base64.decode64(value))
168
+ end
169
+
170
+ def retrieve(key_sql)
171
+ select("value", key_sql).map{|value| decode_value(value.first)}
172
+ end
173
+
174
+ def for_request(id)
175
+ retrieve("request_id = #{id}")
176
+ end
177
+
178
+ def to_a
179
+ super.map do |row|
180
+ row[-1] = decode_value(row[-1])
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+
@@ -0,0 +1,43 @@
1
+ module 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.size.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
@@ -0,0 +1,45 @@
1
+ module Insight
2
+ module FilteredBacktrace
3
+
4
+ def backtrace
5
+ @backtrace
6
+ end
7
+
8
+ def has_backtrace?
9
+ filtered_backtrace.any?
10
+ end
11
+
12
+ def filtered_backtrace
13
+ @filtered_backtrace ||= @backtrace.grep(FilteredBacktrace.backtrace_regexp)
14
+ end
15
+
16
+ def self.backtrace_regexp
17
+ @backtrace_regexp ||=
18
+ begin
19
+ if true or (app_root = root_for_backtrace_filtering).nil?
20
+ /.*/
21
+ else
22
+ excludes = %w{vendor}
23
+ %r{^#{app_root}(?:#{::File::Separator}(?!#{excludes.join("|")}).+)$}
24
+ end
25
+ end
26
+ end
27
+
28
+ def self.root_for_backtrace_filtering(sub_path = nil)
29
+ if defined?(Rails) && Rails.respond_to?(:root)
30
+ sub_path ? Rails.root.join(sub_path) : Rails.root
31
+ else
32
+ root = if defined?(RAILS_ROOT)
33
+ RAILS_ROOT
34
+ elsif defined?(ROOT)
35
+ ROOT
36
+ elsif defined?(Sinatra::Application)
37
+ Sinatra::Application.root
38
+ else
39
+ nil
40
+ end
41
+ sub_path ? ::File.join(root, sub_path) : root
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,9 @@
1
+ module Insight
2
+ module Instrumentation; end
3
+ end
4
+ require 'insight/instrumentation/instrument'
5
+ require 'insight/instrumentation/probe'
6
+ require 'insight/instrumentation/client'
7
+ require 'insight/instrumentation/setup'
8
+ require 'insight/instrumentation/package-definition'
9
+ require 'insight/instrumentation/probe-definition'
@@ -0,0 +1,10 @@
1
+ module Insight::Instrumentation
2
+ module Backstage
3
+ def backstage
4
+ Thread.current["instrumented_backstage"] = true
5
+ yield
6
+ ensure
7
+ Thread.current["instrumented_backstage"] = false
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,20 @@
1
+ require 'insight/instrumentation/package-definition'
2
+ module Insight::Instrumentation
3
+ module Client
4
+ def probe(collector, &block)
5
+ ::Insight::Instrumentation::PackageDefinition::probe(collector, &block)
6
+ end
7
+
8
+ def request_start(env, start)
9
+ end
10
+
11
+ def before_detect(method_call, arguments)
12
+ end
13
+
14
+ def after_detect(method_call, timing, arguments, result)
15
+ end
16
+
17
+ def request_finish(env, status, headers, body, timing)
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,109 @@
1
+ require 'insight/instrumentation/backstage'
2
+ require 'insight/logger'
3
+
4
+ module Insight
5
+ module Instrumentation
6
+ class Instrument
7
+ MethodCall = Struct.new(:call_number, :backtrace, :file, :line, :object, :context, :kind, :method, :thread)
8
+ class Timing
9
+ def initialize(request_start, start, finish)
10
+ @request_start, @start, @finish = request_start, start, finish
11
+ end
12
+
13
+ attr_reader :request_start, :start, :finish
14
+
15
+ def duration
16
+ @duration ||= ((@finish - @start) * 1000).to_i
17
+ end
18
+
19
+ def delta_t
20
+ @delta_t ||= ((@start - @request_start) * 1000).to_i
21
+ end
22
+ end
23
+
24
+ @@call_seq = 0
25
+
26
+ def self.seq_number
27
+ Thread.exclusive do
28
+ return @@call_seq += 1
29
+ end
30
+ end
31
+
32
+ def initialize()
33
+ @start = Time.now
34
+ @collectors = nil
35
+ end
36
+
37
+ include Backstage
38
+
39
+ include Logging
40
+
41
+ def run(object, context="::", kind=:instance, called_at = caller[0], method = "<unknown>", args=[], &blk)
42
+ file, line, rest = called_at.split(':')
43
+ call_number = backstage{ self.class.seq_number }
44
+ method_call = backstage{ MethodCall.new(call_number, caller(1), file, line, object, context, kind, method, Thread::current) }
45
+ #$stderr.puts [method_call.context, method_call.method].inspect
46
+ start_time = Time.now
47
+ backstage do
48
+ start_event(method_call, args)
49
+ end
50
+ result = blk.call # execute the provided code block
51
+ backstage do
52
+ finish_event(method_call, args, start_time, result)
53
+ end
54
+ end
55
+
56
+ def collectors_for(method_call)
57
+ probe_chain = if method_call.kind == :instance
58
+ InstanceProbe.get_probe_chain(method_call.context)
59
+ else
60
+ ClassProbe.get_probe_chain(method_call.context)
61
+ end
62
+ collectors = probe_chain.inject([]) do |list, probe|
63
+ probe.collectors(method_call.method)
64
+ end
65
+ logger.debug do
66
+ "Probe chain for: #{method_call.context} #{method_call.kind} #{method_call.method}:\n #{collectors.map{|col| col.class.name}.join(", ")}"
67
+ end
68
+ collectors
69
+ end
70
+
71
+ def start_event(method_call, arguments)
72
+ logger.debug{ "Starting event: #{method_call.context} #{method_call.kind} #{method_call.method}" }
73
+
74
+ collectors_for(method_call).each do |collector|
75
+ collector.before_detect(method_call, arguments)
76
+ end
77
+ end
78
+
79
+ def finish_event(method_call, arguments, start_time, result)
80
+ timing = Timing.new(@start, start_time, Time.now)
81
+ logger.debug{ "Finishing event: #{method_call.context} #{method_call.kind} #{method_call.method}" }
82
+ collectors_for(method_call).each do |collector|
83
+ collector.after_detect(method_call, timing, arguments, result)
84
+ end
85
+ end
86
+
87
+ def all_collectors
88
+ PackageDefinition.all_collectors
89
+ end
90
+
91
+ def start(env)
92
+ all_collectors.each do |collector|
93
+ collector.request_start(env, @start)
94
+ end
95
+ end
96
+
97
+ def finish(env, status, headers, body)
98
+ @timing = Timing.new(@start, @start, Time.now)
99
+ all_collectors.each do |collector|
100
+ collector.request_finish(env, status, headers, body, @timing)
101
+ end
102
+ end
103
+
104
+ def duration
105
+ @timing.duration
106
+ end
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,58 @@
1
+ module Insight::Instrumentation
2
+ class PackageDefinition
3
+ class << self
4
+ def start
5
+ @started = begin
6
+ probes.each do |probe|
7
+ probe.fulfill_probe_orders
8
+ end
9
+ true
10
+ end
11
+ end
12
+
13
+ def probes
14
+ InstanceProbe.all_probes + ClassProbe.all_probes
15
+ end
16
+
17
+ def clear_collectors
18
+ all_collectors.clear
19
+ end
20
+
21
+ def all_collectors
22
+ @all_collectors ||= []
23
+ end
24
+
25
+ def add_collector(collector)
26
+ unless all_collectors.include?(collector)
27
+ all_collectors << collector
28
+ end
29
+ end
30
+
31
+ def probe(collector, &block)
32
+ add_collector(collector)
33
+ definer = self.new(collector)
34
+ definer.instance_eval &block
35
+ end
36
+ end
37
+
38
+ def get_class_probe(name)
39
+ ClassProbe.probe_for(name)
40
+ end
41
+
42
+ def get_instance_probe(name)
43
+ InstanceProbe.probe_for(name)
44
+ end
45
+
46
+ def initialize(collector)
47
+ @collector = collector
48
+ end
49
+
50
+ attr_reader :collector
51
+
52
+ def instrument(name, &block)
53
+ definer = ProbeDefinition.new(self, name)
54
+ definer.instance_eval(&block) unless block.nil?
55
+ return definer
56
+ end
57
+ end
58
+ end