newrelic_rpm 2.9.9 → 2.10.3

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 (120) hide show
  1. data/CHANGELOG +117 -49
  2. data/bin/mongrel_rpm +2 -2
  3. data/install.rb +42 -33
  4. data/lib/new_relic/agent.rb +149 -39
  5. data/lib/new_relic/agent/agent.rb +139 -122
  6. data/lib/new_relic/agent/busy_calculator.rb +91 -0
  7. data/lib/new_relic/agent/collection_helper.rb +11 -2
  8. data/lib/new_relic/agent/error_collector.rb +33 -27
  9. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +30 -26
  10. data/lib/new_relic/agent/instrumentation/authlogic.rb +8 -0
  11. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +316 -105
  12. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +22 -0
  13. data/lib/new_relic/agent/instrumentation/memcache.rb +18 -12
  14. data/lib/new_relic/agent/instrumentation/merb/errors.rb +2 -1
  15. data/lib/new_relic/agent/instrumentation/metric_frame.rb +258 -0
  16. data/lib/new_relic/agent/instrumentation/net.rb +7 -11
  17. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +1 -1
  18. data/lib/new_relic/agent/instrumentation/rack.rb +109 -0
  19. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +6 -5
  20. data/lib/new_relic/agent/instrumentation/rails/errors.rb +7 -7
  21. data/lib/new_relic/agent/instrumentation/sinatra.rb +46 -0
  22. data/lib/new_relic/agent/method_tracer.rb +305 -150
  23. data/lib/new_relic/agent/sampler.rb +34 -0
  24. data/lib/new_relic/agent/samplers/cpu_sampler.rb +11 -1
  25. data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +37 -0
  26. data/lib/new_relic/agent/samplers/memory_sampler.rb +22 -10
  27. data/lib/new_relic/agent/samplers/object_sampler.rb +24 -0
  28. data/lib/new_relic/agent/shim_agent.rb +10 -0
  29. data/lib/new_relic/agent/stats_engine.rb +16 -278
  30. data/lib/new_relic/agent/stats_engine/metric_stats.rb +118 -0
  31. data/lib/new_relic/agent/stats_engine/samplers.rb +81 -0
  32. data/lib/new_relic/agent/stats_engine/transactions.rb +149 -0
  33. data/lib/new_relic/agent/transaction_sampler.rb +73 -67
  34. data/lib/new_relic/agent/worker_loop.rb +69 -68
  35. data/lib/new_relic/commands/deployments.rb +4 -6
  36. data/lib/new_relic/control.rb +121 -60
  37. data/lib/new_relic/control/external.rb +13 -0
  38. data/lib/new_relic/control/merb.rb +2 -0
  39. data/lib/new_relic/control/rails.rb +16 -6
  40. data/lib/new_relic/control/ruby.rb +8 -5
  41. data/lib/new_relic/control/sinatra.rb +18 -0
  42. data/lib/new_relic/delayed_job_injection.rb +25 -0
  43. data/lib/new_relic/histogram.rb +89 -0
  44. data/lib/new_relic/local_environment.rb +64 -30
  45. data/lib/new_relic/metric_data.rb +15 -6
  46. data/lib/new_relic/metric_parser.rb +14 -1
  47. data/lib/new_relic/metric_parser/active_record.rb +14 -0
  48. data/lib/new_relic/metric_parser/controller.rb +5 -2
  49. data/lib/new_relic/metric_parser/external.rb +50 -0
  50. data/lib/new_relic/metric_parser/other_transaction.rb +15 -0
  51. data/lib/new_relic/metric_parser/web_frontend.rb +14 -0
  52. data/lib/new_relic/metric_spec.rb +39 -20
  53. data/lib/new_relic/metrics.rb +9 -7
  54. data/lib/new_relic/noticed_error.rb +6 -8
  55. data/lib/new_relic/rack/metric_app.rb +5 -4
  56. data/lib/new_relic/rack/{newrelic.ru → mongrel_rpm.ru} +4 -4
  57. data/lib/new_relic/rack/newrelic.yml +1 -0
  58. data/lib/new_relic/{rack.rb → rack_app.rb} +0 -0
  59. data/lib/new_relic/recipes.rb +1 -1
  60. data/lib/new_relic/stats.rb +40 -26
  61. data/lib/new_relic/transaction_analysis.rb +5 -2
  62. data/lib/new_relic/transaction_sample.rb +134 -55
  63. data/lib/new_relic/version.rb +27 -20
  64. data/lib/new_relic_api.rb +67 -47
  65. data/lib/newrelic_rpm.rb +5 -5
  66. data/lib/tasks/tests.rake +2 -0
  67. data/newrelic.yml +69 -29
  68. data/test/active_record_fixtures.rb +2 -2
  69. data/test/config/newrelic.yml +4 -7
  70. data/test/config/test_control.rb +1 -2
  71. data/test/new_relic/agent/active_record_instrumentation_test.rb +115 -31
  72. data/test/new_relic/agent/agent_controller_test.rb +274 -0
  73. data/test/new_relic/agent/agent_test_controller.rb +42 -6
  74. data/test/new_relic/agent/busy_calculator_test.rb +79 -0
  75. data/test/new_relic/agent/collection_helper_test.rb +10 -3
  76. data/test/new_relic/agent/error_collector_test.rb +35 -17
  77. data/test/new_relic/agent/method_tracer_test.rb +60 -20
  78. data/test/new_relic/agent/metric_data_test.rb +2 -2
  79. data/test/new_relic/agent/metric_frame_test.rb +51 -0
  80. data/test/new_relic/agent/net_instrumentation_test.rb +77 -0
  81. data/test/new_relic/agent/{agent_test.rb → rpm_agent_test.rb} +26 -5
  82. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +79 -0
  83. data/test/new_relic/{samplers_test.rb → agent/stats_engine/samplers_test.rb} +23 -22
  84. data/test/new_relic/agent/{stats_engine_test.rb → stats_engine/stats_engine_test.rb} +19 -101
  85. data/test/new_relic/agent/task_instrumentation_test.rb +176 -0
  86. data/test/new_relic/agent/transaction_sample_builder_test.rb +2 -2
  87. data/test/new_relic/agent/transaction_sample_test.rb +53 -38
  88. data/test/new_relic/agent/transaction_sampler_test.rb +101 -33
  89. data/test/new_relic/agent/worker_loop_test.rb +16 -14
  90. data/test/new_relic/control_test.rb +26 -13
  91. data/test/new_relic/metric_parser_test.rb +31 -1
  92. data/test/new_relic/metric_spec_test.rb +2 -2
  93. data/test/new_relic/stats_test.rb +0 -8
  94. data/test/new_relic/version_number_test.rb +31 -1
  95. data/test/test_helper.rb +37 -1
  96. data/ui/controllers/newrelic_controller.rb +19 -11
  97. data/ui/helpers/google_pie_chart.rb +5 -11
  98. data/ui/helpers/newrelic_helper.rb +40 -35
  99. data/ui/views/layouts/newrelic_default.rhtml +7 -7
  100. data/ui/views/newrelic/_sample.rhtml +5 -1
  101. data/ui/views/newrelic/images/new-relic-rpm-desktop.gif +0 -0
  102. data/ui/views/newrelic/images/textmate.png +0 -0
  103. data/ui/views/newrelic/index.rhtml +13 -1
  104. data/ui/views/newrelic/show_sample.rhtml +5 -2
  105. data/ui/views/newrelic/stylesheets/style.css +54 -3
  106. metadata +65 -145
  107. data/Manifest +0 -143
  108. data/Rakefile +0 -22
  109. data/init.rb +0 -38
  110. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +0 -127
  111. data/lib/new_relic/agent/instrumentation/error_instrumentation.rb +0 -14
  112. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +0 -13
  113. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +0 -38
  114. data/lib/new_relic/agent/patch_const_missing.rb +0 -125
  115. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +0 -22
  116. data/lib/new_relic/metric_parser/database.rb +0 -23
  117. data/newrelic_rpm.gemspec +0 -35
  118. data/test/new_relic/agent/classloader_patch_test.rb +0 -56
  119. data/test/new_relic/agent/controller_test.rb +0 -107
  120. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +0 -70
