newrelic_rpm 2.13.4 → 2.13.5.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 (57) hide show
  1. data/CHANGELOG +9 -0
  2. data/lib/new_relic/agent.rb +2 -1
  3. data/lib/new_relic/agent/agent.rb +393 -204
  4. data/lib/new_relic/agent/error_collector.rb +113 -43
  5. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +14 -16
  6. data/lib/new_relic/agent/instrumentation/queue_time.rb +201 -0
  7. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +1 -1
  8. data/lib/new_relic/agent/instrumentation/sequel.rb +95 -0
  9. data/lib/new_relic/agent/method_tracer.rb +391 -313
  10. data/lib/new_relic/agent/samplers/cpu_sampler.rb +43 -41
  11. data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +2 -0
  12. data/lib/new_relic/agent/samplers/memory_sampler.rb +122 -120
  13. data/lib/new_relic/agent/samplers/object_sampler.rb +2 -0
  14. data/lib/new_relic/agent/stats_engine/metric_stats.rb +0 -1
  15. data/lib/new_relic/agent/stats_engine/samplers.rb +20 -14
  16. data/lib/new_relic/agent/stats_engine/transactions.rb +35 -7
  17. data/lib/new_relic/control.rb +12 -17
  18. data/lib/new_relic/control/configuration.rb +1 -0
  19. data/lib/new_relic/control/frameworks/rails.rb +7 -4
  20. data/lib/new_relic/control/frameworks/rails3.rb +1 -1
  21. data/lib/new_relic/control/instrumentation.rb +2 -18
  22. data/lib/new_relic/local_environment.rb +117 -59
  23. data/lib/new_relic/rack/developer_mode.rb +212 -207
  24. data/lib/new_relic/recipes.rb +0 -9
  25. data/lib/new_relic/stats.rb +87 -81
  26. data/lib/new_relic/transaction_analysis.rb +1 -1
  27. data/lib/new_relic/version.rb +2 -2
  28. data/lib/newrelic_rpm.rb +2 -3
  29. data/lib/tasks/tests.rake +5 -1
  30. data/newrelic_rpm.gemspec +14 -5
  31. data/test/config/test_control.rb +14 -2
  32. data/test/new_relic/agent/active_record_instrumentation_test.rb +342 -119
  33. data/test/new_relic/agent/add_method_tracer_test.rb +158 -0
  34. data/test/new_relic/agent/agent_connect_test.rb +295 -0
  35. data/test/new_relic/agent/agent_controller_test.rb +86 -18
  36. data/test/new_relic/agent/agent_start_test.rb +326 -0
  37. data/test/new_relic/agent/agent_start_worker_thread_test.rb +157 -0
  38. data/test/new_relic/agent/apdex_from_server_test.rb +9 -0
  39. data/test/new_relic/agent/collection_helper_test.rb +3 -1
  40. data/test/new_relic/agent/error_collector_notice_error_test.rb +255 -0
  41. data/test/new_relic/agent/error_collector_test.rb +6 -0
  42. data/test/new_relic/agent/method_tracer_test.rb +2 -2
  43. data/test/new_relic/agent/method_tracer_trace_execution_scoped_test.rb +233 -0
  44. data/test/new_relic/agent/net_instrumentation_test.rb +17 -12
  45. data/test/new_relic/agent/queue_time_test.rb +333 -0
  46. data/test/new_relic/agent/rpm_agent_test.rb +4 -2
  47. data/test/new_relic/agent/stats_engine/samplers_test.rb +27 -1
  48. data/test/new_relic/agent/transaction_sample_subtest_test.rb +56 -0
  49. data/test/new_relic/agent/transaction_sample_test.rb +103 -174
  50. data/test/new_relic/agent/transaction_sampler_test.rb +9 -2
  51. data/test/new_relic/control_test.rb +7 -2
  52. data/test/new_relic/metric_spec_test.rb +1 -1
  53. data/test/new_relic/stats_test.rb +112 -15
  54. data/test/test_helper.rb +79 -16
  55. data/ui/helpers/developer_mode_helper.rb +2 -0
  56. metadata +19 -7
  57. data/lib/new_relic_api.rb +0 -276
@@ -1,350 +1,428 @@
1
1
  require 'new_relic/control'
2
2
  module NewRelic
