newrelic_rpm 2.8.11 → 2.9.2

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of newrelic_rpm might be problematic. Click here for more details.

Files changed (137) hide show
  1. data/CHANGELOG +267 -0
  2. data/LICENSE +1 -1
  3. data/Manifest +142 -0
  4. data/README.md +138 -0
  5. data/Rakefile +10 -28
  6. data/bin/mongrel_rpm +33 -0
  7. data/cert/cacert.pem +34 -0
  8. data/init.rb +38 -0
  9. data/lib/new_relic/agent/agent.rb +160 -347
  10. data/lib/new_relic/agent/collection_helper.rb +13 -24
  11. data/lib/new_relic/agent/error_collector.rb +29 -15
  12. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +63 -76
  13. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +90 -48
  14. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +72 -47
  15. data/lib/new_relic/agent/instrumentation/error_instrumentation.rb +14 -0
  16. data/lib/new_relic/agent/instrumentation/merb/controller.rb +10 -1
  17. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +5 -7
  18. data/lib/new_relic/agent/instrumentation/merb/errors.rb +3 -1
  19. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +7 -0
  20. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +34 -7
  21. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +20 -12
  22. data/lib/new_relic/agent/instrumentation/rails/errors.rb +5 -4
  23. data/lib/new_relic/agent/method_tracer.rb +159 -135
  24. data/lib/new_relic/agent/patch_const_missing.rb +46 -26
  25. data/lib/new_relic/agent/sampler.rb +12 -0
  26. data/lib/new_relic/agent/samplers/cpu_sampler.rb +44 -0
  27. data/lib/new_relic/agent/samplers/memory_sampler.rb +126 -0
  28. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  29. data/lib/new_relic/agent/shim_agent.rb +11 -0
  30. data/lib/new_relic/agent/stats_engine.rb +85 -46
  31. data/lib/new_relic/agent/transaction_sampler.rb +63 -38
  32. data/lib/new_relic/agent/worker_loop.rb +8 -18
  33. data/lib/new_relic/agent.rb +200 -25
  34. data/lib/new_relic/commands/deployments.rb +9 -9
  35. data/lib/new_relic/control/merb.rb +22 -0
  36. data/lib/new_relic/control/rails.rb +141 -0
  37. data/lib/new_relic/{config → control}/ruby.rb +13 -2
  38. data/lib/new_relic/control.rb +424 -0
  39. data/lib/new_relic/local_environment.rb +201 -79
  40. data/lib/new_relic/metric_data.rb +7 -0
  41. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  42. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  43. data/lib/new_relic/metric_parser/active_record.rb +11 -0
  44. data/lib/new_relic/metric_parser/controller.rb +51 -0
  45. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  46. data/lib/new_relic/metric_parser/database.rb +23 -0
  47. data/lib/new_relic/metric_parser/errors.rb +6 -0
  48. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  49. data/lib/new_relic/metric_parser/view.rb +61 -0
  50. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  51. data/lib/new_relic/metric_parser.rb +107 -0
  52. data/lib/new_relic/metric_spec.rb +5 -0
  53. data/lib/new_relic/noticed_error.rb +5 -1
  54. data/lib/new_relic/rack/metric_app.rb +57 -0
  55. data/lib/new_relic/rack/newrelic.ru +25 -0
  56. data/lib/new_relic/rack/newrelic.yml +25 -0
  57. data/lib/new_relic/rack.rb +5 -0
  58. data/lib/new_relic/recipes.rb +10 -3
  59. data/lib/new_relic/stats.rb +130 -144
  60. data/lib/new_relic/transaction_analysis.rb +7 -8
  61. data/lib/new_relic/transaction_sample.rb +86 -10
  62. data/lib/new_relic/version.rb +41 -160
  63. data/lib/new_relic_api.rb +7 -6
  64. data/lib/newrelic_rpm.rb +30 -17
  65. data/lib/tasks/{agent_tests.rake → tests.rake} +1 -1
  66. data/newrelic.yml +115 -62
  67. data/newrelic_rpm.gemspec +36 -0
  68. data/test/active_record_fixtures.rb +55 -0
  69. data/test/config/newrelic.yml +21 -3
  70. data/test/config/{test_config.rb → test_control.rb} +14 -10
  71. data/test/new_relic/agent/active_record_instrumentation_test.rb +189 -0
  72. data/test/new_relic/agent/agent_test.rb +104 -0
  73. data/test/new_relic/agent/agent_test_controller.rb +18 -1
  74. data/test/new_relic/agent/classloader_patch_test.rb +56 -0
  75. data/test/new_relic/agent/{tc_collection_helper.rb → collection_helper_test.rb} +28 -23
  76. data/test/new_relic/agent/controller_test.rb +107 -0
  77. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +70 -0
  78. data/test/new_relic/agent/error_collector_test.rb +155 -0
  79. data/test/new_relic/agent/{tc_method_tracer.rb → method_tracer_test.rb} +6 -12
  80. data/test/new_relic/agent/metric_data_test.rb +56 -0
  81. data/test/new_relic/agent/stats_engine_test.rb +266 -0
  82. data/test/new_relic/agent/{tc_transaction_sample_builder.rb → transaction_sample_builder_test.rb} +6 -5
  83. data/test/new_relic/agent/{tc_transaction_sample.rb → transaction_sample_test.rb} +9 -13
  84. data/test/new_relic/agent/transaction_sampler_test.rb +317 -0
  85. data/test/new_relic/agent/{tc_worker_loop.rb → worker_loop_test.rb} +1 -1
  86. data/test/new_relic/control_test.rb +97 -0
  87. data/test/new_relic/{tc_deployments_api.rb → deployments_api_test.rb} +8 -4
  88. data/test/new_relic/environment_test.rb +75 -0
  89. data/test/new_relic/metric_parser_test.rb +142 -0
  90. data/test/new_relic/{tc_metric_spec.rb → metric_spec_test.rb} +28 -1
  91. data/test/new_relic/samplers_test.rb +71 -0
  92. data/test/new_relic/{tc_shim_agent.rb → shim_agent_test.rb} +1 -1
  93. data/test/new_relic/stats_test.rb +291 -0
  94. data/test/new_relic/version_number_test.rb +46 -0
  95. data/test/test_helper.rb +7 -30
  96. data/test/ui/newrelic_controller_test.rb +14 -0
  97. data/test/ui/{tc_newrelic_helper.rb → newrelic_helper_test.rb} +16 -7
  98. data/ui/controllers/newrelic_controller.rb +17 -3
  99. data/ui/helpers/newrelic_helper.rb +44 -15
  100. data/ui/views/layouts/newrelic_default.rhtml +7 -8
  101. data/ui/views/newrelic/_sample.rhtml +5 -2
  102. data/ui/views/newrelic/_segment.rhtml +1 -1
  103. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  104. data/ui/views/newrelic/_segment_row.rhtml +4 -4
  105. data/ui/views/newrelic/_show_sample_detail.rhtml +3 -1
  106. data/ui/views/newrelic/_show_sample_sql.rhtml +2 -1
  107. data/ui/views/newrelic/explain_sql.rhtml +2 -5
  108. data/ui/views/newrelic/images/file_icon.png +0 -0
  109. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  110. data/ui/views/newrelic/index.rhtml +21 -13
  111. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  112. data/ui/views/newrelic/show_sample.rhtml +18 -3
  113. data/ui/views/newrelic/stylesheets/style.css +39 -0
  114. data/ui/views/newrelic/threads.rhtml +52 -0
  115. metadata +192 -70
  116. data/README +0 -136
  117. data/lib/new_relic/agent/instrumentation/rails/rails.rb +0 -6
  118. data/lib/new_relic/agent/samplers/cpu.rb +0 -29
  119. data/lib/new_relic/agent/samplers/memory.rb +0 -53
  120. data/lib/new_relic/agent/samplers/mongrel.rb +0 -26
  121. data/lib/new_relic/agent/synchronize.rb +0 -40
  122. data/lib/new_relic/config/merb.rb +0 -35
  123. data/lib/new_relic/config/rails.rb +0 -114
  124. data/lib/new_relic/config.rb +0 -279
  125. data/lib/new_relic/shim_agent.rb +0 -96
  126. data/test/new_relic/agent/model_fixture.rb +0 -15
  127. data/test/new_relic/agent/tc_active_record.rb +0 -90
  128. data/test/new_relic/agent/tc_agent.rb +0 -148
  129. data/test/new_relic/agent/tc_controller.rb +0 -77
  130. data/test/new_relic/agent/tc_dispatcher_instrumentation.rb +0 -52
  131. data/test/new_relic/agent/tc_error_collector.rb +0 -127
  132. data/test/new_relic/agent/tc_stats_engine.rb +0 -218
  133. data/test/new_relic/agent/tc_synchronize.rb +0 -37
  134. data/test/new_relic/agent/tc_transaction_sampler.rb +0 -302
  135. data/test/new_relic/tc_config.rb +0 -36
  136. data/test/new_relic/tc_environment.rb +0 -94
  137. data/test/new_relic/tc_stats.rb +0 -141