@@ -10,20 +10,21 @@ if defined? ActionController
10
10
 
11
11
  when /^2\.1\./ # Rails 2.1
12
12
  ActionView::PartialTemplate.class_eval do
13
- add_method_tracer :render, 'View/#{path_without_extension}.#{@view.template_format}.#{extension}/Partial'
13
+ add_method_tracer :render, 'View/#{path_without_extension[%r{^(/.*/)?(.*)$},2]}.#{@view.template_format}.#{extension}/Partial'
14
14
  end
15
+ # this is for template rendering, as opposed to partial rendering.
15
16
  ActionView::Template.class_eval do
16
- add_method_tracer :render, 'View/#{path_without_extension}.#{@view.template_format}.#{extension}/Rendering'
17
+ add_method_tracer :render, 'View/#{path_without_extension[%r{^(/.*/)?(.*)$},2]}.#{@view.template_format}.#{extension}/Rendering'
17
18
  end
18
19
 
19
20
  when /^2\./ # Rails 2.2-2.*
20
21
  ActionView::RenderablePartial.module_eval do
21
- add_method_tracer :render_partial, 'View/#{path}/Partial'
22
+ add_method_tracer :render_partial, 'View/#{path[%r{^(/.*/)?(.*)$},2]}/Partial'
22
23
  end