3
- module Agent
4
- # This module contains class methods added to support installing custom
5
- # metric tracers and executing for individual metrics.
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 An
27
- # def self.process
28
- # ...
29
- # end
30
- # class << self
31
- # include NewRelic::Agent::MethodTracer
32
- # add_method_tracer :process
33
- # end
34
- # end
35
-
36
- module MethodTracer
37
-
38
- def self.included clazz #:nodoc:
39
- clazz.extend ClassMethods
40
- clazz.send :include, InstanceMethods
41
- end
42
-
43
- def self.extended clazz #:nodoc:
44
- clazz.extend ClassMethods
45
- clazz.extend InstanceMethods
46
- end
3
+ module Agent
4
+ # This module contains class methods added to support installing custom
5
+ # metric tracers and executing for individual metrics.
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 An
27
+ # def self.process
28
+ # ...
29
+ # end
30
+ # class << self
31
+ # include NewRelic::Agent::MethodTracer
32
+ # add_method_tracer :process
33
+ # end
34
+ # end
47
35
 
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
36
+ module MethodTracer
59
37
 
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
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 - t0).to_f # for some reason this is 3 usec faster than Time - Time
80
- stats.each { |stat| stat.trace_call(duration) }
81
- end
38
+ def self.included clazz #:nodoc:
39
+ clazz.extend ClassMethods
40
+ clazz.send :include, InstanceMethods
82
41
  end
83
42
 
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)
43
+ def self.extended clazz #:nodoc:
44
+ clazz.extend ClassMethods
45
+ clazz.extend InstanceMethods
92
46
  end
93
47
 
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 NewRelic::Agent::MethodTracer::ClassMethods#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={})
107
-
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
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)]
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)
125
55
  else
126
- metric_stats = EMPTY_ARRAY
56
+ trace_execution_unscoped(metric_names, &block)
57
+ end
58
+ end
59
+
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
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 - t0).to_f # for some reason this is 3 usec faster than Time - Time
80
+ stats.each { |stat| stat.trace_call(duration) }
127
81
  end
128
82
  end
129
83
 
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.to_f, deduct_call_time_from_parent)
135
- rescue => e
136
- NewRelic::Control.instance.log.error("Caught exception in trace_method_execution header. Metric name = #{first_name}, exception = #{e}")
137
- NewRelic::Control.instance.log.error(e.backtrace.join("\n"))
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)
138
92
  end
139
93
 
140
- begin
141
- yield
142
- ensure
143
- t1 = Time.now
144
- duration = (t1 - t0).to_f
94
+ alias trace_method_execution_no_scope trace_execution_unscoped #:nodoc:
95
+
96
+ module TraceExecutionScoped
97
+ def agent_instance
98
+ NewRelic::Agent.instance
99
+ end
100
+
101
+ def traced?
102
+ NewRelic::Agent.is_execution_traced?
103
+ end
104
+
105
+ def trace_disabled?(options)
106
+ !(traced? || options[:force])
107
+ end
145
108
 
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.to_f
150
- exclusive = duration - scope.children_time
151
- metric_stats.each { |stats| stats.trace_call(duration, exclusive) }
109
+ def stat_engine
110
+ agent_instance.stats_engine
111
+ end
112
+
113
+ def get_stats_scoped(first_name, scoped_metric_only)
114
+ stat_engine.get_stats(first_name, true, scoped_metric_only)
115
+ end
116
+ def get_stats_unscoped(name)
117
+ stat_engine.get_stats_no_scope(name)
118
+ end
119
+
120
+ def main_stat(metric, options)
121
+ get_stats_scoped(metric, options[:scoped_metric_only])
122
+ end
123
+
124
+ def get_metric_stats(metrics, options)
125
+ metrics = Array(metrics)
126
+ first_name = metrics.shift
127
+ stats = metrics.map do | name |
128
+ get_stats_unscoped(name)
152
129
  end
130
+ stats.unshift(main_stat(first_name, options)) if options[:metric]
131
+ [first_name, stats]
132
+ end
133
+
134
+ def set_if_nil(hash, key)
135
+ hash[key] = true if hash[key].nil?
136
+ end
137
+
138
+ def push_flag!(forced)
139
+ agent_instance.push_trace_execution_flag(true) if forced
140
+ end
141
+
142
+ def pop_flag!(forced)
143
+ agent_instance.pop_trace_execution_flag if forced
144
+ end
145
+
146
+ def log_errors(code_area, metric)
147
+ yield
153
148
  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"))
149
+ log.error("Caught exception in #{code_area}. Metric name = #{metric}, exception = #{e}")
150
+ log.error(e.backtrace.join("\n"))
156
151
  end
