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.
Files changed (126) hide show
  1. data/.gitignore +13 -0
  2. data/.rspec +1 -0
  3. data/.simplecov +4 -0
  4. data/.travis.yml +8 -0
  5. data/CHANGELOG +58 -0
  6. data/Gemfile +3 -0
  7. data/Gemfile.lock +82 -0
  8. data/LICENSE +24 -0
  9. data/README.md +189 -0
  10. data/Rakefile +27 -0
  11. data/TODO +7 -0
  12. data/lib/rack-insight.rb +1 -0
  13. data/lib/rack/insight.rb +19 -0
  14. data/lib/rack/insight/app.rb +198 -0
  15. data/lib/rack/insight/config.rb +30 -0
  16. data/lib/rack/insight/database.rb +193 -0
  17. data/lib/rack/insight/enable-button.rb +43 -0
  18. data/lib/rack/insight/filtered_backtrace.rb +45 -0
  19. data/lib/rack/insight/instrumentation.rb +9 -0
  20. data/lib/rack/insight/instrumentation/backstage.rb +10 -0
  21. data/lib/rack/insight/instrumentation/client.rb +20 -0
  22. data/lib/rack/insight/instrumentation/instrument.rb +109 -0
  23. data/lib/rack/insight/instrumentation/package-definition.rb +58 -0
  24. data/lib/rack/insight/instrumentation/probe-definition.rb +20 -0
  25. data/lib/rack/insight/instrumentation/probe.rb +196 -0
  26. data/lib/rack/insight/instrumentation/setup.rb +32 -0
  27. data/lib/rack/insight/logger.rb +53 -0
  28. data/lib/rack/insight/options.rb +116 -0
  29. data/lib/rack/insight/panel.rb +135 -0
  30. data/lib/rack/insight/panel_app.rb +31 -0
  31. data/lib/rack/insight/panels-content.rb +22 -0
  32. data/lib/rack/insight/panels-header.rb +18 -0
  33. data/lib/rack/insight/panels/active_record_panel.rb +46 -0
  34. data/lib/rack/insight/panels/active_resource_panel.rb +48 -0
  35. data/lib/rack/insight/panels/active_resource_panel/query.rb +27 -0
  36. data/lib/rack/insight/panels/cache_panel.rb +68 -0
  37. data/lib/rack/insight/panels/cache_panel/panel_app.rb +46 -0
  38. data/lib/rack/insight/panels/cache_panel/stats.rb +90 -0
  39. data/lib/rack/insight/panels/log_panel.rb +53 -0
  40. data/lib/rack/insight/panels/memory_panel.rb +36 -0
  41. data/lib/rack/insight/panels/mongo_panel.rb +41 -0
  42. data/lib/rack/insight/panels/mongo_panel/mongo_extension.rb +24 -0
  43. data/lib/rack/insight/panels/mongo_panel/stats.rb +46 -0
  44. data/lib/rack/insight/panels/rails_info_panel.rb +19 -0
  45. data/lib/rack/insight/panels/redis_panel.rb +42 -0
  46. data/lib/rack/insight/panels/redis_panel/redis_extension.rb +23 -0
  47. data/lib/rack/insight/panels/redis_panel/stats.rb +50 -0
  48. data/lib/rack/insight/panels/request_variables_panel.rb +70 -0
  49. data/lib/rack/insight/panels/speedtracer_panel.rb +89 -0
  50. data/lib/rack/insight/panels/speedtracer_panel/profiling.rb +29 -0
  51. data/lib/rack/insight/panels/speedtracer_panel/trace-app.rb +52 -0
  52. data/lib/rack/insight/panels/speedtracer_panel/tracer.rb +213 -0
  53. data/lib/rack/insight/panels/sphinx_panel.rb +41 -0
  54. data/lib/rack/insight/panels/sphinx_panel/stats.rb +94 -0
  55. data/lib/rack/insight/panels/sql_panel.rb +53 -0
  56. data/lib/rack/insight/panels/sql_panel/panel_app.rb +37 -0
  57. data/lib/rack/insight/panels/sql_panel/query.rb +94 -0
  58. data/lib/rack/insight/panels/templates_panel.rb +58 -0
  59. data/lib/rack/insight/panels/templates_panel/rendering.rb +81 -0
  60. data/lib/rack/insight/panels/timer_panel.rb +40 -0
  61. data/lib/rack/insight/params_signature.rb +61 -0
  62. data/lib/rack/insight/path-filter.rb +23 -0
  63. data/lib/rack/insight/public/__insight__/bookmarklet.html +10 -0
  64. data/lib/rack/insight/public/__insight__/bookmarklet.js +223 -0
  65. data/lib/rack/insight/public/__insight__/insight.css +235 -0
  66. data/lib/rack/insight/public/__insight__/insight.js +127 -0
  67. data/lib/rack/insight/public/__insight__/jquery-1.3.2.js +4376 -0
  68. data/lib/rack/insight/public/__insight__/jquery.tablesorter.min.js +1 -0
  69. data/lib/rack/insight/public/__insight__/spinner.gif +0 -0
  70. data/lib/rack/insight/rack_static_bug_avoider.rb +16 -0
  71. data/lib/rack/insight/redirect_interceptor.rb +25 -0
  72. data/lib/rack/insight/render.rb +72 -0
  73. data/lib/rack/insight/request-recorder.rb +22 -0
  74. data/lib/rack/insight/rspec_matchers.rb +33 -0
  75. data/lib/rack/insight/toolbar.rb +69 -0
  76. data/lib/rack/insight/version.rb +7 -0
  77. data/lib/rack/insight/views/enable-button.html.erb +21 -0
  78. data/lib/rack/insight/views/error.html.erb +17 -0
  79. data/lib/rack/insight/views/headers_fragment.html.erb +20 -0
  80. data/lib/rack/insight/views/panels/active_record.html.erb +17 -0
  81. data/lib/rack/insight/views/panels/active_resource.html.erb +47 -0
  82. data/lib/rack/insight/views/panels/cache.html.erb +93 -0
  83. data/lib/rack/insight/views/panels/execute_sql.html.erb +32 -0
  84. data/lib/rack/insight/views/panels/explain_sql.html.erb +32 -0
  85. data/lib/rack/insight/views/panels/log.html.erb +21 -0
  86. data/lib/rack/insight/views/panels/mongo.html.erb +32 -0
  87. data/lib/rack/insight/views/panels/profile_sql.html.erb +32 -0
  88. data/lib/rack/insight/views/panels/rails_info.html.erb +19 -0
  89. data/lib/rack/insight/views/panels/redis.html.erb +46 -0
  90. data/lib/rack/insight/views/panels/request_variables.html.erb +25 -0
  91. data/lib/rack/insight/views/panels/speedtracer/serverevent.html.erb +10 -0
  92. data/lib/rack/insight/views/panels/speedtracer/servertrace.html.erb +12 -0
  93. data/lib/rack/insight/views/panels/speedtracer/traces.html.erb +18 -0
  94. data/lib/rack/insight/views/panels/sphinx.html.erb +32 -0
  95. data/lib/rack/insight/views/panels/sql.html.erb +43 -0
  96. data/lib/rack/insight/views/panels/templates.html.erb +6 -0
  97. data/lib/rack/insight/views/panels/timer.html.erb +19 -0
  98. data/lib/rack/insight/views/panels/view_cache.html.erb +19 -0
  99. data/lib/rack/insight/views/redirect.html.erb +16 -0
  100. data/lib/rack/insight/views/request_fragment.html.erb +25 -0
  101. data/lib/rack/insight/views/toolbar.html.erb +29 -0
  102. data/rack-insight.gemspec +40 -0
  103. data/spec/custom_matchers.rb +0 -0
  104. data/spec/fixtures/config.ru +8 -0
  105. data/spec/fixtures/dummy_panel.rb +2 -0
  106. data/spec/fixtures/sample_app.rb +72 -0
  107. data/spec/fixtures/star_trek_panel.rb +1 -0
  108. data/spec/insight_spec.rb +163 -0
  109. data/spec/instrumentation_spec.rb +188 -0
  110. data/spec/rack/insight/config_spec.rb +20 -0
  111. data/spec/rack/insight/panels/active_record_panel_spec.rb +43 -0
  112. data/spec/rack/insight/panels/active_resource_panel_spec.rb +40 -0
  113. data/spec/rack/insight/panels/cache_panel_spec.rb +178 -0
  114. data/spec/rack/insight/panels/log_panel_spec.rb +44 -0
  115. data/spec/rack/insight/panels/memory_panel_spec.rb +21 -0
  116. data/spec/rack/insight/panels/mongo_panel_spec_pending.rb +52 -0
  117. data/spec/rack/insight/panels/rails_info_panel_spec.rb +29 -0
  118. data/spec/rack/insight/panels/redis_panel_spec.rb +67 -0
  119. data/spec/rack/insight/panels/speedtracer_panel_spec.rb +86 -0
  120. data/spec/rack/insight/panels/sql_panel_spec.rb +146 -0
  121. data/spec/rack/insight/panels/templates_panel_spec.rb +86 -0
  122. data/spec/rack/insight/panels/timer_panel_spec.rb +38 -0
  123. data/spec/rcov.opts +1 -0
  124. data/spec/spec.opts +1 -0
  125. data/spec/spec_helper.rb +111 -0
  126. 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