@@ -1,24 +1,21 @@
1
1
  # We have to patch the mongrel dispatcher live since the classes
2
2
  # aren't defined when our instrumentation loads
3
+ # To use this module, you need to monkey patch a method newrelic_response_code
4
+ # which will return the response status code when the dispatcher finishes.
3
5
  module NewRelic::Agent::Instrumentation
4
6
  module DispatcherInstrumentation
5
7
 
6
- @@newrelic_agent = NewRelic::Agent.agent
7
- @@newrelic_rails_dispatch_stat = @@newrelic_agent.stats_engine.get_stats 'Rails/HTTP Dispatch'
8
- @@newrelic_mongrel_queue_stat = @@newrelic_agent.stats_engine.get_stats('WebFrontend/Mongrel/Average Queue Time')
9
-
10
8
  def newrelic_dispatcher_start
11
9
  # Put the current time on the thread. Can't put in @ivar because this could
12
10
  # be a class or instance context
13
- t0 = Time.now.to_f
14
- NewRelic::Config.instance.log.warn "Recursive entry into dispatcher_start!\n#{caller.join("\n ")}" if Thread.current[:newrelic_t0]
15
- Thread.current[:newrelic_t0] = t0
16
- NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_start t0
11
+ newrelic_dispatcher_start_time = Time.now.to_f
12
+ Thread.current[:newrelic_dispatcher_start] = newrelic_dispatcher_start_time
13
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_start newrelic_dispatcher_start_time
17
14
  # capture the time spent in the mongrel queue, if running in mongrel. This is the
