ghazel-newrelic_rpm 3.1.0.1 → 3.4.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. data/CHANGELOG +120 -35
  2. data/LICENSE +29 -2
  3. data/README.rdoc +2 -2
  4. data/bin/mongrel_rpm +0 -0
  5. data/bin/newrelic +0 -0
  6. data/bin/newrelic_cmd +0 -0
  7. data/lib/new_relic/agent.rb +50 -38
  8. data/lib/new_relic/agent/agent.rb +459 -337
  9. data/lib/new_relic/agent/beacon_configuration.rb +71 -11
  10. data/lib/new_relic/agent/browser_monitoring.rb +73 -14
  11. data/lib/new_relic/agent/busy_calculator.rb +11 -3
  12. data/lib/new_relic/agent/chained_call.rb +2 -2
  13. data/lib/new_relic/agent/database.rb +223 -0
  14. data/lib/new_relic/agent/error_collector.rb +231 -183
  15. data/lib/new_relic/agent/instrumentation.rb +2 -2
  16. data/lib/new_relic/agent/instrumentation/active_merchant.rb +10 -2
  17. data/lib/new_relic/agent/instrumentation/active_record.rb +138 -0
  18. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +7 -1
  19. data/lib/new_relic/agent/instrumentation/authlogic.rb +6 -0
  20. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +46 -14
  21. data/lib/new_relic/agent/instrumentation/data_mapper.rb +8 -2
  22. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +11 -3
  23. data/lib/new_relic/agent/instrumentation/memcache.rb +49 -25
  24. data/lib/new_relic/agent/instrumentation/merb/controller.rb +7 -2
  25. data/lib/new_relic/agent/instrumentation/merb/errors.rb +7 -1
  26. data/lib/new_relic/agent/instrumentation/metric_frame.rb +31 -4
  27. data/lib/new_relic/agent/instrumentation/metric_frame/pop.rb +1 -5
  28. data/lib/new_relic/agent/instrumentation/net.rb +8 -2
  29. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +5 -2
  30. data/lib/new_relic/agent/instrumentation/queue_time.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +66 -35
  32. data/lib/new_relic/agent/instrumentation/rails/action_web_service.rb +7 -1
  33. data/lib/new_relic/agent/instrumentation/rails/errors.rb +7 -1
  34. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +121 -1
  35. data/lib/new_relic/agent/instrumentation/rails3/errors.rb +7 -1
  36. data/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb +21 -0
  37. data/lib/new_relic/agent/instrumentation/resque.rb +80 -0
  38. data/lib/new_relic/agent/instrumentation/sinatra.rb +46 -20
  39. data/lib/new_relic/agent/instrumentation/sunspot.rb +6 -0
  40. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +7 -2
  41. data/lib/new_relic/agent/method_tracer.rb +205 -99
  42. data/lib/new_relic/agent/new_relic_service.rb +221 -0
  43. data/lib/new_relic/agent/pipe_channel_manager.rb +161 -0
  44. data/lib/new_relic/agent/pipe_service.rb +54 -0
  45. data/lib/new_relic/agent/samplers/delayed_job_sampler.rb +89 -0
  46. data/lib/new_relic/agent/samplers/memory_sampler.rb +6 -7
  47. data/lib/new_relic/agent/shim_agent.rb +5 -5
  48. data/lib/new_relic/agent/sql_sampler.rb +282 -0
  49. data/lib/new_relic/agent/stats_engine.rb +2 -0
  50. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +123 -0
  51. data/lib/new_relic/agent/stats_engine/metric_stats.rb +35 -30
  52. data/lib/new_relic/agent/stats_engine/samplers.rb +10 -4
  53. data/lib/new_relic/agent/stats_engine/transactions.rb +28 -87
  54. data/lib/new_relic/agent/transaction_info.rb +74 -0
  55. data/lib/new_relic/agent/transaction_sample_builder.rb +18 -3
  56. data/lib/new_relic/agent/transaction_sampler.rb +108 -20
  57. data/lib/new_relic/agent/worker_loop.rb +14 -6
  58. data/lib/new_relic/collection_helper.rb +19 -11
  59. data/lib/new_relic/command.rb +1 -1
  60. data/lib/new_relic/commands/deployments.rb +2 -2
  61. data/lib/new_relic/commands/install.rb +2 -13
  62. data/lib/new_relic/control.rb +2 -3
  63. data/lib/new_relic/control/class_methods.rb +12 -6
  64. data/lib/new_relic/control/configuration.rb +57 -8
  65. data/lib/new_relic/control/frameworks.rb +10 -0
  66. data/lib/new_relic/control/frameworks/external.rb +4 -4
  67. data/lib/new_relic/control/frameworks/merb.rb +2 -1
  68. data/lib/new_relic/control/frameworks/rails.rb +35 -22
  69. data/lib/new_relic/control/frameworks/rails3.rb +12 -7
  70. data/lib/new_relic/control/frameworks/ruby.rb +5 -5
  71. data/lib/new_relic/control/frameworks/sinatra.rb +1 -4
  72. data/lib/new_relic/control/instance_methods.rb +38 -12
  73. data/lib/new_relic/control/instrumentation.rb +23 -4
  74. data/lib/new_relic/control/logging_methods.rb +70 -15
  75. data/lib/new_relic/control/server_methods.rb +22 -9
  76. data/lib/new_relic/delayed_job_injection.rb +16 -3
  77. data/lib/new_relic/helper.rb +21 -0
  78. data/lib/new_relic/language_support.rb +95 -0
  79. data/lib/new_relic/local_environment.rb +92 -48
  80. data/lib/new_relic/metric_data.rb +7 -2
  81. data/lib/new_relic/metric_spec.rb +12 -9
  82. data/lib/new_relic/noticed_error.rb +6 -1
  83. data/lib/new_relic/rack/browser_monitoring.rb +18 -19
  84. data/lib/new_relic/rack/developer_mode.rb +3 -2
  85. data/lib/new_relic/recipes.rb +8 -4
  86. data/lib/new_relic/stats.rb +17 -60
  87. data/lib/new_relic/transaction_analysis.rb +2 -1
  88. data/lib/new_relic/transaction_analysis/segment_summary.rb +4 -2
  89. data/lib/new_relic/transaction_sample.rb +60 -75
  90. data/lib/new_relic/transaction_sample/segment.rb +31 -79
  91. data/lib/new_relic/version.rb +2 -2
  92. data/lib/newrelic_rpm.rb +1 -1
  93. data/newrelic.yml +2 -2
  94. data/newrelic_rpm.gemspec +46 -54
  95. data/test/active_record_fixtures.rb +3 -3
  96. data/test/config/newrelic.yml +1 -1
  97. data/test/fixtures/proc_cpuinfo.txt +575 -0
  98. data/test/new_relic/agent/agent/connect_test.rb +128 -25
  99. data/test/new_relic/agent/agent/start_test.rb +9 -94
  100. data/test/new_relic/agent/agent/start_worker_thread_test.rb +2 -4
  101. data/test/new_relic/agent/agent_test.rb +51 -78
  102. data/test/new_relic/agent/agent_test_controller.rb +1 -1
  103. data/test/new_relic/agent/agent_test_controller_test.rb +49 -33
  104. data/test/new_relic/agent/beacon_configuration_test.rb +12 -5
  105. data/test/new_relic/agent/browser_monitoring_test.rb +99 -50
  106. data/test/new_relic/agent/database_test.rb +161 -0
  107. data/test/new_relic/agent/error_collector_test.rb +47 -23
  108. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +96 -42
  109. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +0 -2
  110. data/test/new_relic/agent/instrumentation/instrumentation_test.rb +1 -1
  111. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +3 -11
  112. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +9 -9
  113. data/test/new_relic/agent/instrumentation/queue_time_test.rb +6 -11
  114. data/test/new_relic/agent/memcache_instrumentation_test.rb +54 -18
  115. data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +1 -1
  116. data/test/new_relic/agent/method_tracer/instance_methods/trace_execution_scoped_test.rb +1 -1
  117. data/test/new_relic/agent/method_tracer_test.rb +3 -2
  118. data/test/new_relic/agent/new_relic_service_test.rb +151 -0
  119. data/test/new_relic/agent/pipe_channel_manager_test.rb +114 -0
  120. data/test/new_relic/agent/pipe_service_test.rb +113 -0
  121. data/test/new_relic/agent/rpm_agent_test.rb +4 -31
  122. data/test/new_relic/agent/sql_sampler_test.rb +192 -0
  123. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +19 -18
  124. data/test/new_relic/agent/stats_engine_test.rb +41 -6
  125. data/test/new_relic/agent/transaction_info_test.rb +13 -0
  126. data/test/new_relic/agent/transaction_sample_builder_test.rb +27 -4
  127. data/test/new_relic/agent/transaction_sampler_test.rb +68 -46
  128. data/test/new_relic/agent/worker_loop_test.rb +3 -3
  129. data/test/new_relic/agent_test.rb +242 -0
  130. data/test/new_relic/collection_helper_test.rb +50 -28
  131. data/test/new_relic/control/configuration_test.rb +77 -0
  132. data/test/new_relic/control/logging_methods_test.rb +49 -21
  133. data/test/new_relic/control_test.rb +115 -54
  134. data/test/new_relic/delayed_job_injection_test.rb +21 -0
  135. data/test/new_relic/fake_collector.rb +210 -0
  136. data/test/new_relic/fake_service.rb +44 -0
  137. data/test/new_relic/local_environment_test.rb +14 -1
  138. data/test/new_relic/metric_parser/metric_parser_test.rb +11 -0
  139. data/test/new_relic/rack/browser_monitoring_test.rb +84 -23
  140. data/test/new_relic/rack/developer_mode_helper_test.rb +141 -0
  141. data/test/new_relic/rack/developer_mode_test.rb +31 -0
  142. data/test/new_relic/stats_test.rb +3 -18
  143. data/test/new_relic/transaction_analysis/segment_summary_test.rb +14 -0
  144. data/test/new_relic/transaction_analysis_test.rb +3 -3
  145. data/test/new_relic/transaction_sample/segment_test.rb +15 -80
  146. data/test/new_relic/transaction_sample_test.rb +25 -18
  147. data/test/script/build_test_gem.sh +51 -0
  148. data/test/script/ci.sh +140 -0
  149. data/test/script/ci_agent-tests_runner.sh +82 -0
  150. data/test/script/ci_bench.sh +52 -0
  151. data/test/script/ci_multiverse_runner.sh +63 -0
  152. data/test/test_contexts.rb +1 -0
  153. data/test/test_helper.rb +18 -5
  154. data/ui/helpers/developer_mode_helper.rb +14 -8
  155. data/ui/helpers/google_pie_chart.rb +0 -1
  156. data/ui/views/newrelic/index.rhtml +2 -2
  157. data/vendor/gems/dependency_detection-0.0.1.build/LICENSE +4 -18
  158. data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +10 -0
  159. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/mem_cache.rb +11 -11
  160. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +17 -4
  161. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/view.rb +4 -0
  162. metadata +50 -36
  163. data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +0 -108
  164. data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +0 -112
  165. data/lib/new_relic/agent/samplers/delayed_job_lock_sampler.rb +0 -40
  166. data/lib/new_relic/data_serialization.rb +0 -84
  167. data/lib/new_relic/histogram.rb +0 -91
  168. data/lib/new_relic/rack/metric_app.rb +0 -65
  169. data/lib/new_relic/rack/mongrel_rpm.ru +0 -28
  170. data/lib/new_relic/rack/newrelic.yml +0 -27
  171. data/lib/new_relic/rack_app.rb +0 -6
  172. data/test/new_relic/data_serialization_test.rb +0 -70
  173. data/vendor/gems/dependency_detection-0.0.1.build/README +0 -0
  174. data/vendor/gems/metric_parser-0.1.0.pre1/LICENSE +0 -0
  175. data/vendor/gems/metric_parser-0.1.0.pre1/README +0 -0
