logical-insight 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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