18
15
  # current time less the timestamp placed in 'started_on' by mongrel.
19
16
  mongrel_start = Thread.current[:started_on]
20
- @@newrelic_mongrel_queue_stat.trace_call(t0 - mongrel_start.to_f) if mongrel_start
21
- @@newrelic_agent.start_transaction
17
+ mongrel_queue_stat.trace_call(newrelic_dispatcher_start_time - mongrel_start.to_f) if mongrel_start
18
+ NewRelic::Agent.agent.start_transaction
22
19
 
23
20
  # Reset the flag indicating the controller action should be ignored.
24
21
  # It may be set by the action to either true or false or left nil meaning false
@@ -26,18 +23,26 @@ module NewRelic::Agent::Instrumentation
26
23
  end
27
24
 
28
25
  def newrelic_dispatcher_finish
29
- t0 = Thread.current[:newrelic_t0]
30
- if t0.nil?
31
- NewRelic::Config.instance.log.warn "Dispatcher finish called twice!\n#{caller.join("\n ")}"
32
- return
26
+ #puts @env.to_a.map{|k,v| "#{'%32s' % k}: #{v.inspect[0..64]}"}.join("\n")
27
+ dispatcher_end_time = Time.now.to_f
28
+ NewRelic::Agent.agent.end_transaction
29
+ NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_finish dispatcher_end_time
30
+ unless Thread.current[:controller_ignored]
31
+ # Store the response header
32
+ newrelic_dispatcher_start_time = Thread.current[:newrelic_dispatcher_start]
33
+ response_code = newrelic_response_code
34
+ if response_code
35
+ stats = response_stats[response_code] ||= NewRelic::Agent.agent.stats_engine.get_stats("HTTP/Response/#{response_code}")
36
+ stats.trace_call(dispatcher_end_time - newrelic_dispatcher_start_time)
37
+ end
38
+ dispatch_stat.trace_call(dispatcher_end_time - newrelic_dispatcher_start_time)
33
39
  end
34
- t1 = Time.now.to_f
35
- @@newrelic_agent.end_transaction
36
- @@newrelic_rails_dispatch_stat.trace_call(t1 - t0) unless Thread.current[:controller_ignored]
37
- NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.dispatcher_finish t1
38
- Thread.current[:newrelic_t0] = nil
39
40
  end
40
-
41
+ def newrelic_response_code
42
+ raise "Must be implemented in the dispatcher class"
43
+ end
44
+ # Used only when no before/after callbacks are available with
45
+ # the dispatcher, such as Rails before 2.0
41
46
  def dispatch_newrelic(*args)
42
47
  newrelic_dispatcher_start
43
48
  begin
@@ -47,48 +52,62 @@ module NewRelic::Agent::Instrumentation
47
52
  end
48
53
  end
49
54
 
55
+ private
56
+ # memoize the stats to avoid the cost of the lookup each time.
57
+ def dispatch_stat
58
+ @@newrelic_rails_dispatch_stat ||= NewRelic::Agent.agent.stats_engine.get_stats 'Rails/HTTP Dispatch'
59
+ end
60
+ def mongrel_queue_stat
61
+ @@newrelic_mongrel_queue_stat ||= NewRelic::Agent.agent.stats_engine.get_stats('WebFrontend/Mongrel/Average Queue Time')
62
+ end
63
+ def response_stats
64
+ @@newrelic_response_stats ||= { '200' => NewRelic::Agent.agent.stats_engine.get_stats('HTTP/Response/200')}
65
+ end
66
+
50
67
  # This won't work with Rails 2.2 multi-threading
51
68
  module BusyCalculator
52
69
  extend self
53
70
  # the fraction of the sample period that the dispatcher was busy
54
- @instance_busy = NewRelic::Agent.agent.stats_engine.get_stats('Instance/Busy')
71
+
55
72
  @harvest_start = Time.now.to_f
56
73
  @accumulator = 0
57
- @dispatcher_start = nil
74
+ @entrypoint_stack = []
75
+ @lock = Mutex.new
76
+
58
77
  def dispatcher_start(time)
59
- Thread.critical = true
60
- @dispatcher_start = time
61
- Thread.critical = false
78
+ @lock.synchronize do
79
+ @entrypoint_stack.push time
80
+ end
62
81
  end
63
82
 
64
83
  def dispatcher_finish(time)
65
- Thread.critical = true
66
- @accumulator += (time - @dispatcher_start)
67
- @dispatcher_start = nil
68
-
69
- Thread.critical = false
84
+ @lock.synchronize do
85
+ NewRelic::Control.instance.log.error("Stack underflow tracking dispatcher entry and exit!\n #{caller.join(" \n")}") and return if @entrypoint_stack.empty?
86
+ @accumulator += (time - @entrypoint_stack.pop)
87
+ end
70
88
  end