157
- end
158
- end
159
- end
160
152
 
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
- #
231
-
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
153
+ def trace_execution_scoped_header(metric, options, t0=Time.now.to_f)
154
+ scope = log_errors("trace_execution_scoped header", metric) do
155
+ push_flag!(options[:force])
156
+ scope = stat_engine.push_scope(metric, t0, options[:deduct_call_time_from_parent])
157
+ end
158
+ # needed in case we have an error, above, to always return
159
+ # the start time.
160
+ [t0, scope]
161
+ end
265
162
 
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
163
+ def trace_execution_scoped_footer(t0, first_name, metric_stats, expected_scope, forced, t1=Time.now.to_f)
164
+ log_errors("trace_method_execution footer", first_name) do
165
+ duration = t1 - t0
166
+
167
+ pop_flag!(forced)
168
+ if expected_scope
169
+ scope = stat_engine.pop_scope(expected_scope, duration, t1)
170
+ exclusive = duration - scope.children_time
171
+ metric_stats.each { |stats| stats.trace_call(duration, exclusive) }
172
+ end
173
+ end
174
+ end
271
175
 
272
- fail "Can't add a tracer where push_scope is false and metric is false" if options[:push_scope] == false && !options[:metric]
176
+ # Trace a given block with stats and keep track of the caller.
177
+ # See NewRelic::Agent::MethodTracer::ClassMethods#add_method_tracer for a description of the arguments.
178
+ # +metric_names+ is either a single name or an array of metric names.
179
+ # If more than one metric is passed, the +produce_metric+ option only applies to the first. The
180
+ # others are always recorded. Only the first metric is pushed onto the scope stack.
181
+ #
182
+ # Generally you pass an array of metric names if you want to record the metric under additional
183
+ # categories, but generally this *should never ever be done*. Most of the time you can aggregate
184
+ # on the server.
273
185
 
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
281
- def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
282
- #{header}
283
- t0 = Time.now
284
- stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope "#{metric_name_code}"
285
- begin
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
288
- ensure
289
- #{"NewRelic::Agent.instance.pop_trace_execution_flag\n" if options[:force] }
290
- duration = (Time.now - t0).to_f
291
- stats.trace_call(duration)
292
- #{options[:code_footer]}
186
+ def trace_execution_scoped(metric_names, options={})
187
+ return yield if trace_disabled?(options)
188
+ set_if_nil(options, :metric)
189
+ set_if_nil(options, :deduct_call_time_from_parent)
190
+ first_name, metric_stats = get_metric_stats(metric_names, options)
191
+ start_time, expected_scope = trace_execution_scoped_header(first_name, options)
192
+ begin
193
+ yield
194
+ ensure
195
+ trace_execution_scoped_footer(start_time, first_name, metric_stats, expected_scope, options[:force])
196
+ end
293
197
  end
198
+
294
199
  end
295
- CODE
296
- else
297
- code = <<-CODE
298
- def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
299
- #{options[:code_header]}
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
305
- #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
306
- end
307
- #{options[:code_footer]}
308
- result
200
+ include TraceExecutionScoped
201
+
309
202
  end