23
24
  ActionView::Template.class_eval do
24
- add_method_tracer :render, 'View/#{path}/Rendering'
25
+ add_method_tracer :render, 'View/#{path[%r{^(/.*/)?(.*)$},2]}/Rendering'
25
26
  end
26
- end
27
+ end unless NewRelic::Control.instance['disable_view_instrumentation']
27
28
 
28
29
  ActionController::Base.class_eval do
29
30
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
@@ -1,15 +1,16 @@
1
1
 
2
2
  ActionController::Base.class_eval do
3
3
 
4
- def newrelic_notice_error(exception)
5
- filtered_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
6
-
7
- NewRelic::Agent.agent.error_collector.notice_error(exception, request, newrelic_metric_path, filtered_params)
4
+ # Make a note of an exception associated with the currently executin
5
+ # controller action. Note that this used to be available on Object
6
+ # but we replaced that global method with NewRelic::Agent#notice_error.
7
+ # Use that one outside of controller actions.
8
+ def newrelic_notice_error(exception, custom_params = {})
9
+ NewRelic::Agent::Instrumentation::MetricFrame.notice_error exception, custom_params
8
10
  end
9
11
 
10
12
  def rescue_action_with_newrelic_trace(exception)
11
- newrelic_notice_error exception
12
-
13
+ NewRelic::Agent::Instrumentation::MetricFrame.notice_error exception
13
14
  rescue_action_without_newrelic_trace exception
14
15
  end
15
16
 
@@ -21,4 +22,3 @@ ActionController::Base.class_eval do
21
22
 
22
23
  end if defined? ActionController
23
24
 
24
- Object.send :include, NewRelic::Agent::Instrumentation::ErrorInstrumentation
@@ -0,0 +1,46 @@
1
+ if defined?(Sinatra::Base)
2
+ require 'new_relic/agent/instrumentation/controller_instrumentation'
3
+ module NewRelic
4
+ module Agent
5
+ module Instrumentation
6
+ # NewRelic instrumentation for Sinatra applications. Sinatra actions will
7
+ # appear in the UI similar to controller actions, and have breakdown charts
8
+ # and transaction traces.
9
+ #
10
+ # The actions in the UI will correspond to the pattern expression used
11
+ # to match them. HTTP operations are not distinguished. Multiple matches
12
+ # will all be tracked as separate actions.
13
+ module Sinatra
14
+
15
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
16
+
17
+ def route_eval_with_newrelic(&block_arg)
18
+ path = unescape(@request.path_info)
19
+ name = path
20
+ # Go through each route and look for a match
21
+ if routes = self.class.routes[@request.request_method]
22
+ routes.detect do |pattern, keys, conditions, block|
23
+ if block_arg.equal? block
24
+ name = pattern.source
25
+ end
26
+ end
27
+ end
28
+ # strip of leading ^ and / chars and trailing $ and /
29
+ name.gsub!(%r{^[/^]*(.*?)[/\$]*$}, '\1')
30
+ name = 'root' if name.empty?
31
+ perform_action_with_newrelic_trace(:category => :sinatra, :name => name) do
32
+ route_eval_without_newrelic(&block_arg)
33
+ end
34
+ end
35
+ end
36
+
37
+ ::Sinatra::Base.class_eval do
38
+ include NewRelic::Agent::Instrumentation::Sinatra
39
+ alias route_eval_without_newrelic route_eval
40
+ alias route_eval route_eval_with_newrelic
41
+ end
42
+
43
+ end
44
+ end
45
+ end
46
+ end
@@ -1,195 +1,350 @@
1
- module NewRelic::Agent
2
-
3
- # These are stubs for API methods installed when the agent is disabled.
4
-
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
13
-
14
- # These are the class methods added to support installing custom
1
+ require 'new_relic/control'
2
+ module NewRelic
3
+ module Agent
4
+ # This module contains class methods added to support installing custom
15
5
  # metric tracers and executing for individual metrics.
