newrelic_rpm 3.1.0 → 3.1.1.beta1

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 (94) hide show
  1. data/CHANGELOG +3 -0
  2. data/lib/new_relic/agent.rb +29 -12
  3. data/lib/new_relic/agent/agent.rb +355 -78
  4. data/lib/new_relic/agent/beacon_configuration.rb +49 -7
  5. data/lib/new_relic/agent/browser_monitoring.rb +20 -1
  6. data/lib/new_relic/agent/busy_calculator.rb +11 -3
  7. data/lib/new_relic/agent/chained_call.rb +2 -2
  8. data/lib/new_relic/agent/error_collector.rb +229 -183
  9. data/lib/new_relic/agent/instrumentation.rb +2 -2
  10. data/lib/new_relic/agent/instrumentation/active_merchant.rb +5 -1
  11. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +5 -1
  12. data/lib/new_relic/agent/instrumentation/authlogic.rb +4 -0
  13. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +16 -5
  14. data/lib/new_relic/agent/instrumentation/data_mapper.rb +5 -1
  15. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +5 -1
  16. data/lib/new_relic/agent/instrumentation/memcache.rb +5 -1
  17. data/lib/new_relic/agent/instrumentation/merb/controller.rb +5 -1
  18. data/lib/new_relic/agent/instrumentation/merb/errors.rb +5 -1
  19. data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +0 -5
  20. data/lib/new_relic/agent/instrumentation/net.rb +5 -1
  21. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +7 -3
  22. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +5 -1
  23. data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +5 -1
  24. data/lib/new_relic/agent/instrumentation/rails/errors.rb +4 -0
  25. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +4 -0
  26. data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +13 -6
  27. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +4 -0
  28. data/lib/new_relic/agent/instrumentation/sinatra.rb +4 -0
  29. data/lib/new_relic/agent/instrumentation/sunspot.rb +4 -0
  30. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +5 -1
  31. data/lib/new_relic/agent/method_tracer.rb +205 -99
  32. data/lib/new_relic/agent/shim_agent.rb +0 -1
  33. data/lib/new_relic/agent/stats_engine.rb +1 -0
  34. data/lib/new_relic/agent/stats_engine/metric_stats.rb +23 -7
  35. data/lib/new_relic/agent/stats_engine/samplers.rb +8 -2
  36. data/lib/new_relic/agent/stats_engine/transactions.rb +26 -12
  37. data/lib/new_relic/agent/transaction_sampler.rb +3 -1
  38. data/lib/new_relic/agent/worker_loop.rb +13 -5
  39. data/lib/new_relic/collection_helper.rb +6 -3
  40. data/lib/new_relic/control.rb +1 -3
  41. data/lib/new_relic/control/class_methods.rb +8 -3
  42. data/lib/new_relic/control/configuration.rb +24 -5
  43. data/lib/new_relic/control/frameworks.rb +10 -0
  44. data/lib/new_relic/control/frameworks/external.rb +4 -4
  45. data/lib/new_relic/control/frameworks/merb.rb +1 -0
  46. data/lib/new_relic/control/frameworks/rails.rb +5 -5
  47. data/lib/new_relic/control/frameworks/rails3.rb +5 -3
  48. data/lib/new_relic/control/frameworks/ruby.rb +5 -5
  49. data/lib/new_relic/control/frameworks/sinatra.rb +1 -4
  50. data/lib/new_relic/control/instance_methods.rb +23 -7
  51. data/lib/new_relic/control/instrumentation.rb +22 -3
  52. data/lib/new_relic/control/logging_methods.rb +25 -7
  53. data/lib/new_relic/control/server_methods.rb +16 -6
  54. data/lib/new_relic/data_serialization.rb +83 -14
  55. data/lib/new_relic/delayed_job_injection.rb +7 -1
  56. data/lib/new_relic/local_environment.rb +55 -25
  57. data/lib/new_relic/metric_data.rb +7 -2
  58. data/lib/new_relic/metric_spec.rb +5 -3
  59. data/lib/new_relic/stats.rb +16 -7
  60. data/lib/new_relic/transaction_analysis.rb +2 -1
  61. data/lib/new_relic/transaction_analysis/segment_summary.rb +4 -2
  62. data/lib/new_relic/transaction_sample.rb +33 -7
  63. data/lib/new_relic/transaction_sample/segment.rb +21 -3
  64. data/lib/new_relic/version.rb +2 -2
  65. data/newrelic_rpm.gemspec +7 -11
  66. data/test/config/newrelic.yml +1 -1
  67. data/test/new_relic/agent/agent/start_worker_thread_test.rb +1 -4
  68. data/test/new_relic/agent/agent_test.rb +16 -0
  69. data/test/new_relic/agent/agent_test_controller.rb +1 -1
  70. data/test/new_relic/agent/agent_test_controller_test.rb +14 -19
  71. data/test/new_relic/agent/beacon_configuration_test.rb +2 -2
  72. data/test/new_relic/agent/browser_monitoring_test.rb +7 -3
  73. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +13 -4
  74. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +0 -10
  75. data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +1 -1
  76. data/test/new_relic/agent_test.rb +168 -0
  77. data/test/new_relic/collection_helper_test.rb +21 -3
  78. data/test/new_relic/control/configuration_test.rb +25 -0
  79. data/test/new_relic/data_serialization_test.rb +58 -3
  80. data/test/new_relic/delayed_job_injection_test.rb +17 -0
  81. data/test/new_relic/transaction_analysis/segment_summary_test.rb +14 -0
  82. data/test/new_relic/transaction_analysis_test.rb +3 -3
  83. data/test/new_relic/transaction_sample/segment_test.rb +11 -0
  84. data/test/test_helper.rb +1 -1
  85. data/vendor/gems/dependency_detection-0.0.1.build/LICENSE +4 -18
  86. metadata +13 -13
  87. data/lib/new_relic/histogram.rb +0 -91
  88. data/lib/new_relic/rack/metric_app.rb +0 -65
  89. data/lib/new_relic/rack/mongrel_rpm.ru +0 -28
  90. data/lib/new_relic/rack/newrelic.yml +0 -27
  91. data/lib/new_relic/rack_app.rb +0 -6
  92. data/vendor/gems/dependency_detection-0.0.1.build/README +0 -0
  93. data/vendor/gems/metric_parser-0.1.0.pre1/LICENSE +0 -0
  94. data/vendor/gems/metric_parser-0.1.0.pre1/README +0 -0