71
89
 
72
- def is_busy?
73
- @dispatcher_start
90
+ def busy_count
91
+ @entrypoint_stack.size
74
92
  end
75
-
93
+
94
+ # Called before uploading to to the server to collect current busy stats.
76
95
  def harvest_busy
77
- Thread.critical = true
78
-
79
- busy = @accumulator
80
- @accumulator = 0
81
-
96
+ busy = 0
82
97
  t0 = Time.now.to_f
83
-
84
- if @dispatcher_start
85
- busy += (t0 - @dispatcher_start)
86
- @dispatcher_start = t0
98
+ @lock.synchronize do
99
+ busy = @accumulator
100
+ @accumulator = 0
101
+
102
+ # Walk through the stack and capture all times up to
103
+ # now for entrypoints
104
+ @entrypoint_stack.size.times do |frame|
105
+ busy += (t0 - @entrypoint_stack[frame])
106
+ @entrypoint_stack[frame] = t0
107
+ end
108
+
87
109
  end
88
110
 
89
-
90
- Thread.critical = false
91
-
92
111
  busy = 0.0 if busy < 0.0 # don't go below 0%
93
112
 
94
113
  time_window = (t0 - @harvest_start)
@@ -96,10 +115,16 @@ module NewRelic::Agent::Instrumentation
96
115
 
97
116
  busy = busy / time_window
98
117
 
99
- busy = 1.0 if busy > 1.0 # cap at 100%
100
- @instance_busy.record_data_point busy
118
+ instance_busy_stats.record_data_point busy unless busy == 0
101
119
  @harvest_start = t0
102
120
  end
121
+ private
122
+ def instance_busy_stats
123
+ # Late binding on the Instance/busy stats
124
+ @instance_busy ||= NewRelic::Agent.agent.stats_engine.get_stats('Instance/Busy')
125
+ end
126
+
103
127
  end
128
+
104
129
  end
105
130
  end
@@ -0,0 +1,14 @@
1
+ module NewRelic::Agent::Instrumentation
2
+ module ErrorInstrumentation
3
+ module Shim
4
+ def newrelic_notice_error(*args); end
5
+ end
6
+ # Send the error instance to New Relic.
7
+ # +metric_path+ is the optional metric identifier given for the context of the error.
8
+ # +param_info+ is additional hash of info to be shown with the error.
9
+ def newrelic_notice_error(exception, metric_path = nil, param_info = {})
10
+ metric_path ||= self.newrelic_metric_path if self.respond_to? :newrelic_metric_path
11
+ NewRelic::Agent.agent.error_collector.notice_error(exception, nil, metric_path, param_info)
12
+ end
13
+ end
14
+ end
@@ -4,7 +4,16 @@ require 'merb-core/controller/merb_controller'
4
4
  Merb::Controller.class_eval do
5
5
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
6
6
 
7
- class_inheritable_accessor :newrelic_ignore_attr
7
+ class_inheritable_accessor :do_not_trace
8
+ class_inheritable_accessor :ignore_apdex
9
+
10
+ def self.newrelic_write_attr(attr_name, value) # :nodoc:
11
+ self.send "#{attr_name}=", attr_name, value
12
+ end
13
+
14
+ def self.newrelic_read_attr(attr_name) # :nodoc:
15
+ self.send attr_name
16
+ end
8
17
 
9
18
  protected
10
19
  # determine the path that is used in the metric name for
@@ -1,15 +1,13 @@
1
1
  require 'merb-core/dispatch/dispatcher'
2
2
  # NewRelic RPM instrumentation for http request dispatching (Routes mapping)
3
- # Note, the dispatcher class from no module into into the ActionController modile
4
- # in rails 2.0. Thus we need to check for both
5
-
6
3
  Merb::Request.class_eval do
7
4
 
8
- # This is for merb prior to 1.0
9
5
  include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
6
+
10
7
  alias_method :dispatch_without_newrelic, :handle
11
8
  alias_method :handle, :dispatch_newrelic
12
-
13
- # After merb 1.0, you can use before and after callbacks
14
- # for this?
9
+ def newrelic_response_code
10
+ # Don't have an easy way to get the HTTP status from here yet
11
+ nil
12
+ end
15
13
  end
@@ -1,6 +1,8 @@
1
1
  # Hook in the notification to merb
2
2
  error_notifier = Proc.new {
3
- NewRelic::Agent.agent.error_collector.notice_error("#{request.controller.name}/#{params[:action]}", request.path, params, request.exceptions.first)
3
+ if request.exceptions #check that there's actually an exception
4
+ NewRelic::Agent.agent.error_collector.notice_error(request.exceptions.first, request, "#{params[:controller]}/#{params[:action]}", params)
5
+ end
4
6
  }
5
7
  Merb::Dispatcher::DefaultException.before error_notifier
6
8
  Exceptions.before error_notifier