16
- # This module is included in class Module.
6
+ #
7
+ # == Examples
8
+ #
9
+ # When the agent initializes, it extends Module with these methods.
10
+ # However if you want to use the API in code that might get loaded
11
+ # before the agent is initialized you will need to require
12
+ # this file:
13
+ #
14
+ # require 'new_relic/agent/method_tracer'
15
+ # class A
16
+ # include NewRelic::Agent::MethodTracer
17
+ # def process
18
+ # ...
19
+ # end
20
+ # add_method_tracer :process
21
+ # end
22
+ #
23
+ # To instrument a class method:
24
+ #
25
+ # require 'new_relic/agent/method_tracer'
26
+ # class A
27
+ # extend NewRelic::Agent::MethodTracer
28
+ # def self.process
29
+ # ...
30
+ # end
31
+ # class << self
32
+ # add_method_tracer :process
33
+ # end
34
+ # end
35
+
17
36
  module MethodTracer
18
37
 
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
38
+ def self.included clazz #:nodoc:
39
+ clazz.extend ClassMethods
40
+ clazz.send :include, InstanceMethods
27
41
  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
36
-
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
42
+
43
+ def self.extended clazz #:nodoc:
44
+ clazz.extend ClassMethods
45
+ clazz.extend InstanceMethods
43
46
  end
44
47
 
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
48
+ module InstanceMethods
49
+ # Deprecated: original method preserved for API backward compatibility.
50
+ # Use either #trace_execution_scoped or #trace_execution_unscoped
51
+ def trace_method_execution(metric_names, push_scope, produce_metric, deduct_call_time_from_parent, &block) #:nodoc:
52
+ if push_scope
53
+ trace_execution_scoped(metric_names, :metric => produce_metric,
54
+ :deduct_call_time_from_parent => deduct_call_time_from_parent, &block)
55
+ else
56
+ trace_execution_unscoped(metric_names, &block)
57
+ end
58
+ end
49
59
 
50
- begin
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
56
- rescue => e
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"))
60
+ # Trace a given block with stats assigned to the given metric_name. It does not
61
+ # provide scoped measurements, meaning whatever is being traced will not 'blame the
62
+ # Controller'--that is to say appear in the breakdown chart.
63
+ # This is code is inlined in #add_method_tracer.
64
+ # * <tt>metric_names</tt> is a single name or an array of names of metrics
65
+ # * <tt>:force => true</tt> will force the metric to be captured even when
66
+ # tracing is disabled with NewRelic::Agent#disable_all_tracing
67
+ #
68
+ def trace_execution_unscoped(metric_names, options={})
69
+ return yield unless NewRelic::Agent.is_execution_traced?
70
+ t0 = Time.now.to_f
71
+ stats = Array(metric_names).map do | metric_name |
72
+ NewRelic::Agent.instance.stats_engine.get_stats_no_scope metric_name
73
+ end
74
+ begin
75
+ NewRelic::Agent.instance.push_trace_execution_flag(true) if options[:force]
76
+ yield
77
+ ensure
78
+ NewRelic::Agent.instance.pop_trace_execution_flag if options[:force]
79
+ duration = Time.now.to_f - t0 # for some reason this is 3 usec faster than Time - Time
80
+ stats.each { |stat| stat.trace_call(duration) }
81
+ end
82
+ end
83
+
84
+ EMPTY_ARRAY = [].freeze
85
+
86
+ # Deprecated. Use #trace_execution_scoped, a version with an options hash.
87
+ def trace_method_execution_with_scope(metric_names, produce_metric, deduct_call_time_from_parent, scoped_metric_only=false, &block) #:nodoc:
88
+ trace_execution_scoped(metric_names,
89
+ :metric => produce_metric,
90
+ :deduct_call_time_from_parent => deduct_call_time_from_parent,
91
+ :scoped_metric_only => scoped_metric_only, &block)
59
92
  end