@@ -1,8 +1,8 @@
1
1
  require 'new_relic/agent'
2
- # stub module that contains instrumentation defined in the
3
- # instrumentation directory
4
2
  module NewRelic
5
3
  module Agent
4
+ # stub module that contains instrumentation defined in the
5
+ # instrumentation directory
6
6
  module Instrumentation
7
7
  end
8
8
  end
@@ -2,7 +2,11 @@ DependencyDetection.defer do
2
2
  depends_on do
3
3
  defined?(ActiveMerchant)
4
4
  end
5
-
5
+
6
+ executes do
7
+ NewRelic::Agent.logger.debug 'Installing ActiveMerchant instrumentation'
8
+ end
9
+
6
10
  executes do
7
11
  ActiveMerchant::Billing::Gateway.implementations.each do |gateway|
8
12
  gateway.class_eval do
@@ -33,7 +33,11 @@ DependencyDetection.defer do
33
33
  depends_on do
34
34
  defined?(ActsAsSolr::CommonMethods)
35
35
  end
36
-
36
+
37
+ executes do
38
+ NewRelic::Agent.logger.debug 'Installing ActsAsSolr instrumentation'
39
+ end
40
+
37
41
  executes do
38
42
  ActsAsSolr::ParserMethods.module_eval do
39
43
  include NewRelic::Instrumentation::ActsAsSolrInstrumentation::ParserMethodsInstrumentation
@@ -5,6 +5,10 @@ DependencyDetection.defer do
5
5
  defined?(AuthLogic::Session::Base)
6
6
  end
7
7
 
8
+ executes do
9
+ NewRelic::Agent.logger.debug 'Installing AuthLogic instrumentation'
10
+ end
11
+
8
12
  executes do
9
13
  AuthLogic::Session::Base.class_eval do
10
14
  add_method_tracer :find, 'Custom/Authlogic/find'
@@ -276,18 +276,26 @@ module NewRelic
276
276
  def newrelic_request_headers
277
277
  self.respond_to?(:request) && self.request.respond_to?(:headers) && self.request.headers
278
278
  end
279
-
279
+
280
+ # overrideable method to determine whether to trace an action
281
+ # or not - you may override this in your controller and supply
282
+ # your own logic for ignoring transactions.
280
283
  def do_not_trace?
281
284
  _is_filtered?('do_not_trace')
282
285
  end
283
-
286
+
287
+ # overrideable method to determine whether to trace an action
288
+ # for purposes of apdex measurement - you can use this to
289
+ # ignore things like api calls or other fast non-user-facing
290
+ # actions
284
291
  def ignore_apdex?
285
292
  _is_filtered?('ignore_apdex')
286
293
  end
287
294
 
288
295
  private
289
296
 
290
- # Profile the instrumented call. Dev mode only. Experimental.
297
+ # Profile the instrumented call. Dev mode only. Experimental
298
+ # - should definitely not be used on production applications
291
299
  def perform_action_with_newrelic_profile(args)
292
300
  frame_data = _push_metric_frame(block_given? ? args : [])
293
301
  val = nil
@@ -358,7 +366,8 @@ module NewRelic
358
366
  [category, path, params]
359
367
  end
360
368
 
361
- # Filter out
369
+ # Filter out a request if it matches one of our parameters for
370
+ # ignoring it - the key is either 'do_not_trace' or 'ignore_apdex'
362
371
  def _is_filtered?(key)
363
372
  ignore_actions = self.class.newrelic_read_attr(key) if self.class.respond_to? :newrelic_read_attr
364
373
  case ignore_actions