@@ -0,0 +1,7 @@
1
+ if defined?(PhusionPassenger)
2
+ NewRelic::Control.instance.log.debug "Installing Passenger shutdown hook."
3
+ PhusionPassenger.on_event(:stopping_worker_process) do
4
+ NewRelic::Control.instance.log.info "Passenger stopping this process, shutdown the agent."
5
+ NewRelic::Agent.instance.shutdown
6
+ end
7
+ end
@@ -1,6 +1,30 @@
1
1
 
2
- if defined? ActionController
2
+ if defined? ActionController
3
+
4
+ case Rails::VERSION::STRING
3
5
 
6
+ when /^(1\.|2\.0)/ # Rails 1.* - 2.0
7
+ ActionController::Base.class_eval do
8
+ add_method_tracer :render, 'View/#{newrelic_metric_path}/Rendering'
9
+ end
10
+
11
+ when /^2\.1\./ # Rails 2.1
12
+ ActionView::PartialTemplate.class_eval do
13
+ add_method_tracer :render, 'View/#{path_without_extension}.#{@view.template_format}.#{extension}/Partial'
14
+ end
15
+ ActionView::Template.class_eval do
16
+ add_method_tracer :render, 'View/#{path_without_extension}.#{@view.template_format}.#{extension}/Rendering'
17
+ end
18
+
19
+ when /^2\./ # Rails 2.2-2.*
20
+ ActionView::RenderablePartial.module_eval do
21
+ add_method_tracer :render_partial, 'View/#{path}/Partial'
22
+ end
23
+ ActionView::Template.class_eval do
24
+ add_method_tracer :render, 'View/#{path}/Rendering'
25
+ end
26
+ end
27
+
4
28
  ActionController::Base.class_eval do
5
29
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
6
30
 
@@ -10,15 +34,18 @@ if defined? ActionController
10
34
  alias_method :perform_action, :perform_action_with_newrelic_trace
11
35
  private :perform_action
12
36
 
13
- add_method_tracer :render, 'View/#{newrelic_metric_path}/Rendering'
37
+ #add_method_tracer :render_for_file, 'View/#{args[0]}/ForFile/Rendering'
38
+ #add_method_tracer :render_for_text, 'View/#{newrelic_metric_path}/Text/Rendering'
39
+ #add_method_tracer :render, 'View/#{newrelic_metric_path}/Rendering'
14
40
 
15
- def self.newrelic_ignore_attr=(value)
16
- write_inheritable_attribute('do_not_trace', value)
41
+ def self.newrelic_write_attr(attr_name, value) # :nodoc:
42
+ write_inheritable_attribute(attr_name, value)
17
43
  end
18
- def self.newrelic_ignore_attr
19
- read_inheritable_attribute('do_not_trace')
44
+
45
+ def self.newrelic_read_attr(attr_name) # :nodoc:
46
+ read_inheritable_attribute(attr_name)
20
47
  end
21
-
48
+
22
49
  # determine the path that is used in the metric name for
23
50
  # the called controller action
24
51
  def newrelic_metric_path(action_name_override = nil)
@@ -1,5 +1,4 @@
1
1
  require 'dispatcher'
2
-
3
2
  # NewRelic RPM instrumentation for http request dispatching (Routes mapping)
4
3
  # Note, the dispatcher class from no module into into the ActionController module
5
4
  # in Rails 2.0. Thus we need to check for both
@@ -7,23 +6,32 @@ if defined? ActionController::Dispatcher
7
6
  target = ActionController::Dispatcher
8
7
  elsif defined? Dispatcher
9
8
  target = Dispatcher
10
- else
11
- target = nil
12
9
  end
13
10
 
11
+ # NOTE TODO: maybe this should be done with a middleware?
14
12
  if target
13
+ require 'action_pack/version'
15
14
  NewRelic::Agent.instance.log.debug "Adding #{target} instrumentation"
16
15
 
17
- # in Rails 2.3 (Rack-based) we don't want to add instrumentation on class level
18
- unless defined? ::Rails::Rack
19
- target = target.class_eval { class << self; self; end }
20
- end
21
-
22
16
  target.class_eval do
23
- include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
24
-
25
- alias_method :dispatch_without_newrelic, :dispatch
26
- alias_method :dispatch, :dispatch_newrelic
17
+ if ActionPack::VERSION::MAJOR >= 2
18
+ # In versions later that 1.* the dispatcher callbacks are used
19
+ include NewRelic::Agent::Instrumentation::DispatcherInstrumentation
20
+ before_dispatch :newrelic_dispatcher_start
21
+ after_dispatch :newrelic_dispatcher_finish
22
+ def newrelic_response_code
23
+ (@response.headers['Status']||'200')[0..2] if ActionPack::VERSION::MAJOR == 2 && ActionPack::VERSION::MINOR < 3
24
+ end
25
+ else
26
+ # In version 1.2.* the instrumentation is done by method chaining
27
+ # the static dispatch method on the dispatcher class
28
+ extend NewRelic::Agent::Instrumentation::DispatcherInstrumentation
29
+ class << self
30
+ alias_method :dispatch_without_newrelic, :dispatch
31
+ alias_method :dispatch, :dispatch_newrelic
32
+ def newrelic_response_code; end
33
+ end
34
+ end
27
35
  end