@@ -15,14 +15,20 @@ module NewRelic
15
15
  end
16
16
 
17
17
  DependencyDetection.defer do
18
+ @name = :rails3_error
19
+
18
20
  depends_on do
19
- defined?(Rails) && Rails.respond_to?(:version) && Rails.version.to_i == 3
21
+ defined?(::Rails) && ::Rails.respond_to?(:version) && ::Rails.version.to_i == 3
20
22
  end
21
23
 
22
24
  depends_on do
23
25
  defined?(ActionController) && defined?(ActionController::Base)
24
26
  end
25
27
 
28
+ executes do
29
+ NewRelic::Agent.logger.debug 'Installing Rails3 Error instrumentation'
30
+ end
31
+
26
32
  executes do
27
33
  class ActionController::Base
28
34
  include NewRelic::Agent::Instrumentation::Rails3::Errors
@@ -0,0 +1,21 @@
1
+ DependencyDetection.defer do
2
+ @name = :rainbows
3
+
4
+ depends_on do
5
+ defined?(::Rainbows) && defined?(::Rainbows::HttpServer)
6
+ end
7
+
8
+ executes do
9
+ NewRelic::Agent.logger.debug 'Installing Rainbows instrumentation'
10
+ end
11
+
12
+ executes do
13
+ Rainbows::HttpServer.class_eval do
14
+ old_worker_loop = instance_method(:worker_loop)
15
+ define_method(:worker_loop) do | worker |
16
+ NewRelic::Agent.after_fork(:force_reconnect => true)
17
+ old_worker_loop.bind(self).call(worker)
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,80 @@
1
+ DependencyDetection.defer do
2
+ @name = :resque
3
+
4
+ depends_on do
5
+ defined?(::Resque::Job) && !NewRelic::Control.instance['disable_resque'] &&
6
+ !NewRelic::LanguageSupport.using_version?('1.9.1')
7
+ end
8
+
9
+ executes do
10
+ NewRelic::Agent.logger.debug 'Installing Resque instrumentation'
11
+ end
12
+
13
+ executes do
14
+ # == Resque Instrumentation
15
+ #
16
+ # Installs a hook to ensure the agent starts manually when the worker
17
+ # starts and also adds the tracer to the process method which executes
18
+ # in the forked task.
19
+
20
+ module Resque
21
+ module Plugins
22
+ module NewRelicInstrumentation
23
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation
24
+
25
+ def around_perform_with_monitoring(*args)
26
+ begin
27
+ perform_action_with_newrelic_trace(:name => 'perform',
28
+ :class_name => self.name,
29
+ :category => 'OtherTransaction/ResqueJob') do
30
+ yield(*args)
31
+ end
32
+ ensure
33
+ NewRelic::Agent.shutdown if NewRelic::LanguageSupport.can_fork?
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ module NewRelic
41
+ module Agent
42
+ module Instrumentation
43
+ module ResqueInstrumentationInstaller
44
+ def payload_class
45
+ klass = super
46
+ klass.instance_eval do
47
+ extend ::Resque::Plugins::NewRelicInstrumentation
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ ::Resque::Job.class_eval do
56
+ def self.new(*args)
57
+ super(*args).extend NewRelic::Agent::Instrumentation::ResqueInstrumentationInstaller
58
+ end
59
+ end
60
+
61
+ if NewRelic::LanguageSupport.can_fork?
62
+ ::Resque.before_first_fork do
63
+ NewRelic::Agent.manual_start(:dispatcher => :resque,
64
+ :sync_startup => true,
65
+ :start_channel_listener => true)
66
+ end
67
+
68
+ ::Resque.before_fork do |job|
69
+ NewRelic::Agent.register_report_channel(job.object_id)
70
+ end
71
+
72
+ ::Resque.after_fork do |job|
73
+ NewRelic::Agent.after_fork(:report_to_channel => job.object_id)
74
+ end
75
+ end
76
+ end
77
+ end
78
+
79
+ # call this now so it is memoized before potentially forking worker processes
80
+ NewRelic::LanguageSupport.can_fork?
@@ -1,15 +1,22 @@
1
1
  require 'new_relic/agent/instrumentation/controller_instrumentation'