@@ -402,7 +411,9 @@ module NewRelic
402
411
  NewRelic::Control.instance.log.debug("#{e.backtrace[0..20]}")
403
412
  now
404
413
  end
405
-
414
+
415
+ # returns the NewRelic::MethodTraceStats object associated
416
+ # with the dispatcher time measurement
406
417
  def _dispatch_stat
407
418
  NewRelic::Agent.agent.stats_engine.get_stats_no_scope 'HttpDispatcher'
408
419
  end
@@ -53,7 +53,11 @@ DependencyDetection.defer do
53
53
  depends_on do
54
54
  defined?(DataMapper::Collection)
55
55
  end
56
-
56
+
57
+ executes do
58
+ NewRelic::Agent.logger.debug 'Installing DataMapper instrumentation'
59
+ end
60
+
57
61
  executes do
58
62
  DataMapper::Model.class_eval do
59
63
  add_method_tracer :get, 'ActiveRecord/#{self.name}/get'
@@ -8,7 +8,11 @@ DependencyDetection.defer do
8
8
  depends_on do
9
9
  defined?(::Delayed) && defined?(::Delayed::Job)
10
10
  end
11
-
11
+
12
+ executes do
13
+ NewRelic::Agent.logger.debug 'Installing DelayedJob instrumentation'
14
+ end
15
+
12
16
  executes do
13
17
  Delayed::Job.class_eval do
14
18
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
@@ -45,7 +45,11 @@ DependencyDetection.defer do
45
45
  depends_on do
46
46
  !NewRelic::Control.instance['disable_memcache_instrumentation']
47
47
  end
48
-
48
+
49
+ executes do
50
+ NewRelic::Agent.logger.debug 'Installing Memcached instrumentation'
51
+ end
52
+
49
53
  executes do
50
54
  %w[get get_multi set add incr decr delete replace append prepend cas].each do | method_name |
51
55
  NewRelic::Agent::Instrumentation::Memcache.instrument_method(::MemCache, method_name) if defined? ::MemCache
@@ -5,7 +5,11 @@ DependencyDetection.defer do
5
5
  depends_on do
6
6
  defined?(Merb) && defined?(Merb::Controller)
7
7
  end
8
-
8
+
9
+ executes do
10
+ NewRelic::Agent.logger.debug 'Installing Merb Controller instrumentation'
11
+ end
12
+
9
13
  executes do
10
14
  require 'merb-core/controller/merb_controller'
11
15
 
@@ -6,7 +6,11 @@ DependencyDetection.defer do
6
6
  depends_on do
7
7
  Merb::Dispatcher::DefaultException.respond_to?(:before)
8
8
  end
9
-
9
+
10
+ executes do
11
+ NewRelic::Agent.logger.debug 'Installing Merb Errors instrumentation'
12
+ end
13
+
10
14
  executes do
11
15
 
12
16
  # Hook in the notification to merb
@@ -17,10 +17,6 @@ module NewRelic
17
17
  NewRelic::Agent.logger.error "Underflow in metric frames: #{caller.join("\n ")}"
18
18
  end
19
19
 
20
- def process_histogram_for_transaction(ending)
21
- agent.histogram.process((ending - start).to_f)
22
- end
23
-
24
20
  def notice_scope_empty
25
21
  transaction_sampler.notice_scope_empty
26
22
  end
@@ -59,7 +55,6 @@ module NewRelic
59
55
 
60
56
  def notify_transaction_sampler(web_transaction)
61
57
  record_transaction_cpu
62
- process_histogram_for_transaction(Time.now) if web_transaction
63
58
  notice_scope_empty
64
59
  end
65
60
 
@@ -2,7 +2,11 @@ DependencyDetection.defer do
2
2
  depends_on do
3
3
  defined?(Net) && defined?(Net::HTTP)
4
4
  end
5
-
5
+
6
+ executes do
7
+ NewRelic::Agent.logger.debug 'Installing Net instrumentation'
8
+ end
9
+
6
10
  executes do
7
11
  Net::HTTP.class_eval do
8
12
  def request_with_newrelic_trace(*args, &block)
@@ -1,3 +1,5 @@
1
+ # FIXME: this should be a separate dependency block for each kind of
2
+ # view instrumentation
1
3
  DependencyDetection.defer do
2
4
  depends_on do
3
5
  defined?(ActionController) && defined?(ActionController::Base)
@@ -11,8 +13,7 @@ DependencyDetection.defer do
11
13
  !NewRelic::Control.instance['disable_view_instrumentation']
12
14
  end
13
15
 
14
- executes do
15
-
16
+ executes do
16
17
  case Rails::VERSION::STRING
17
18
 
18
19
  when /^(1\.|2\.0)/ # Rails 1.* - 2.0
@@ -48,9 +49,12 @@ DependencyDetection.defer do
48
49
  depends_on do
49
50
  defined?(Rails) && Rails::VERSION::MAJOR.to_i == 2
50
51
  end
51
-
52
+
52
53
  executes do
54
+ NewRelic::Agent.logger.debug 'Installing Rails Controller instrumentation'
55
+ end
53
56
 