60
93
 
61
- begin
62
- yield
63
- ensure
64
- t1 = Time.now.to_f
65
- duration = t1 - t0
94
+ alias trace_method_execution_no_scope trace_execution_unscoped #:nodoc:
95
+
96
+ # Trace a given block with stats and keep track of the caller.
97
+ # See #add_method_tracer for a description of the arguments.
98
+ # +metric_names+ is either a single name or an array of metric names.
99
+ # If more than one metric is passed, the +produce_metric+ option only applies to the first. The
100
+ # others are always recorded. Only the first metric is pushed onto the scope stack.
101
+ #
102
+ # Generally you pass an array of metric names if you want to record the metric under additional
103
+ # categories, but generally this *should never ever be done*. Most of the time you can aggregate
104
+ # on the server.
105
+
106
+ def trace_execution_scoped(metric_names, options={})
66
107
 
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
108
+ return yield unless NewRelic::Agent.is_execution_traced? || options[:force]
109
+
110
+ produce_metric = options[:metric] != false
111
+ deduct_call_time_from_parent = options[:deduct_call_time_from_parent] != false
112
+ scoped_metric_only = produce_metric && options[:scoped_metric_only]
113
+ t0 = Time.now.to_f
114
+ if metric_names.instance_of? Array
115
+ first_name = metric_names.first
116
+ metric_stats = []
117
+ metric_stats << NewRelic::Agent.instance.stats_engine.get_stats(first_name, true, scoped_metric_only) if produce_metric
118
+ metric_names[1..-1].each do | name |
119
+ metric_stats << NewRelic::Agent.instance.stats_engine.get_stats_no_scope(name)
120
+ end
121
+ else
122
+ first_name = metric_names
123
+ if produce_metric
124
+ metric_stats = [NewRelic::Agent.instance.stats_engine.get_stats(first_name, true, scoped_metric_only)]
125
+ else
126
+ metric_stats = EMPTY_ARRAY
73
127
  end
128
+ end
129
+
130
+ begin
131
+ # Keep a reference to the scope we are pushing so we can do a sanity check making
132
+ # sure when we pop we get the one we 'expected'
133
+ NewRelic::Agent.instance.push_trace_execution_flag(true) if options[:force]
134
+ expected_scope = NewRelic::Agent.instance.stats_engine.push_scope(first_name, t0, deduct_call_time_from_parent)
74
135
  rescue => e
75
- NewRelic::Control.instance.log.error("Caught exception in trace_method_execution footer. Metric name = #{metric_name}, exception = #{e}")
136
+ NewRelic::Control.instance.log.error("Caught exception in trace_method_execution header. Metric name = #{first_name}, exception = #{e}")
76
137
  NewRelic::Control.instance.log.error(e.backtrace.join("\n"))
77
138
  end
139
+
140
+ begin
141
+ yield
142
+ ensure
143
+ t1 = Time.now.to_f
144
+ duration = t1 - t0
145
+
146
+ begin
147
+ NewRelic::Agent.instance.pop_trace_execution_flag if options[:force]
148
+ if expected_scope
149
+ scope = NewRelic::Agent.instance.stats_engine.pop_scope expected_scope, duration, t1
150
+ exclusive = duration - scope.children_time
151
+ metric_stats.each { |stats| stats.trace_call(duration, exclusive) }
152
+ end
153
+ rescue => e
154
+ NewRelic::Control.instance.log.error("Caught exception in trace_method_execution footer. Metric name = #{first_name}, exception = #{e}")
155
+ NewRelic::Control.instance.log.error(e.backtrace.join("\n"))
156
+ end
157
+ end
78
158
  end
79
159
  end