310
- CODE
203
+
204
+ module ClassMethods
205
+ # Add a method tracer to the specified method.
206
+ #
207
+ # === Common Options
208
+ #
209
+ # * <tt>:push_scope => false</tt> specifies this method tracer should not
210
+ # keep track of the caller; it will not show up in controller breakdown
211
+ # pie charts.
212
+ # * <tt>:metric => false</tt> specifies that no metric will be recorded.
213
+ # Instead the call will show up in transaction traces as well as traces
214
+ # shown in Developer Mode.
215
+ #
216
+ # === Uncommon Options
217
+ #
218
+ # * <tt>:scoped_metric_only => true</tt> indicates that the unscoped metric
219
+ # should not be recorded. Normally two metrics are potentially created
220
+ # on every invocation: the aggregate method where statistics for all calls
221
+ # of that metric are stored, and the "scoped metric" which records the
222
+ # statistics for invocations in a particular scope--generally a controller
223
+ # action. This option indicates that only the second type should be recorded.
224
+ # The effect is similar to <tt>:metric => false</tt> but in addition you
225
+ # will also see the invocation in breakdown pie charts.
226
+ # * <tt>:deduct_call_time_from_parent => false</tt> indicates that the method invocation
227
+ # time should never be deducted from the time reported as 'exclusive' in the
228
+ # caller. You would want to use this if you are tracing a recursive method
229
+ # or a method that might be called inside another traced method.
230
+ # * <tt>:code_header</tt> and <tt>:code_footer</tt> specify ruby code that
231
+ # is inserted into the tracer before and after the call.
232
+ # * <tt>:force = true</tt> will ensure the metric is captured even if called inside
233
+ # an untraced execution call. (See NewRelic::Agent#disable_all_tracing)
234
+ #
235
+ # === Overriding the metric name
236
+ #
237
+ # +metric_name_code+ is a string that is eval'd to get the
238
+ # name of the metric associated with the call, so if you want to
239
+ # use interpolaion evaluated at call time, then single quote
240
+ # the value like this:
241
+ #
242
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo'
243
+ #
244
+ # This would name the metric according to the class of the runtime
245
+ # intance, as opposed to the class where +foo+ is defined.
246
+ #
247
+ # If not provided, the metric name will be <tt>Custom/ClassName/method_name</tt>.
248
+ #
249
+ # === Examples
250
+ #
251
+ # Instrument +foo+ only for custom views--will not show up in transaction traces or caller breakdown graphs:
252
+ #
253
+ # add_method_tracer :foo, :push_scope => false
254
+ #
255
+ # Instrument +foo+ just for transaction traces only:
256
+ #
257
+ # add_method_tracer :foo, :metric => false
258
+ #
259
+ # Instrument +foo+ so it shows up in transaction traces and caller breakdown graphs
260
+ # for actions:
261
+ #
262
+ # add_method_tracer :foo
263
+ #
264
+ # which is equivalent to:
265
+ #
266
+ # add_method_tracer :foo, 'Custom/#{self.class.name}/foo', :push_scope => true, :metric => true
267
+ #
268
+ # Instrument the class method +foo+ with the metric name 'Custom/People/fetch':
269
+ #
270
+ # class << self
271
+ # add_method_tracer :foo, 'Custom/People/fetch'
272
+ # end
273
+ #
274
+
275
+ module AddMethodTracer
276
+ ALLOWED_KEYS = [:force, :metric, :push_scope, :deduct_call_time_from_parent, :code_header, :code_footer, :scoped_metric_only].freeze
277
+
278
+ def unrecognized_keys(expected, given)
279
+ given.keys - expected
280
+ end
281
+
282
+ def any_unrecognized_keys?(expected, given)
283
+ unrecognized_keys(expected, given).any?
284
+ end
285
+
286
+ def check_for_illegal_keys!(options)
287
+ if any_unrecognized_keys?(ALLOWED_KEYS, options)
288
+ raise "Unrecognized options in add_method_tracer_call: #{unrecognized_keys(ALLOWED_KEYS, options).join(', ')}"
289
+ end
290
+ end
291
+
292
+ def set_deduct_call_time_based_on_metric(options)
293
+ {:deduct_call_time_from_parent => !!options[:metric]}.merge(options)
294
+ end
295
+
296
+ def check_for_push_scope_and_metric(options)
297
+ unless options[:push_scope] || options[:metric]
298
+ raise "Can't add a tracer where push_scope is false and metric is false"
299
+ end
300
+ end
301
+
302
+ DEFAULT_SETTINGS = {:push_scope => true, :metric => true, :force => false, :code_header => "", :code_footer => "", :scoped_metric_only => false}.freeze
303
+
304
+ def validate_options(options)
305
+ raise TypeError.new("provided options must be a Hash") unless options.is_a?(Hash)
306
+ check_for_illegal_keys!(options)
307
+ options = set_deduct_call_time_based_on_metric(DEFAULT_SETTINGS.merge(options))
308
+ check_for_push_scope_and_metric(options)
309
+ options
310
+ end
311
+
312
+ # Default to the class where the method is defined.
313
+ def default_metric_name_code(method_name)
314
+ "Custom/#{self.name}/#{method_name.to_s}"
315
+ end
316
+
317
+ def log
318
+ NewRelic::Control.instance.log
319
+ end
320
+
321
+ def method_exists?(method_name)
322
+ exists = method_defined?(method_name) || private_method_defined?(method_name)
323
+ log.warn("Did not trace #{self.name}##{method_name} because that method does not exist") unless exists
324
+ exists
325
+ end
326
+
327
+ def traced_method_exists?(method_name, metric_name_code)
328
+ exists = method_defined?(_traced_method_name(method_name, metric_name_code))
329
+ log.warn("Attempt to trace a method twice with the same metric: Method = #{method_name}, Metric Name = #{metric_name_code}") if exists
330
+ exists
331
+ end
332
+
333
+ def assemble_code_header(method_name, metric_name_code, options)
334
+ unless options[:force]
335
+ "return #{_untraced_method_name(method_name, metric_name_code)}(*args, &block) unless NewRelic::Agent.is_execution_traced?\n"
336
+ end.to_s + options[:code_header].to_s
337
+ end
338
+
339
+ def method_without_push_scope(method_name, metric_name_code, options)
340
+ "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
341
+ #{assemble_code_header(method_name, metric_name_code, options)}
342
+ t0 = Time.now
343
+ stats = NewRelic::Agent.instance.stats_engine.get_stats_no_scope \"#{metric_name_code}\"
344
+ begin
345
+ #{"NewRelic::Agent.instance.push_trace_execution_flag(true)\n" if options[:force]}
346
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)\n
347
+ ensure
348
+ #{"NewRelic::Agent.instance.pop_trace_execution_flag\n" if options[:force] }
349
+ duration = (Time.now - t0).to_f
350
+ stats.trace_call(duration)
351
+ #{options[:code_footer]}
352
+ end
353
+ end"
354
+ end
355
+
356
+ def method_with_push_scope(method_name, metric_name_code, options)
357
+ klass = (self === Module) ? "self" : "self.class"
358
+
359
+ "def #{_traced_method_name(method_name, metric_name_code)}(*args, &block)
360
+ #{options[:code_header]}
361
+ result = #{klass}.trace_execution_scoped(\"#{metric_name_code}\",
362
+ :metric => #{options[:metric]},
363
+ :forced => #{options[:force]},
364
+ :deduct_call_time_from_parent => #{options[:deduct_call_time_from_parent]},
365
+ :scoped_metric_only => #{options[:scoped_metric_only]}) do
366
+ #{_untraced_method_name(method_name, metric_name_code)}(*args, &block)
367
+ end
368
+ #{options[:code_footer]}
369
+ result
370
+ end"
371
+ end
372
+
373
+ def code_to_eval(method_name, metric_name_code, options)
374
+ options = validate_options(options)
375
+ if options[:push_scope]
376
+ method_with_push_scope(method_name, metric_name_code, options)
377
+ else
378
+ method_without_push_scope(method_name, metric_name_code, options)
379
+ end
380
+ end
311
381
  end