57
+ executes do
54
58
  ActionController::Base.class_eval do
55
59
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
56
60
 
@@ -2,7 +2,11 @@ DependencyDetection.defer do
2
2
  depends_on do
3
3
  defined?(ActionWebService)
4
4
  end
5
-
5
+
6
+ executes do
7
+ NewRelic::Agent.logger.debug 'Installing Rails ActionWebService instrumentation'
8
+ end
9
+
6
10
  executes do
7
11
  # NewRelic Agent instrumentation for WebServices
8
12
 
@@ -87,7 +87,11 @@ DependencyDetection.defer do
87
87
  depends_on do
88
88
  !NewRelic::Control.instance['disable_activerecord_instrumentation']
89
89
  end
90
-
90
+
91
+ executes do
92
+ NewRelic::Agent.logger.debug 'Installing Rails ActiveRecord instrumentation'
93
+ end
94
+
91
95
  executes do
92
96
  ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
93
97
  include ::NewRelic::Agent::Instrumentation::ActiveRecordInstrumentation
@@ -7,6 +7,10 @@ DependencyDetection.defer do
7
7
  defined?(Rails) && Rails::VERSION::MAJOR.to_i == 2
8
8
  end
9
9
 
10
+ executes do
11
+ NewRelic::Agent.logger.debug 'Installing Rails Error instrumentation'
12
+ end
13
+
10
14
  executes do
11
15
 
12
16
  ActionController::Base.class_eval do
@@ -50,6 +50,10 @@ DependencyDetection.defer do
50
50
  defined?(ActionController) && defined?(ActionController::Base)
51
51
  end
52
52
 
53
+ executes do
54
+ NewRelic::Agent.logger.debug 'Installing Rails3 Controller instrumentation'
55
+ end
56
+
53
57
  executes do
54
58
  class ActionController::Base
55
59
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
@@ -13,9 +13,11 @@ module NewRelic
13
13
  end
14
14
  end
15
15
 
16
- def log_with_newrelic_instrumentation(sql, name, &block)
16
+ def log_with_newrelic_instrumentation(*args, &block)
17
17
 
18
- return log_without_newrelic_instrumentation(sql, name, &block) unless NewRelic::Agent.is_execution_traced?
18
+ return log_without_newrelic_instrumentation(*args, &block) unless NewRelic::Agent.is_execution_traced?
19
+
20
+ sql, name, binds = args
19
21
 
20
22
  # Capture db config if we are going to try to get the explain plans
21
23
  if (defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter) && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)) ||
@@ -26,7 +28,7 @@ module NewRelic
26
28
  model = parts.first
27
29
  operation = parts.last.downcase
28
30
  metric_name = case operation
29
- when 'load' then 'find'
31
+ when 'load', 'count', 'exists' then 'find'
30
32
  when 'indexes', 'columns' then nil # fall back to DirectSQL
31
33
  when 'destroy', 'find', 'save', 'create' then operation
32
34
  when 'update' then 'save'
@@ -53,14 +55,15 @@ module NewRelic
53
55
  end
54
56
 
55
57
  if !metric
56
- log_without_newrelic_instrumentation(sql, name, &block)
58
+ log_without_newrelic_instrumentation(*args, &block)
57
59
  else
58
60
  metrics = [metric, "ActiveRecord/all"]
59
61
  metrics << "ActiveRecord/#{metric_name}" if metric_name
60
62
  self.class.trace_execution_scoped(metrics) do
63
+ sql, name, binds = args
61
64
  t0 = Time.now
62
65
  begin
63
- log_without_newrelic_instrumentation(sql, name, &block)
66
+ log_without_newrelic_instrumentation(*args, &block)
64
67
  ensure
65
68
  NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, supported_config, (Time.now - t0).to_f)
66
69
  end
@@ -89,7 +92,11 @@ DependencyDetection.defer do
89
92
  depends_on do
90
93
  !NewRelic::Control.instance['disable_activerecord_instrumentation']
91
94
  end
92
-
95
+
96
+ executes do
97
+ NewRelic::Agent.logger.debug 'Installing Rails3 ActiveRecord instrumentation'
98
+ end
99
+
93
100
  executes do
94
101
  Rails.configuration.after_initialize do
95
102
  ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
@@ -23,6 +23,10 @@ DependencyDetection.defer do
23
23
  defined?(ActionController) && defined?(ActionController::Base)
24
24
  end
25
25
 
26
+ executes do
27
+ NewRelic::Agent.logger.debug 'Installing Rails3 Error instrumentation'
28
+ end
29
+
26
30
  executes do
27
31
  class ActionController::Base
28
32
  include NewRelic::Agent::Instrumentation::Rails3::Errors
@@ -5,6 +5,10 @@ DependencyDetection.defer do
5
5
  defined?(::Sinatra) && defined?(::Sinatra::Base)
6
6
  end
7
7
 
8
+ executes do
9
+ NewRelic::Agent.logger.debug 'Installing Sinatra instrumentation'
10
+ end
11
+
8
12
  executes do