28
36
  else
29
37
  NewRelic::Agent.instance.log.debug "WARNING: Dispatcher instrumentation not added"
@@ -1,11 +1,10 @@
1
- # todo: patch rescue_action and track how many are occuring and capture instances as well
1
+
2
2
  ActionController::Base.class_eval do
3
3
 
4
4
  def newrelic_notice_error(exception)
5
- local_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
5
+ filtered_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
6
6
 
7
- NewRelic::Agent.agent.error_collector.notice_error(newrelic_metric_path, (request) ? request.path : nil,
8
- local_params, exception)
7
+ NewRelic::Agent.agent.error_collector.notice_error(exception, request, newrelic_metric_path, filtered_params)
9
8
  end
10
9
 
11
10
  def rescue_action_with_newrelic_trace(exception)
@@ -21,3 +20,5 @@ ActionController::Base.class_eval do
21
20
  protected :rescue_action
22
21
 
23
22
  end if defined? ActionController
23
+
24
+ Object.send :include, NewRelic::Agent::Instrumentation::ErrorInstrumentation
@@ -1,171 +1,195 @@
1
+ module NewRelic::Agent
1
2
 
2
- class Module
3
-
4
- # Original method preserved for API backward compatibility
5
- def trace_method_execution (metric_name, push_scope, produce_metric, deduct_call_time_from_parent, &block)
6
- if push_scope
7
- trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent, &block)
8
- else
9
- trace_method_execution_no_scope(metric_name, &block)
10
- end
11
- end
12
-
13
- # This is duplicated inline in add_method_tracer
14
- def trace_method_execution_no_scope(metric_name)
15
- t0 = Time.now.to_f
16
- stats = @@newrelic_stats_engine.get_stats_no_scope metric_name
17
-
18
- result = yield
19
- duration = Time.now.to_f - t0 # for some reason this is 3 usec faster than Time - Time
20
- stats.trace_call(duration, duration)
21
- result
22
- end
3
+ # These are stubs for API methods installed when the agent is disabled.
23
4
 
24
- def trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent)
5
+ module MethodTracerShim
6
+ def trace_method_execution(*args); yield; end
7
+
8
+ def trace_method_execution_no_scope(metric_name); yield; end
9
+ def trace_method_execution_with_scope(*args); yield; end
10
+ def add_method_tracer (*args); end
11
+ def remove_method_tracer(*args); end
12
+ end
25
13
 
26
- t0 = Time.now.to_f
27
- stats = nil
14
+ # These are the class methods added to support installing custom
15
+ # metric tracers and executing for individual metrics.
16
+ # This module is included in class Module.
17
+ module MethodTracer
28
18
 
29
- begin
30
- # Keep a reference to the scope we are pushing so we can do a sanity check making
31
- # sure when we pop we get the one we 'expected'
32
- expected_scope = @@newrelic_stats_engine.push_scope(metric_name, t0, deduct_call_time_from_parent)
19
+ # Deprecated: original method preserved for API backward compatibility, use one of the other
20
+ # +trace_method..+ calls.
21
+ def trace_method_execution(metric_name, push_scope, produce_metric, deduct_call_time_from_parent, &block)
22
+ if push_scope
23
+ trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent, &block)
24
+ else
25
+ trace_method_execution_no_scope(metric_name, &block)
26
+ end
27
+ end
28
+
29
+ # Trace a given block with stats assigned to the given metric_name. It does not
30
+ # provide scoped measurements, meaning whatever is being traced will not 'blame the
31
+ # Controller'--that is to say appear in the breakdown chart.
32
+ # This is duplicated inline in #add_method_tracer.
33
+ def trace_method_execution_no_scope(metric_name)
34
+ t0 = Time.now.to_f
35
+ stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope metric_name
33
36
 
34
- stats = @@newrelic_stats_engine.get_stats metric_name, true if produce_metric
35
- rescue => e
36
- NewRelic::Config.instance.log.error("Caught exception in trace_method_execution header. Metric name = #{metric_name}, exception = #{e}")
37
- NewRelic::Config.instance.log.error(e.backtrace.join("\n"))
37
+ begin
38
+ yield
39
+ ensure
40
+ duration = Time.now.to_f - t0 # for some reason this is 3 usec faster than Time - Time
41
+ stats.trace_call(duration, duration)
42
+ end
38
43
  end
39
44
 
40
- begin
41
- yield
42
- ensure
43
- t1 = Time.now.to_f
44
- duration = t1 - t0
45
+ # Trace a given block with stats, like #trace_method_execution_no_scope but
46
+ def trace_method_execution_with_scope(metric_name, produce_metric, deduct_call_time_from_parent, scoped_metric_only=false)
47
+ t0 = Time.now.to_f
48
+ stats = nil
45
49
 
46
50
  begin