312
- class_eval code, __FILE__, __LINE__
382
+ include AddMethodTracer
313
383
 
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)
384
+ def add_method_tracer(method_name, metric_name_code=nil, options = {})
385
+ return unless method_exists?(method_name)
386
+ metric_name_code ||= default_metric_name_code(method_name)
387
+ return if traced_method_exists?(method_name, metric_name_code)
316
388
 
317
- NewRelic::Control.instance.log.debug("Traced method: class = #{self.name}, method = #{method_name}, "+
318
- "metric = '#{metric_name_code}'")
319
- end
389
+ traced_method = code_to_eval(method_name, metric_name_code, options)
390
+
391
+ class_eval traced_method, __FILE__, __LINE__
392
+ alias_method _untraced_method_name(method_name, metric_name_code), method_name
393
+ alias_method method_name, _traced_method_name(method_name, metric_name_code)
394
+ log.debug("Traced method: class = #{self.name},"+
395
+ "method = #{method_name}, "+
396
+ "metric = '#{metric_name_code}'")
397
+ end
398
+
399
+ # For tests only because tracers must be removed in reverse-order
400
+ # from when they were added, or else other tracers that were added to the same method
401
+ # may get removed as well.
402
+ def remove_method_tracer(method_name, metric_name_code) # :nodoc:
403
+ return unless NewRelic::Control.instance.agent_enabled?
320
404
 
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}'"
405
+ if method_defined? "#{_traced_method_name(method_name, metric_name_code)}"
406
+ alias_method method_name, "#{_untraced_method_name(method_name, metric_name_code)}"
407
+ undef_method "#{_traced_method_name(method_name, metric_name_code)}"
408
+ else
409
+ raise "No tracer for '#{metric_name_code}' on method '#{method_name}'"
410
+ end
332
411
  end
333
- end
334
- private
412
+ private
335
413
 
336
- def _untraced_method_name(method_name, metric_name)
337
- "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
338
- end
414
+ def _untraced_method_name(method_name, metric_name)
415
+ "#{_sanitize_name(method_name)}_without_trace_#{_sanitize_name(metric_name)}"
416
+ end
339
417
 
340
- def _traced_method_name(method_name, metric_name)
341
- "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
342
- end
418
+ def _traced_method_name(method_name, metric_name)
419
+ "#{_sanitize_name(method_name)}_with_trace_#{_sanitize_name(metric_name)}"
420
+ end
343
421
 
344
- def _sanitize_name(name)
345
- name.to_s.tr_s('^a-zA-Z0-9', '_')
422
+ def _sanitize_name(name)
423
+ name.to_s.tr_s('^a-zA-Z0-9', '_')
424
+ end
346
425
  end
347
426
  end
348
427
  end
349
428
  end
350
- end