9
13
  ::Sinatra::Base.class_eval do
10
14
  include NewRelic::Agent::Instrumentation::Sinatra
@@ -3,6 +3,10 @@ DependencyDetection.defer do
3
3
  defined?(::Sunspot)
4
4
  end
5
5
 
6
+ executes do
7
+ NewRelic::Agent.logger.debug 'Installing Rails Sunspot instrumentation'
8
+ end
9
+
6
10
  executes do
7
11
  ::Sunspot.module_eval do
8
12
  class << self
@@ -2,7 +2,11 @@ DependencyDetection.defer do
2
2
  depends_on do
3
3
  defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
4
4
  end
5
-
5
+
6
+ executes do
7
+ NewRelic::Agent.logger.debug 'Installing Unicorn instrumentation'
8
+ end
9
+
6
10
  executes do
7
11
  Unicorn::HttpServer.class_eval do
8
12
  NewRelic::Agent.logger.debug "Installing Unicorn worker hook."
@@ -44,7 +44,9 @@ module NewRelic
44
44
  clazz.extend ClassMethods
45
45
  clazz.extend InstanceMethods
46
46
  end
47
-
47
+
48
+ # Defines modules used at instrumentation runtime, to do the
49
+ # actual tracing of time spent
48
50
  module InstanceMethods
49
51
  # Deprecated: original method preserved for API backward compatibility.
50
52
  # Use either #trace_execution_scoped or #trace_execution_unscoped
@@ -90,35 +92,51 @@ module NewRelic
90
92
  end
91
93
 
92
94
  alias trace_method_execution_no_scope trace_execution_unscoped #:nodoc:
93
-
95
+
96
+ # Refactored out of the previous trace_execution_scoped
97
+ # method, most methods in this module relate to code used in
98
+ # the #trace_execution_scoped method in this module
94
99
  module TraceExecutionScoped
100
+ # Shorthand to return the NewRelic::Agent.instance
95
101
  def agent_instance
96
102
  NewRelic::Agent.instance
97
103
  end
98
-
104
+
105
+ # Shorthand to return the status of tracing
99
106
  def traced?
100
107
  NewRelic::Agent.is_execution_traced?
101
108
  end
102
-
109
+
110
+ # Tracing is disabled if we are not in a traced context and
111
+ # no force option is supplied
103
112
  def trace_disabled?(options)
104
113
  !(traced? || options[:force])
105
114
  end
106
-
115
+
116
+ # Shorthand to return the current statistics engine
107
117
  def stat_engine
108
118
  agent_instance.stats_engine
109
119
  end
110
-
120
+
121
+ # returns a scoped metric stat for the specified name
111
122
  def get_stats_scoped(first_name, scoped_metric_only)
112
123
  stat_engine.get_stats(first_name, true, scoped_metric_only)
113
124
  end
125
+ # Shorthand method to get stats from the stat engine
114
126
  def get_stats_unscoped(name)
115
127
  stat_engine.get_stats_no_scope(name)
116
128
  end
117
-
129
+
130
+ # the main statistic we should record in
131
+ # #trace_execution_scoped - a scoped metric provided by the
132
+ # first item in the metric array
118
133
  def main_stat(metric, options)
119
134
  get_stats_scoped(metric, options[:scoped_metric_only])
120
135
  end
121
-
136
+
137
+ # returns an array containing the first metric, and an array
138
+ # of other unscoped statistics we should also record along
139
+ # side it
122
140
  def get_metric_stats(metrics, options)
123
141
  metrics = Array(metrics)
124
142
  first_name = metrics.shift
@@ -128,26 +146,51 @@ module NewRelic
128
146
  stats.unshift(main_stat(first_name, options)) if options[:metric]
129
147
  [first_name, stats]
130
148
  end
131
-
149
+
150
+ # Helper for setting a hash key if the hash key is nil,
151
+ # instead of the default ||= behavior which sets if it is
152
+ # false as well
132
153
  def set_if_nil(hash, key)
133
154
  hash[key] = true if hash[key].nil?
134
155
  end
135
-
156
+
157
+ # delegates to #agent_instance to push a trace execution
158
+ # flag, only if execution of this metric is forced.
159
+ #
160
+ # This causes everything scoped inside this metric to be
161
+ # recorded, even if the parent transaction is generally not.
136
162
  def push_flag!(forced)
137
163
  agent_instance.push_trace_execution_flag(true) if forced
138
164
  end
139
-
165
+
166
+ # delegates to #agent_instance to pop the trace execution
167
+ # flag, only if execution of this metric is
168
+ # forced. otherwise this is taken care of for us
169
+ # automatically.
170
+ #
171
+ # This ends the forced recording of metrics within the
172
+ # #trace_execution_scoped block
140
173
  def pop_flag!(forced)
141
174
  agent_instance.pop_trace_execution_flag if forced
142
175
  end
143
-
176
+
177
+ # helper for logging errors to the newrelic_agent.log
178
+ # properly. Logs the error at error level, and includes a
179
+ # backtrace if we're running at debug level
144
180
  def log_errors(code_area, metric)
