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.
- data/.gitignore +13 -0
- data/.rspec +1 -0
- data/.simplecov +4 -0
- data/.travis.yml +8 -0
- data/CHANGELOG +58 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +82 -0
- data/LICENSE +24 -0
- data/README.md +189 -0
- data/Rakefile +27 -0
- data/TODO +7 -0
- data/lib/rack-insight.rb +1 -0
- data/lib/rack/insight.rb +19 -0
- data/lib/rack/insight/app.rb +198 -0
- data/lib/rack/insight/config.rb +30 -0
- data/lib/rack/insight/database.rb +193 -0
- data/lib/rack/insight/enable-button.rb +43 -0
- data/lib/rack/insight/filtered_backtrace.rb +45 -0
- data/lib/rack/insight/instrumentation.rb +9 -0
- data/lib/rack/insight/instrumentation/backstage.rb +10 -0
- data/lib/rack/insight/instrumentation/client.rb +20 -0
- data/lib/rack/insight/instrumentation/instrument.rb +109 -0
- data/lib/rack/insight/instrumentation/package-definition.rb +58 -0
- data/lib/rack/insight/instrumentation/probe-definition.rb +20 -0
- data/lib/rack/insight/instrumentation/probe.rb +196 -0
- data/lib/rack/insight/instrumentation/setup.rb +32 -0
- data/lib/rack/insight/logger.rb +53 -0
- data/lib/rack/insight/options.rb +116 -0
- data/lib/rack/insight/panel.rb +135 -0
- data/lib/rack/insight/panel_app.rb +31 -0
- data/lib/rack/insight/panels-content.rb +22 -0
- data/lib/rack/insight/panels-header.rb +18 -0
- data/lib/rack/insight/panels/active_record_panel.rb +46 -0
- data/lib/rack/insight/panels/active_resource_panel.rb +48 -0
- data/lib/rack/insight/panels/active_resource_panel/query.rb +27 -0
- data/lib/rack/insight/panels/cache_panel.rb +68 -0
- data/lib/rack/insight/panels/cache_panel/panel_app.rb +46 -0
- data/lib/rack/insight/panels/cache_panel/stats.rb +90 -0
- data/lib/rack/insight/panels/log_panel.rb +53 -0
- data/lib/rack/insight/panels/memory_panel.rb +36 -0
- data/lib/rack/insight/panels/mongo_panel.rb +41 -0
- data/lib/rack/insight/panels/mongo_panel/mongo_extension.rb +24 -0
- data/lib/rack/insight/panels/mongo_panel/stats.rb +46 -0
- data/lib/rack/insight/panels/rails_info_panel.rb +19 -0
- data/lib/rack/insight/panels/redis_panel.rb +42 -0
- data/lib/rack/insight/panels/redis_panel/redis_extension.rb +23 -0
- data/lib/rack/insight/panels/redis_panel/stats.rb +50 -0
- data/lib/rack/insight/panels/request_variables_panel.rb +70 -0
- data/lib/rack/insight/panels/speedtracer_panel.rb +89 -0
- data/lib/rack/insight/panels/speedtracer_panel/profiling.rb +29 -0
- data/lib/rack/insight/panels/speedtracer_panel/trace-app.rb +52 -0
- data/lib/rack/insight/panels/speedtracer_panel/tracer.rb +213 -0
- data/lib/rack/insight/panels/sphinx_panel.rb +41 -0
- data/lib/rack/insight/panels/sphinx_panel/stats.rb +94 -0
- data/lib/rack/insight/panels/sql_panel.rb +53 -0
- data/lib/rack/insight/panels/sql_panel/panel_app.rb +37 -0
- data/lib/rack/insight/panels/sql_panel/query.rb +94 -0
- data/lib/rack/insight/panels/templates_panel.rb +58 -0
- data/lib/rack/insight/panels/templates_panel/rendering.rb +81 -0
- data/lib/rack/insight/panels/timer_panel.rb +40 -0
- data/lib/rack/insight/params_signature.rb +61 -0
- data/lib/rack/insight/path-filter.rb +23 -0
- data/lib/rack/insight/public/__insight__/bookmarklet.html +10 -0
- data/lib/rack/insight/public/__insight__/bookmarklet.js +223 -0
- data/lib/rack/insight/public/__insight__/insight.css +235 -0
- data/lib/rack/insight/public/__insight__/insight.js +127 -0
- data/lib/rack/insight/public/__insight__/jquery-1.3.2.js +4376 -0
- data/lib/rack/insight/public/__insight__/jquery.tablesorter.min.js +1 -0
- data/lib/rack/insight/public/__insight__/spinner.gif +0 -0
- data/lib/rack/insight/rack_static_bug_avoider.rb +16 -0
- data/lib/rack/insight/redirect_interceptor.rb +25 -0
- data/lib/rack/insight/render.rb +72 -0
- data/lib/rack/insight/request-recorder.rb +22 -0
- data/lib/rack/insight/rspec_matchers.rb +33 -0
- data/lib/rack/insight/toolbar.rb +69 -0
- data/lib/rack/insight/version.rb +7 -0
- data/lib/rack/insight/views/enable-button.html.erb +21 -0
- data/lib/rack/insight/views/error.html.erb +17 -0
- data/lib/rack/insight/views/headers_fragment.html.erb +20 -0
- data/lib/rack/insight/views/panels/active_record.html.erb +17 -0
- data/lib/rack/insight/views/panels/active_resource.html.erb +47 -0
- data/lib/rack/insight/views/panels/cache.html.erb +93 -0
- data/lib/rack/insight/views/panels/execute_sql.html.erb +32 -0
- data/lib/rack/insight/views/panels/explain_sql.html.erb +32 -0
- data/lib/rack/insight/views/panels/log.html.erb +21 -0
- data/lib/rack/insight/views/panels/mongo.html.erb +32 -0
- data/lib/rack/insight/views/panels/profile_sql.html.erb +32 -0
- data/lib/rack/insight/views/panels/rails_info.html.erb +19 -0
- data/lib/rack/insight/views/panels/redis.html.erb +46 -0
- data/lib/rack/insight/views/panels/request_variables.html.erb +25 -0
- data/lib/rack/insight/views/panels/speedtracer/serverevent.html.erb +10 -0
- data/lib/rack/insight/views/panels/speedtracer/servertrace.html.erb +12 -0
- data/lib/rack/insight/views/panels/speedtracer/traces.html.erb +18 -0
- data/lib/rack/insight/views/panels/sphinx.html.erb +32 -0
- data/lib/rack/insight/views/panels/sql.html.erb +43 -0
- data/lib/rack/insight/views/panels/templates.html.erb +6 -0
- data/lib/rack/insight/views/panels/timer.html.erb +19 -0
- data/lib/rack/insight/views/panels/view_cache.html.erb +19 -0
- data/lib/rack/insight/views/redirect.html.erb +16 -0
- data/lib/rack/insight/views/request_fragment.html.erb +25 -0
- data/lib/rack/insight/views/toolbar.html.erb +29 -0
- data/rack-insight.gemspec +40 -0
- data/spec/custom_matchers.rb +0 -0
- data/spec/fixtures/config.ru +8 -0
- data/spec/fixtures/dummy_panel.rb +2 -0
- data/spec/fixtures/sample_app.rb +72 -0
- data/spec/fixtures/star_trek_panel.rb +1 -0
- data/spec/insight_spec.rb +163 -0
- data/spec/instrumentation_spec.rb +188 -0
- data/spec/rack/insight/config_spec.rb +20 -0
- data/spec/rack/insight/panels/active_record_panel_spec.rb +43 -0
- data/spec/rack/insight/panels/active_resource_panel_spec.rb +40 -0
- data/spec/rack/insight/panels/cache_panel_spec.rb +178 -0
- data/spec/rack/insight/panels/log_panel_spec.rb +44 -0
- data/spec/rack/insight/panels/memory_panel_spec.rb +21 -0
- data/spec/rack/insight/panels/mongo_panel_spec_pending.rb +52 -0
- data/spec/rack/insight/panels/rails_info_panel_spec.rb +29 -0
- data/spec/rack/insight/panels/redis_panel_spec.rb +67 -0
- data/spec/rack/insight/panels/speedtracer_panel_spec.rb +86 -0
- data/spec/rack/insight/panels/sql_panel_spec.rb +146 -0
- data/spec/rack/insight/panels/templates_panel_spec.rb +86 -0
- data/spec/rack/insight/panels/timer_panel_spec.rb +38 -0
- data/spec/rcov.opts +1 -0
- data/spec/spec.opts +1 -0
- data/spec/spec_helper.rb +111 -0
- metadata +380 -0
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Rack::Insight
|
|
2
|
+
class MongoPanel
|
|
3
|
+
|
|
4
|
+
class Stats
|
|
5
|
+
class Query
|
|
6
|
+
attr_reader :time
|
|
7
|
+
attr_reader :command
|
|
8
|
+
|
|
9
|
+
def initialize(time, command)
|
|
10
|
+
@time = time
|
|
11
|
+
@command = command
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def display_time
|
|
15
|
+
"%.2fms" % time
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
attr_reader :calls
|
|
20
|
+
attr_reader :queries
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@queries = []
|
|
24
|
+
@calls = 0
|
|
25
|
+
@time = 0.0
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def record_call(time, command)
|
|
29
|
+
@queries << Query.new(time, command)
|
|
30
|
+
@calls += 1
|
|
31
|
+
@time += time
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def display_time
|
|
35
|
+
"%.2fms" % time
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def time
|
|
39
|
+
@queries.inject(0) do |memo, query|
|
|
40
|
+
memo + query.time
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Rack::Insight
|
|
2
|
+
class RailsInfoPanel < Panel
|
|
3
|
+
|
|
4
|
+
def name
|
|
5
|
+
"rails_info"
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def heading
|
|
9
|
+
return unless (defined?(Rails) && defined?(Rails::Info))
|
|
10
|
+
"Rails #{Rails.version}"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def content
|
|
14
|
+
return unless (defined?(Rails) && defined?(Rails::Info))
|
|
15
|
+
render_template "panels/rails_info"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
module Rack::Insight
|
|
2
|
+
|
|
3
|
+
class RedisPanel < Panel
|
|
4
|
+
require "rack/insight/panels/redis_panel/redis_extension"
|
|
5
|
+
|
|
6
|
+
require "rack/insight/panels/redis_panel/stats"
|
|
7
|
+
|
|
8
|
+
def self.record(redis_command_args, backtrace, &block)
|
|
9
|
+
return block.call unless Rack::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["rack-insight.redis"] = Stats.new
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.stats
|
|
23
|
+
Thread.current["rack-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
|
+
Rack::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 Rack::Insight
|
|
2
|
+
class RedisPanel
|
|
3
|
+
|
|
4
|
+
class Stats
|
|
5
|
+
class Query
|
|
6
|
+
include Rack::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 Rack::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("rack-insight") == 0 or k.index("rack-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 'uuidtools'
|
|
7
|
+
require 'rack/insight/panels/speedtracer_panel/trace-app'
|
|
8
|
+
require 'rack/insight/panels/speedtracer_panel/tracer'
|
|
9
|
+
|
|
10
|
+
module Rack::Insight
|
|
11
|
+
module SpeedTracer
|
|
12
|
+
class Panel < ::Rack::Insight::Panel
|
|
13
|
+
|
|
14
|
+
def initialize(app)
|
|
15
|
+
@app = app
|
|
16
|
+
@uuid = UUIDTools::UUID.random_create.to_s
|
|
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['rack-insight.speedtracer-id'] = @uuid
|
|
58
|
+
|
|
59
|
+
status, headers, body = @app.call(env)
|
|
60
|
+
|
|
61
|
+
store(env, env['rack-insight.speedtracer-id'], env['rack-insight.speedtracer-record'])
|
|
62
|
+
headers['X-TraceUrl'] = '__insight__/speedtracer?id=' + env['rack-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,29 @@
|
|
|
1
|
+
module Rack::Insight
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
#Variant of the Speed Tracer Panel that performs a nearly complete profile of
|
|
5
|
+
#all code called during a request. Note that this will slow overall response
|
|
6
|
+
#time by several orders of magnitude, and may return more data than
|
|
7
|
+
#SpeedTracer is prepared to display
|
|
8
|
+
class ProfilingSpeedTracer < SpeedTracer
|
|
9
|
+
def before(env)
|
|
10
|
+
super
|
|
11
|
+
tracer = env['st.tracer']
|
|
12
|
+
Kernel::set_trace_func proc {|event, file, line, name, binding,classname|
|
|
13
|
+
case event
|
|
14
|
+
when "c-call", "call"
|
|
15
|
+
methodname = classname ? "" : classname
|
|
16
|
+
methodname += name.to_s
|
|
17
|
+
tracer.start_event(file, line, name, classname || "", "")
|
|
18
|
+
when "c-return", "return"
|
|
19
|
+
tracer.finish_event
|
|
20
|
+
end
|
|
21
|
+
}
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def after(env, status, headers, body)
|
|
25
|
+
Kernel::set_trace_func(nil)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module Rack::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,213 @@
|
|
|
1
|
+
module Rack::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("rack-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["rack-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
|
+
arguments_string = ""
|
|
24
|
+
#XXX ServerEvent use method call...
|
|
25
|
+
event = ServerEvent.new(method_call, arguments_string)
|
|
26
|
+
@pstack.push event
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def after_detect(method_call, timing, arguments, result)
|
|
30
|
+
event = @pstack.pop
|
|
31
|
+
if event.nil?
|
|
32
|
+
else
|
|
33
|
+
event.finish
|
|
34
|
+
|
|
35
|
+
unless (parent = @pstack.last).nil?
|
|
36
|
+
parent.children.push event
|
|
37
|
+
else
|
|
38
|
+
@children.push event
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def make_string_of(array)
|
|
44
|
+
array.map do |item|
|
|
45
|
+
short_string(item)
|
|
46
|
+
end.join(",")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def short_string(item, max_per_elem = 50)
|
|
50
|
+
begin
|
|
51
|
+
string = item.inspect
|
|
52
|
+
if string.length > max_per_elem
|
|
53
|
+
case item
|
|
54
|
+
when NilClass
|
|
55
|
+
"nil"
|
|
56
|
+
when Hash
|
|
57
|
+
"{ " + item.map do |key, value|
|
|
58
|
+
short_string(key, 15) + "=>" + short_string(value, 30)
|
|
59
|
+
end.join(", ") + " }"
|
|
60
|
+
when find_constant("ActionView::Base")
|
|
61
|
+
tmpl = item.template
|
|
62
|
+
if tmpl.nil?
|
|
63
|
+
item.path.inspect
|
|
64
|
+
else
|
|
65
|
+
[tmpl.base_path, tmpl.name].join("/")
|
|
66
|
+
end
|
|
67
|
+
when find_constant("ActiveRecord::Base")
|
|
68
|
+
string = "#{item.class.name}(#{item.id})"
|
|
69
|
+
else
|
|
70
|
+
string = item.class.name
|
|
71
|
+
end
|
|
72
|
+
else
|
|
73
|
+
string
|
|
74
|
+
end
|
|
75
|
+
rescue Exception => ex
|
|
76
|
+
"..."
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
class TraceRecord
|
|
83
|
+
include Render
|
|
84
|
+
def initialize
|
|
85
|
+
@start = Time.now
|
|
86
|
+
@children = []
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
attr_accessor :children
|
|
90
|
+
attr_reader :start
|
|
91
|
+
|
|
92
|
+
def finish
|
|
93
|
+
@finish ||= Time.now
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def time_in_children
|
|
97
|
+
@children.inject(0) do |time, child|
|
|
98
|
+
time + child.duration
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def duration
|
|
103
|
+
((@finish - @start) * 1000).to_i
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def to_json
|
|
107
|
+
Yajl::Encoder.encode(hash_representation, :pretty => true, :indent => ' ')
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
private
|
|
111
|
+
# all timestamps in SpeedTracer are in milliseconds
|
|
112
|
+
def range(start, finish)
|
|
113
|
+
{
|
|
114
|
+
'duration' => ((finish - start) * 1000).to_i,
|
|
115
|
+
'start' => [start.to_i, start.usec/1000].join(''),
|
|
116
|
+
#'end' => [finish.to_i, finish.usec/1000].join('')
|
|
117
|
+
}
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def symbolize_hash(hash)
|
|
121
|
+
symbolled_hash = {}
|
|
122
|
+
hash.each_key do |key|
|
|
123
|
+
if String === key
|
|
124
|
+
next if hash.has_key?(key.to_sym)
|
|
125
|
+
symbolled_hash[key.to_sym] = hash[key]
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
hash.merge!(symbolled_hash)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class ServerEvent < TraceRecord
|
|
133
|
+
attr_reader :name
|
|
134
|
+
|
|
135
|
+
def initialize(method_call, arguments)
|
|
136
|
+
super()
|
|
137
|
+
@arguments = arguments
|
|
138
|
+
@name = "#{method_call.context}#{method_call.kind == :instance ? "#" : "::"}#{method_call.method}(#{arguments})"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def hash_representation
|
|
142
|
+
{
|
|
143
|
+
'range' => range(@start, @finish),
|
|
144
|
+
'operation' => {
|
|
145
|
+
# 'sourceCodeLocation' => {
|
|
146
|
+
# 'className' => @file,
|
|
147
|
+
# 'methodName' => @method,
|
|
148
|
+
# 'lineNumber' => @line
|
|
149
|
+
# },
|
|
150
|
+
'type' => 'METHOD',
|
|
151
|
+
'label' => @name
|
|
152
|
+
},
|
|
153
|
+
'children' => @children
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def to_html
|
|
158
|
+
render_template('panels/speedtracer/serverevent',
|
|
159
|
+
{:self_time => duration - time_in_children}.merge(symbolize_hash(hash_representation)))
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class RequestRecord < TraceRecord
|
|
165
|
+
def initialize(id, method, uri)
|
|
166
|
+
super()
|
|
167
|
+
|
|
168
|
+
@id = id
|
|
169
|
+
@method = method
|
|
170
|
+
@uri = uri
|
|
171
|
+
@event_id = 0
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
def uuid
|
|
175
|
+
@id
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def hash_representation
|
|
179
|
+
finish
|
|
180
|
+
{ 'trace' => {
|
|
181
|
+
|
|
182
|
+
'url' => "/__insight__/speedtracer?id=#@id",
|
|
183
|
+
|
|
184
|
+
'frameStack' => {
|
|
185
|
+
|
|
186
|
+
'range' => range(@start, @finish),
|
|
187
|
+
'operation' => {
|
|
188
|
+
'type' => 'HTTP',
|
|
189
|
+
'label' => [@method, @uri].join(' ')
|
|
190
|
+
},
|
|
191
|
+
'children' => @children
|
|
192
|
+
|
|
193
|
+
}, #end frameStack
|
|
194
|
+
|
|
195
|
+
'resources' => {
|
|
196
|
+
'Application' => '/', #Should get the Rails app name...
|
|
197
|
+
'Application.endpoint' => '/' #Should get the env path thing
|
|
198
|
+
}, #From what I can tell, Speed Tracer treats this whole hash as optional
|
|
199
|
+
|
|
200
|
+
'range' => range(@start, @finish)
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def to_html
|
|
206
|
+
hash = hash_representation
|
|
207
|
+
extra = {:self_time => duration - time_in_children}
|
|
208
|
+
"<a href='#{hash['trace']['url']}'>Raw JSON</a>\n" +
|
|
209
|
+
render_template('panels/speedtracer/serverevent', extra.merge(symbolize_hash(hash['trace']['frameStack'])))
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|