2
2
 
3
3
  DependencyDetection.defer do
4
+ @name = :sinatra
5
+
4
6
  depends_on do
5
- defined?(::Sinatra) && defined?(::Sinatra::Base)
7
+ defined?(::Sinatra) && defined?(::Sinatra::Base) &&
8
+ Sinatra::Base.private_method_defined?(:dispatch!)
9
+ end
10
+
11
+ executes do
12
+ NewRelic::Agent.logger.debug 'Installing Sinatra instrumentation'
6
13
  end
7
14
 
8
15
  executes do
9
16
  ::Sinatra::Base.class_eval do
10
17
  include NewRelic::Agent::Instrumentation::Sinatra
11
- alias route_eval_without_newrelic route_eval
12
- alias route_eval route_eval_with_newrelic
18
+ alias dispatch_without_newrelic dispatch!
19
+ alias dispatch! dispatch_with_newrelic
13
20
  end
14
21
  end
15
22
  end
@@ -26,26 +33,45 @@ module NewRelic
26
33
  # to match them. HTTP operations are not distinguished. Multiple matches
27
34
  # will all be tracked as separate actions.
28
35
  module Sinatra
36
+ include ::NewRelic::Agent::Instrumentation::ControllerInstrumentation
37
+
38
+ def dispatch_with_newrelic
39
+ txn_name = NewRelic.transaction_name(self.class.routes, @request) do |pattern, keys, conditions|
40
+ process_route(pattern, keys, conditions) do
41
+ pattern.source
42
+ end
43
+ end
44
+
45
+ perform_action_with_newrelic_trace(:category => :sinatra,
46
+ :name => txn_name,
47
+ :params => @request.params) do
48
+ dispatch_without_newrelic
49
+ end
50
+ end
29
51
 