145
181
  yield
146
182
  rescue => e
147
183
  NewRelic::Control.instance.log.error("Caught exception in #{code_area}. Metric name = #{metric}, exception = #{e}")
148
184
  NewRelic::Control.instance.log.error(e.backtrace.join("\n"))
149
185
  end
150
-
186
+
187
+ # provides the header for our traced execution scoped
188
+ # method - gets the initial time, sets the tracing flag if
189
+ # needed, and pushes the scope onto the metric stack
190
+ # logs any errors that occur and returns the start time and
191
+ # the scope so that we can check for it later, to maintain
192
+ # sanity. If the scope stack becomes unbalanced, this
193
+ # transaction loses meaning.
151
194
  def trace_execution_scoped_header(metric, options, t0=Time.now.to_f)
152
195
  scope = log_errors("trace_execution_scoped header", metric) do
153
196
  push_flag!(options[:force])
@@ -157,7 +200,15 @@ module NewRelic
157
200
  # the start time.
158
201
  [t0, scope]
159
202
  end
160
-
203
+
204
+ # Handles the end of the #trace_execution_scoped method -
205
+ # calculating the time taken, popping the tracing flag if
206
+ # needed, deducting time taken by children, and tracing the
207
+ # subsidiary unscoped metrics if any
208
+ #
209
+ # this method fails safely if the header does not manage to
210
+ # push the scope onto the stack - it simply does not trace
211
+ # any metrics.
161
212
  def trace_execution_scoped_footer(t0, first_name, metric_stats, expected_scope, forced, t1=Time.now.to_f)
162
213
  log_errors("trace_method_execution footer", first_name) do
163
214
  duration = t1 - t0
@@ -198,99 +249,51 @@ module NewRelic
198
249
  include TraceExecutionScoped
199
250
 
200
251
  end
201
-
252
+
253
+ # Defines methods used at the class level, for adding instrumentation
202
254
  module ClassMethods
203
- # Add a method tracer to the specified method.
204
- #
205
- # === Common Options
206
- #
207
- # * <tt>:push_scope => false</tt> specifies this method tracer should not
208
- # keep track of the caller; it will not show up in controller breakdown
209
- # pie charts.
210
- # * <tt>:metric => false</tt> specifies that no metric will be recorded.
211
- # Instead the call will show up in transaction traces as well as traces
212
- # shown in Developer Mode.
213
- #
214
- # === Uncommon Options
215
- #
216
- # * <tt>:scoped_metric_only => true</tt> indicates that the unscoped metric
217
- # should not be recorded. Normally two metrics are potentially created
218
- # on every invocation: the aggregate method where statistics for all calls
219
- # of that metric are stored, and the "scoped metric" which records the
220
- # statistics for invocations in a particular scope--generally a controller
221
- # action. This option indicates that only the second type should be recorded.
222
- # The effect is similar to <tt>:metric => false</tt> but in addition you
223
- # will also see the invocation in breakdown pie charts.
224
- # * <tt>:deduct_call_time_from_parent => false</tt> indicates that the method invocation
225
- # time should never be deducted from the time reported as 'exclusive' in the
226
- # caller. You would want to use this if you are tracing a recursive method
227
- # or a method that might be called inside another traced method.
228
- # * <tt>:code_header</tt> and <tt>:code_footer</tt> specify ruby code that
229
- # is inserted into the tracer before and after the call.
230
- # * <tt>:force = true</tt> will ensure the metric is captured even if called inside
231
- # an untraced execution call. (See NewRelic::Agent#disable_all_tracing)
232
- #
233
- # === Overriding the metric name
234
- #
235
- # +metric_name_code+ is a string that is eval'd to get the
236
- # name of the metric associated with the call, so if you want to
237
- # use interpolaion evaluated at call time, then single quote
238
- # the value like this:
239
- #
240
- # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
241
- #
242
- # This would name the metric according to the class of the runtime
243
- # intance, as opposed to the class where +foo+ is defined.
244
- #
245
- # If not provided, the metric name will be <tt>Custom/ClassName/method_name</tt>.
246
- #
247
- # === Examples
248
- #
249
- # Instrument +foo+ only for custom views--will not show up in transaction traces or caller breakdown graphs:
250
- #
251
- # add_method_tracer :foo, :push_scope => false
252
- #
253
- # Instrument +foo+ just for transaction traces only:
254
- #
255
- # add_method_tracer :foo, :metric => false
256
- #
257
- # Instrument +foo+ so it shows up in transaction traces and caller breakdown graphs
258
- # for actions:
259
- #
260
- # add_method_tracer :foo
261
- #
262
- # which is equivalent to:
263
- #
264
- # add_method_tracer :foo, 'Custom/#{self.class.name}/foo', :push_scope => true, :metric => true
265
- #
266
- # Instrument the class method +foo+ with the metric name 'Custom/People/fetch':
267
- #
268
- # class << self
269
- # add_method_tracer :foo, 'Custom/People/fetch'
270
- # end
271
- #
272
-
255
+ # contains methods refactored out of the #add_method_tracer method
273
256
  module AddMethodTracer