47
- if expected_scope
48
- scope = @@newrelic_stats_engine.pop_scope expected_scope, duration, t1
49
-
50
- exclusive = duration - scope.children_time
51
- stats.trace_call(duration, exclusive) if stats
52
- end
51
+ # Keep a reference to the scope we are pushing so we can do a sanity check making
52
+ # sure when we pop we get the one we 'expected'
53
+ expected_scope = NewRelic::Agent.instance.stats_engine.push_scope(metric_name, t0, deduct_call_time_from_parent)
54
+
55
+ stats = NewRelic::Agent.instance.stats_engine.get_stats(metric_name, true, scoped_metric_only) if produce_metric
53
56
  rescue => e
54
- NewRelic::Config.instance.log.error("Caught exception in trace_method_execution footer. Metric name = #{metric_name}, exception = #{e}")
55
- NewRelic::Config.instance.log.error(e.backtrace.join("\n"))
57
+ NewRelic::Control.instance.log.error("Caught exception in trace_method_execution header. Metric name = #{metric_name}, exception = #{e}")
58
+ NewRelic::Control.instance.log.error(e.backtrace.join("\n"))
59
+ end
60
+
61
+ begin
62
+ yield
63
+ ensure
64
+ t1 = Time.now.to_f
65
+ duration = t1 - t0
66
+
67
+ begin
68
+ if expected_scope
69
+ scope = NewRelic::Agent.instance.stats_engine.pop_scope expected_scope, duration, t1
70
+
71
+ exclusive = duration - scope.children_time
72
+ stats.trace_call(duration, exclusive) if stats
73
+ end
74
+ rescue => e
75
+ NewRelic::Control.instance.log.error("Caught exception in trace_method_execution footer. Metric name = #{metric_name}, exception = #{e}")
76
+ NewRelic::Control.instance.log.error(e.backtrace.join("\n"))
77
+ end
56
78
  end
57
79
  end
58
- end
59
-
60
- # Add a method tracer to the specified method.
61
- # metric_name_code is ruby code that determines the name of the
62
- # metric to be collected during tracing. As such, the code
63
- # should be provided in 'single quote' strings rather than
64
- # "double quote" strings, so that #{} evaluation happens
65
- # at traced method execution time.
66
- # Example: tracing a method :foo, where the metric name is
67
- # the first argument converted to a string
68
- # add_method_tracer :foo, '#{args.first.to_s}'
69
- # statically defined metric names can be specified as regular strings
70
- # push_scope specifies whether this method tracer should push
71
- # the metric name onto the scope stack.
72
- def add_method_tracer (method_name, metric_name_code, options = {})
73
- return unless NewRelic::Agent.agent.config.tracers_enabled?
74
-
75
- @@newrelic_stats_engine ||= NewRelic::Agent.agent.stats_engine
76
-
77
- if !options.is_a?(Hash)
78
- options = {:push_scope => options}
79
- end
80
- # options[:push_scope] true if we are noting the scope of this for
81
- # stats collection as well as the transaction tracing
82
- options[:push_scope] = true if options[:push_scope].nil?
83
- # options[:metric] true if you are tracking stats for a metric, otherwise
84
- # it's just for transaction tracing.
85
- options[:metric] = true if options[:metric].nil?
86
- options[:deduct_call_time_from_parent] = false if options[:deduct_call_time_from_parent].nil? && !options[:metric]
87
- options[:deduct_call_time_from_parent] = true if options[:deduct_call_time_from_parent].nil?
88
- options[:code_header] ||= ""
89
- options[:code_footer] ||= ""
90
-
91
- klass = (self === Module) ? "self" : "self.class"
92
-
93
- unless method_defined?(method_name) || private_method_defined?(method_name)
94
- NewRelic::Config.instance.log.warn("Did not trace #{self}##{method_name} because that method does not exist")
95
- return
96
- end
97
-
98
- traced_method_name = _traced_method_name(method_name, metric_name_code)
99
- if method_defined? traced_method_name
100
- NewRelic::Config.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}")
101
- return
102
- end
103
-
104
- fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
105
80
 
106
- if options[:push_scope] == false
107
- class_eval "@@newrelic_stats_engine = NewRelic::Agent.agent.stats_engine"
108
- code = <<-CODE
81
+ # Add a method tracer to the specified method.
82
+ # metric_name_code is ruby code that determines the name of the
83
+ # metric to be collected during tracing. As such, the code
84
+ # should be provided in 'single quote' strings rather than
85
+ # "double quote" strings, so that #{} evaluation happens
86
+ # at traced method execution time.
87
+ #
88
+ # Example: tracing a method :foo, where the metric name is
89
+ # the first argument converted to a string
90
+ #
91
+ # add_method_tracer :foo, '#{args.first.to_s}'
92
+ #
93
+ # Statically defined metric names can be specified as regular strings.
94
+ # The option +:push_scope+ specifies whether this method tracer should
95
+ # keep track of the caller so it will show up in controller breakdown
96
+ # pie charts.
97
+ def add_method_tracer (method_name, metric_name_code, options = {})
98
+ if !options.is_a?(Hash)
99
+ options = {:push_scope => options}
100
+ end
101
+ # options[:push_scope] true if we are noting the scope of this for
102
+ # stats collection as well as the transaction tracing
103
+ options[:push_scope] = true if options[:push_scope].nil?
104
+ # options[:metric] true if you are tracking stats for a metric, otherwise
105
+ # it's just for transaction tracing.
106
+ options[:metric] = true if options[:metric].nil?
107
+ options[:deduct_call_time_from_parent] = false if options[:deduct_call_time_from_parent].nil? && !options[:metric]
108
+ options[:deduct_call_time_from_parent] = true if options[:deduct_call_time_from_parent].nil?
109
+ options[:code_header] ||= ""
110
+ options[:code_footer] ||= ""
111
+ options[:scoped_metric_only] ||= false
112
+
113
+ klass = (self === Module) ? "self" : "self.class"
114
+
115
+ unless method_defined?(method_name) || private_method_defined?(method_name)
116
+ NewRelic::Control.instance.log.warn("Did not trace #{self}##{method_name} because that method does not exist")
117
+ return
118
+ end
119
+
120
+ traced_method_name = _traced_method_name(method_name, metric_name_code)
121
+ if method_defined? traced_method_name
122
+ NewRelic::Control.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}")
123
+ return
124
+ end
125
+
126
+ fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
127
+
128
+ if options[:push_scope] == false
129
+ code = <<-CODE
109
130
  def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