80
160
 
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]
161
+ module ClassMethods
162
+ # Add a method tracer to the specified method.
163
+ #
164
+ # === Common Options
165
+ #
166
+ # * <tt>:push_scope => false</tt> specifies this method tracer should not
167
+ # keep track of the caller; it will not show up in controller breakdown
168
+ # pie charts.
169
+ # * <tt>:metric => false</tt> specifies that no metric will be recorded.
170
+ # Instead the call will show up in transaction traces as well as traces
171
+ # shown in Developer Mode.
172
+ #
173
+ # === Uncommon Options
174
+ #
175
+ # * <tt>:scoped_metric_only => true</tt> indicates that the unscoped metric
176
+ # should not be recorded. Normally two metrics are potentially created
177
+ # on every invocation: the aggregate method where statistics for all calls
178
+ # of that metric are stored, and the "scoped metric" which records the
179
+ # statistics for invocations in a particular scope--generally a controller
180
+ # action. This option indicates that only the second type should be recorded.
181
+ # The effect is similar to <tt>:metric => false</tt> but in addition you
182
+ # will also see the invocation in breakdown pie charts.
183
+ # * <tt>:deduct_call_time_from_parent => false</tt> indicates that the method invocation
184
+ # time should never be deducted from the time reported as 'exclusive' in the
185
+ # caller. You would want to use this if you are tracing a recursive method
186
+ # or a method that might be called inside another traced method.
187
+ # * <tt>:code_header</tt> and <tt>:code_footer</tt> specify ruby code that
188
+ # is inserted into the tracer before and after the call.
189
+ # * <tt>:force = true</tt> will ensure the metric is captured even if called inside
190
+ # an untraced execution call. (See NewRelic::Agent#disable_all_tracing)
191
+ #
192
+ # === Overriding the metric name
193
+ #
194
+ # +metric_name_code+ is a string that is eval'd to get the
195
+ # name of the metric associated with the call, so if you want to
196
+ # use interpolaion evaluated at call time, then single quote
197
+ # the value like this:
198
+ #
199
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
200
+ #
201
+ # This would name the metric according to the class of the runtime
202
+ # intance, as opposed to the class where +foo+ is defined.
203
+ #
204
+ # If not provided, the metric name will be <tt>Custom/ClassName/method_name</tt>.
205
+ #
206
+ # === Examples
207
+ #
208
+ # Instrument +foo+ only for custom views--will not show up in transaction traces or caller breakdown graphs:
209
+ #
210
+ # add_method_tracer :foo, :push_scope => false
211
+ #
212
+ # Instrument +foo+ just for transaction traces only:
213
+ #
214
+ # add_method_tracer :foo, :metric => false
215
+ #
216
+ # Instrument +foo+ so it shows up in transaction traces and caller breakdown graphs
217
+ # for actions:
218
+ #
219
+ # add_method_tracer :foo
220
+ #
221
+ # which is equivalent to:
222
+ #
223
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo', :push_scope => true, :metric => true
224
+ #
225
+ # Instrument the class method +foo+ with the metric name 'Custom/People/fetch':
226
+ #
227
+ # class << self
228
+ # add_method_tracer :foo, 'Custom/People/fetch'
229
+ # end
230
+ #
127
231
 
128
- if options[:push_scope] == false
129
- code = <<-CODE
232
+ def add_method_tracer(method_name, metric_name_code=nil, options = {})
233
+ # for backward compatibility:
234
+ if !options.is_a?(Hash)
235
+ options = {:push_scope => options}
236
+ end
237
+ # in case they omit the metric name code
238
+ if metric_name_code.is_a?(Hash)
239
+ options.merge(metric_name_code)
240
+ end
241
+ if (unrecognized = options.keys - [:force, :metric, :push_scope, :deduct_call_time_from_parent, :code_header, :code_footer, :scoped_metric_only]).any?
242
+ fail "Unrecognized options in add_method_tracer_call: #{unrecognized.join(', ')}"
243
+ end
244
+ # options[:push_scope] true if we are noting the scope of this for
245
+ # stats collection as well as the transaction tracing
246
+ options[:push_scope] = true if options[:push_scope].nil?
247
+ # options[:metric] true if you are tracking stats for a metric, otherwise
248
+ # it's just for transaction tracing.
249
+ options[:metric] = true if options[:metric].nil?
250
+ options[:force] = false if options[:force].nil?
251
+ options[:deduct_call_time_from_parent] = false if options[:deduct_call_time_from_parent].nil? && !options[:metric]
252
+ options[:deduct_call_time_from_parent] = true if options[:deduct_call_time_from_parent].nil?
253
+ options[:code_header] ||= ""
254
+ options[:code_footer] ||= ""
255
+ options[:scoped_metric_only] ||= false
256
+
257
+ klass = (self === Module) ? "self" : "self.class"
258
+ # Default to the class where the method is defined.
259
+ metric_name_code = "Custom/#{self.name}/#{method_name.to_s}" unless metric_name_code
260
+
261
+ unless method_defined?(method_name) || private_method_defined?(method_name)
262
+ NewRelic::Control.instance.log.warn("Did not trace #{self.name}##{method_name} because that method does not exist")
263
+ return
264
+ end
265
+
266
+ traced_method_name = _traced_method_name(method_name, metric_name_code)
267
+ if method_defined? traced_method_name
268
+ NewRelic::Control.instance.log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}")
269
+ return
270
+ end
271
+
272
+ fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
273
+
274
+ header = ""
275
+ if !options[:force]
276
+ header << "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless NewRelic::Agent.is_execution_traced?\n"
277
+ end
278
+ header << options[:code_header] if options[:code_header]
279
+ if options[:push_scope] == false
280
+ code = <<-CODE
130
281
  def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