274
257
  ALLOWED_KEYS = [:force, :metric, :push_scope, :deduct_call_time_from_parent, :code_header, :code_footer, :scoped_metric_only].freeze
275
-
258
+
259
+ # used to verify that the keys passed to
260
+ # NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer
261
+ # are valid. Returns a list of keys that were unexpected
276
262
  def unrecognized_keys(expected, given)
277
263
  given.keys - expected
278
264
  end
279
-
265
+
266
+ # used to verify that the keys passed to
267
+ # NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer
268
+ # are valid. checks the expected list against the list
269
+ # actually provided
280
270
  def any_unrecognized_keys?(expected, given)
281
271
  unrecognized_keys(expected, given).any?
282
272
  end
283
-
273
+
274
+ # raises an error when the
275
+ # NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer
276
+ # method is called with improper keys. This aids in
277
+ # debugging new instrumentation by failing fast
284
278
  def check_for_illegal_keys!(options)
285
279
  if any_unrecognized_keys?(ALLOWED_KEYS, options)
286
280
  raise "Unrecognized options in add_method_tracer_call: #{unrecognized_keys(ALLOWED_KEYS, options).join(', ')}"
287
281
  end
288
282
  end
289
-
283
+
284
+ # Sets the options for deducting call time from
285
+ # parents. This defaults to true if we are recording a metric, but
286
+ # can be overridden by the user if desired.
287
+ #
288
+ # has the effect of not allowing overlapping times, and
289
+ # should generally be true
290
290
  def set_deduct_call_time_based_on_metric(options)
291
291
  {:deduct_call_time_from_parent => !!options[:metric]}.merge(options)
292
292
  end
293
-
293
+
294
+ # validity checking - add_method_tracer must receive either
295
+ # push scope or metric, or else it would record no
296
+ # data. Raises an error if this is the case
294
297
  def check_for_push_scope_and_metric(options)
295
298
  unless options[:push_scope] || options[:metric]
296
299
  raise "Can't add a tracer where push_scope is false and metric is false"
@@ -298,7 +301,11 @@ module NewRelic
298
301
  end
299
302
 
300
303
  DEFAULT_SETTINGS = {:push_scope => true, :metric => true, :force => false, :code_header => "", :code_footer => "", :scoped_metric_only => false}.freeze
301
-
304
+
305
+ # Checks the provided options to make sure that they make
306
+ # sense. Raises an error if the options are incorrect to
307
+ # assist with debugging, so that errors occur at class
308
+ # construction time rather than instrumentation run time
302
309
  def validate_options(options)
303
310
  raise TypeError.new("provided options must be a Hash") unless options.is_a?(Hash)
304
311
  check_for_illegal_keys!(options)
@@ -308,28 +315,45 @@ module NewRelic
308
315
  end
309
316
 
310
317
  # Default to the class where the method is defined.
318
+ #
319
+ # Example:
320
+ # Foo.default_metric_name_code('bar') #=> "Custom/#{Foo.name}/bar"
311
321
  def default_metric_name_code(method_name)
312
322
  "Custom/#{self.name}/#{method_name.to_s}"
313
323
  end
314
-
324
+
325
+ # Checks to see if the method we are attempting to trace
326
+ # actually exists or not. #add_method_tracer can't do
327
+ # anything if the method doesn't exist.
315
328
  def newrelic_method_exists?(method_name)
316
329
  exists = method_defined?(method_name) || private_method_defined?(method_name)
317
330
  NewRelic::Control.instance.log.warn("Did not trace #{self.name}##{method_name} because that method does not exist") unless exists
318
331
  exists
319
332
  end
320
-
333
+
334
+ # Checks to see if we have already traced a method with a
335
+ # given metric by checking to see if the traced method
336
+ # exists. Warns the user if methods are being double-traced
337
+ # to help with debugging custom instrumentation.
321
338
  def traced_method_exists?(method_name, metric_name_code)
322
339
  exists = method_defined?(_traced_method_name(method_name, metric_name_code))
323
340
  NewRelic::Control.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}") if exists
324
341
  exists
325
342
  end
326
-
343
+
344
+ # Returns a code snippet to be eval'd that skips tracing
345
+ # when the agent is not tracing execution. turns
346
+ # instrumentation into effectively one method call overhead
347
+ # when the agent is disabled
327
348
  def assemble_code_header(method_name, metric_name_code, options)
328
349
  unless options[:force]
329
350
  "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless NewRelic::Agent.is_execution_traced?\n"
330
351
  end.to_s + options[:code_header].to_s
331
352
  end
332
-
353
+
354
+ # returns an eval-able string that contains the traced
355
+ # method code used if the agent is not creating a scope for
356
+ # use in scoped metrics.
333
357
  def method_without_push_scope(method_name, metric_name_code, options)
334
358
  "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
335
359
  #{assemble_code_header(method_name, metric_name_code, options)}
@@ -347,6 +371,8 @@ module NewRelic
347
371
  end"