30
- include NewRelic::Agent::Instrumentation::ControllerInstrumentation
31
-
32
- def route_eval_with_newrelic(&block_arg)
33
- path = unescape(@request.path_info)
34
- name = path
35
- # Go through each route and look for a match
36
- if routes = self.class.routes[@request.request_method]
37
- routes.detect do |pattern, keys, conditions, block|
38
- if block_arg.equal? block
39
- name = pattern.source
52
+ module NewRelic
53
+ extend self
54
+
55
+ def http_verb(request)
56
+ request.request_method if request.respond_to?(:request_method)
57
+ end
58
+
59
+ def transaction_name(routes, request)
60
+ name = '(unknown)'
61
+ verb = http_verb(request)
62
+
63
+ Array(routes[verb]).each do |pattern, keys, conditions, block|
64
+ if pattern = yield(pattern, keys, conditions)
65
+ name = pattern
40
66
  end
41
67
  end
42
- end
43
- # strip off leading ^ and / chars and trailing $ and /
44
- name.gsub!(%r{^[/^]*(.*?)[/\$\?]*$}, '\1')
45
- name = 'root' if name.empty?
46
- name = @request.request_method + ' ' + name if @request && @request.respond_to?(:request_method)
47
- perform_action_with_newrelic_trace(:category => :sinatra, :name => name, :params => @request.params) do
48
- route_eval_without_newrelic(&block_arg)
68
+
69
+ name.gsub!(%r{^[/^]*(.*?)[/\$\?]*$}, '\1')
70
+ if verb
71
+ name = verb + ' ' + name
72
+ end
73
+
74
+ name
49
75
  end
50
76
  end
51
77
  end
@@ -1,8 +1,14 @@
1
1
  DependencyDetection.defer do
2
+ @name = :sunspot
3
+
2
4
  depends_on do
3
5
  defined?(::Sunspot)
4
6
  end
5
7
 
8
+ executes do
9
+ NewRelic::Agent.logger.debug 'Installing Rails Sunspot instrumentation'
10
+ end
11
+
6
12
  executes do
7
13
  ::Sunspot.module_eval do
8
14
  class << self
@@ -1,11 +1,16 @@
1
1
  DependencyDetection.defer do
2
+ @name = :unicorn
3
+
2
4
  depends_on do
3
5
  defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
4
6
  end
5
-
7
+
8
+ executes do
9
+ NewRelic::Agent.logger.debug 'Installing Unicorn instrumentation'
10
+ end
11
+
6
12
  executes do
7
13
  Unicorn::HttpServer.class_eval do
8
- NewRelic::Agent.logger.debug "Installing Unicorn worker hook."
9
14
  old_worker_loop = instance_method(:worker_loop)
10
15
  define_method(:worker_loop) do | worker |
11
16
  NewRelic::Agent.after_fork(:force_reconnect => true)
@@ -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