110
131
  #{options[:code_header]}
111
132
 
112
133
  t0 = Time.now.to_f
113
- stats = @@newrelic_stats_engine.get_stats_no_scope "#{metric_name_code}"
134
+ stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope "#{metric_name_code}"
114
135
 
115
- result = #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
116
- duration = Time.now.to_f - t0
117
- stats.trace_call(duration, duration) # for some reason this is 3 usec faster than Time - Time
118
- #{options[:code_footer]}
119
- result
136
+ begin
137
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
138
+ ensure
139
+ duration = Time.now.to_f - t0
140
+ stats.trace_call(duration, duration) # for some reason this is 3 usec faster than Time - Time
141
+ #{options[:code_footer]}
142
+ end
120
143
  end
121
144
  CODE
122
- else
123
- code = <<-CODE
145
+ else
146
+ code = <<-CODE
124
147
  def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
125
148
  #{options[:code_header]}
126
- result = #{klass}.trace_method_execution_with_scope("#{metric_name_code}", #{options[:metric]}, #{options[:deduct_call_time_from_parent]}) do
149
+ result = #{klass}.trace_method_execution_with_scope("#{metric_name_code}", #{options[:metric]}, #{options[:deduct_call_time_from_parent]}, #{options[:scoped_metric_only]}) do
127
150
  #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
128
151
  end
129
152
  #{options[:code_footer]}
130
153
  result
131
154
  end
132
155
  CODE
156
+ end
157
+
158
+ class_eval code, __FILE__, __LINE__
159
+
160
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
161
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
162
+
163
+ NewRelic::Control.instance.log.debug("Traced method: class = #{self}, method = #{method_name}, "+
164
+ "metric = '#{metric_name_code}', options: #{options.inspect}, ")
133
165
  end
166
+
167
+ # Not recommended for production use, because tracers must be removed in reverse-order
168
+ # from when they were added, or else other tracers that were added to the same method
169
+ # may get removed as well.
170
+ def remove_method_tracer(method_name, metric_name_code)
171
+ return unless NewRelic::Control.instance.agent_enabled?
134
172
 
135
- class_eval code, __FILE__, __LINE__
136
-
137
- alias_method _untraced_method_name(method_name, metric_name_code), method_name
138
- alias_method method_name, _traced_method_name(method_name, metric_name_code)
173
+ if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
174
+ alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
175
+ undef_method "#{_traced_method_name(method_name, metric_name_code)}"
176
+ else
177
+ raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
178
+ end
179
+ end
139
180
 
140
- NewRelic::Config.instance.log.debug("Traced method: class = #{self}, method = #{method_name}, "+
141
- "metric = '#{metric_name_code}', options: #{options}, ")
142
- end
143
-
144
- # Not recommended for production use, because tracers must be removed in reverse-order
145
- # from when they were added, or else other tracers that were added to the same method
146
- # may get removed as well.
147
- def remove_method_tracer(method_name, metric_name_code)
148
- return unless NewRelic::Agent.agent.config.tracers_enabled?
181
+ private
149
182
 
150
- if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
151
- alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
152
- undef_method "#{_traced_method_name(method_name, metric_name_code)}"
153
- else
154
- raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
155
- end
156
- end
157
-
158
- private
159
-
160
- def _untraced_method_name(method_name, metric_name)
183
+ def _untraced_method_name(method_name, metric_name)
161
184
  "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
162
- end
163
-
164
- def _traced_method_name(method_name, metric_name)
185
+ end
186
+
187
+ def _traced_method_name(method_name, metric_name)
165
188
  "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
166
- end
167
-
168
- def _sanitize_name(name)
169
- name.to_s.tr('^a-z,A-Z,0-9', '_')
189
+ end
190
+
191
+ def _sanitize_name(name)
192
+ name.to_s.tr('^a-z,A-Z,0-9', '_')
193
+ end
170
194
  end
171
195
  end