348
372
  end
349
373
 
374
+ # returns an eval-able string that contains the tracing code
375
+ # for a fully traced metric including scoping
350
376
  def method_with_push_scope(method_name, metric_name_code, options)
351
377
  klass = (self === Module) ? "self" : "self.class"
352
378
 
@@ -363,7 +389,9 @@ module NewRelic
363
389
  result
364
390
  end"
365
391
  end
366
-
392
+
393
+ # Decides which code snippet we should be eval'ing in this
394
+ # context, based on the options.
367
395
  def code_to_eval(method_name, metric_name_code, options)
368
396
  options = validate_options(options)
369
397
  if options[:push_scope]
@@ -375,6 +403,77 @@ module NewRelic
375
403
  end
376
404
  include AddMethodTracer
377
405
 
406
+
407
+
408
+ # Add a method tracer to the specified method.
409
+ #
410
+ # === Common Options
411
+ #
412
+ # * <tt>:push_scope => false</tt> specifies this method tracer should not
413
+ # keep track of the caller; it will not show up in controller breakdown
414
+ # pie charts.
415
+ # * <tt>:metric => false</tt> specifies that no metric will be recorded.
416
+ # Instead the call will show up in transaction traces as well as traces
417
+ # shown in Developer Mode.
418
+ #
419
+ # === Uncommon Options
420
+ #
421
+ # * <tt>:scoped_metric_only => true</tt> indicates that the unscoped metric
422
+ # should not be recorded. Normally two metrics are potentially created
423
+ # on every invocation: the aggregate method where statistics for all calls
424
+ # of that metric are stored, and the "scoped metric" which records the
425
+ # statistics for invocations in a particular scope--generally a controller
426
+ # action. This option indicates that only the second type should be recorded.
427
+ # The effect is similar to <tt>:metric => false</tt> but in addition you
428
+ # will also see the invocation in breakdown pie charts.
429
+ # * <tt>:deduct_call_time_from_parent => false</tt> indicates that the method invocation
430
+ # time should never be deducted from the time reported as 'exclusive' in the
431
+ # caller. You would want to use this if you are tracing a recursive method
432
+ # or a method that might be called inside another traced method.
433
+ # * <tt>:code_header</tt> and <tt>:code_footer</tt> specify ruby code that
434
+ # is inserted into the tracer before and after the call.
435
+ # * <tt>:force = true</tt> will ensure the metric is captured even if called inside
436
+ # an untraced execution call. (See NewRelic::Agent#disable_all_tracing)
437
+ #
438
+ # === Overriding the metric name
439
+ #
440
+ # +metric_name_code+ is a string that is eval'd to get the
441
+ # name of the metric associated with the call, so if you want to
442
+ # use interpolaion evaluated at call time, then single quote
443
+ # the value like this:
444
+ #
445
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
446
+ #
447
+ # This would name the metric according to the class of the runtime
448
+ # intance, as opposed to the class where +foo+ is defined.
449
+ #
450
+ # If not provided, the metric name will be <tt>Custom/ClassName/method_name</tt>.
451
+ #
452
+ # === Examples
453
+ #
454
+ # Instrument +foo+ only for custom views--will not show up in transaction traces or caller breakdown graphs:
455
+ #
456
+ # add_method_tracer :foo, :push_scope => false
457
+ #
458
+ # Instrument +foo+ just for transaction traces only:
459
+ #
460
+ # add_method_tracer :foo, :metric => false
461
+ #
462
+ # Instrument +foo+ so it shows up in transaction traces and caller breakdown graphs
463
+ # for actions:
464
+ #
465
+ # add_method_tracer :foo
466
+ #
467
+ # which is equivalent to:
468
+ #
469
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo', :push_scope => true, :metric => true
470
+ #
471
+ # Instrument the class method +foo+ with the metric name 'Custom/People/fetch':
472
+ #
473
+ # class << self
474
+ # add_method_tracer :foo, 'Custom/People/fetch'
475
+ # end
476
+ #
378
477
  def add_method_tracer(method_name, metric_name_code=nil, options = {})
379
478
  return unless newrelic_method_exists?(method_name)
380
479
  metric_name_code ||= default_metric_name_code(method_name)
@@ -404,15 +503,22 @@ module NewRelic
404
503
  end
405
504
  end
406
505
  private
407
-
506
+
507
+ # given a method and a metric, this method returns the
508
+ # untraced alias of the method name
408
509
  def _untraced_method_name(method_name, metric_name)
409
510
  "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
410
511
  end
411
-
512
+
513
+ # given a method and a metric, this method returns the traced
514
+ # alias of the method name
412
515
  def _traced_method_name(method_name, metric_name)
413
516
  "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
414
517
  end
415
-
518
+
519
+ # makes sure that method names do not contain characters that
520
+ # might break the interpreter, for example ! or ? characters
521
+ # that are not allowed in the middle of method names
416
522
  def _sanitize_name(name)
417
523
  name.to_s.tr_s('^a-zA-Z0-9', '_')
418
524
  end