131
- #{options[:code_header]}
132
-
282
+ #{header}
133
283
  t0 = Time.now.to_f
134
284
  stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope "#{metric_name_code}"
135
-
136
285
  begin
137
- #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
286
+ #{"NewRelic::Agent.instance.push_trace_execution_flag(true)\n" if options[:force]}
287
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)\n
138
288
  ensure
289
+ #{"NewRelic::Agent.instance.pop_trace_execution_flag\n" if options[:force] }
139
290
  duration = Time.now.to_f - t0
140
- stats.trace_call(duration, duration) # for some reason this is 3 usec faster than Time - Time
291
+ stats.trace_call(duration)
141
292
  #{options[:code_footer]}
142
293
  end
143
294
  end
144
295
  CODE
145
- else
146
- code = <<-CODE
296
+ else
297
+ code = <<-CODE
147
298
  def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
148
299
  #{options[:code_header]}
149
- result = #{klass}.trace_method_execution_with_scope("#{metric_name_code}", #{options[:metric]}, #{options[:deduct_call_time_from_parent]}, #{options[:scoped_metric_only]}) do
300
+ result = #{klass}.trace_execution_scoped("#{metric_name_code}",
301
+ :metric => #{options[:metric]},
302
+ :forced => #{options[:force]},
303
+ :deduct_call_time_from_parent => #{options[:deduct_call_time_from_parent]},
304
+ :scoped_metric_only => #{options[:scoped_metric_only]}) do
150
305
  #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
151
306
  end
152
307
  #{options[:code_footer]}
153
308
  result
154
309
  end
155
310
  CODE
311
+ end
312
+ class_eval code, __FILE__, __LINE__
313
+
314
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
315
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
316
+
317
+ NewRelic::Control.instance.log.debug("Traced method: class = #{self.name}, method = #{method_name}, "+
318
+ "metric = '#{metric_name_code}', options: #{options.inspect}")
156
319
  end
157
320
 
158
- class_eval code, __FILE__, __LINE__
321
+ # For tests only because tracers must be removed in reverse-order
322
+ # from when they were added, or else other tracers that were added to the same method
323
+ # may get removed as well.
324
+ def remove_method_tracer(method_name, metric_name_code) # :nodoc:
325
+ return unless NewRelic::Control.instance.agent_enabled?
326
+
327
+ if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
328
+ alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
329
+ undef_method "#{_traced_method_name(method_name, metric_name_code)}"
330
+ else
331
+ raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
332
+ end
333
+ end
334
+ private
159
335
 
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)
336
+ def _untraced_method_name(method_name, metric_name)
337
+ "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
338
+ end
162
339
 
163
- NewRelic::Control.instance.log.debug("Traced method: class = #{self}, method = #{method_name}, "+
164
- "metric = '#{metric_name_code}', options: #{options.inspect}, ")
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?
340
+ def _traced_method_name(method_name, metric_name)
341
+ "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
342
+ end
172
343
 
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}'"
344
+ def _sanitize_name(name)
345
+ name.to_s.tr_s('^a-zA-Z0-9', '_')
178
346
  end
179
347
  end
180
-
181
- private
182
-
183
- def _untraced_method_name(method_name, metric_name)
184
- "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
185
- end
186
-
187
- def _traced_method_name(method_name, metric_name)
188
- "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
189
- end
190
-
191
- def _sanitize_name(name)
192
- name.to_s.tr('^a-z,A-Z,0-9', '_')
193
- end
194
348
  end
195
349
  end
350
+ end