newrelic_rpm 2.8.11 → 2.9.2

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

Potentially problematic release.


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

Files changed (137) hide show
  1. data/CHANGELOG +267 -0
  2. data/LICENSE +1 -1
  3. data/Manifest +142 -0
  4. data/README.md +138 -0
  5. data/Rakefile +10 -28
  6. data/bin/mongrel_rpm +33 -0
  7. data/cert/cacert.pem +34 -0
  8. data/init.rb +38 -0
  9. data/lib/new_relic/agent/agent.rb +160 -347
  10. data/lib/new_relic/agent/collection_helper.rb +13 -24
  11. data/lib/new_relic/agent/error_collector.rb +29 -15
  12. data/lib/new_relic/agent/instrumentation/active_record_instrumentation.rb +63 -76
  13. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +90 -48
  14. data/lib/new_relic/agent/instrumentation/dispatcher_instrumentation.rb +72 -47
  15. data/lib/new_relic/agent/instrumentation/error_instrumentation.rb +14 -0
  16. data/lib/new_relic/agent/instrumentation/merb/controller.rb +10 -1
  17. data/lib/new_relic/agent/instrumentation/merb/dispatcher.rb +5 -7
  18. data/lib/new_relic/agent/instrumentation/merb/errors.rb +3 -1
  19. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +7 -0
  20. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +34 -7
  21. data/lib/new_relic/agent/instrumentation/rails/dispatcher.rb +20 -12
  22. data/lib/new_relic/agent/instrumentation/rails/errors.rb +5 -4
  23. data/lib/new_relic/agent/method_tracer.rb +159 -135
  24. data/lib/new_relic/agent/patch_const_missing.rb +46 -26
  25. data/lib/new_relic/agent/sampler.rb +12 -0
  26. data/lib/new_relic/agent/samplers/cpu_sampler.rb +44 -0
  27. data/lib/new_relic/agent/samplers/memory_sampler.rb +126 -0
  28. data/lib/new_relic/agent/samplers/mongrel_sampler.rb +22 -0
  29. data/lib/new_relic/agent/shim_agent.rb +11 -0
  30. data/lib/new_relic/agent/stats_engine.rb +85 -46
  31. data/lib/new_relic/agent/transaction_sampler.rb +63 -38
  32. data/lib/new_relic/agent/worker_loop.rb +8 -18
  33. data/lib/new_relic/agent.rb +200 -25
  34. data/lib/new_relic/commands/deployments.rb +9 -9
  35. data/lib/new_relic/control/merb.rb +22 -0
  36. data/lib/new_relic/control/rails.rb +141 -0
  37. data/lib/new_relic/{config → control}/ruby.rb +13 -2
  38. data/lib/new_relic/control.rb +424 -0
  39. data/lib/new_relic/local_environment.rb +201 -79
  40. data/lib/new_relic/metric_data.rb +7 -0
  41. data/lib/new_relic/metric_parser/action_mailer.rb +9 -0
  42. data/lib/new_relic/metric_parser/active_merchant.rb +26 -0
  43. data/lib/new_relic/metric_parser/active_record.rb +11 -0
  44. data/lib/new_relic/metric_parser/controller.rb +51 -0
  45. data/lib/new_relic/metric_parser/controller_cpu.rb +38 -0
  46. data/lib/new_relic/metric_parser/database.rb +23 -0
  47. data/lib/new_relic/metric_parser/errors.rb +6 -0
  48. data/lib/new_relic/metric_parser/mem_cache.rb +12 -0
  49. data/lib/new_relic/metric_parser/view.rb +61 -0
  50. data/lib/new_relic/metric_parser/web_service.rb +9 -0
  51. data/lib/new_relic/metric_parser.rb +107 -0
  52. data/lib/new_relic/metric_spec.rb +5 -0
  53. data/lib/new_relic/noticed_error.rb +5 -1
  54. data/lib/new_relic/rack/metric_app.rb +57 -0
  55. data/lib/new_relic/rack/newrelic.ru +25 -0
  56. data/lib/new_relic/rack/newrelic.yml +25 -0
  57. data/lib/new_relic/rack.rb +5 -0
  58. data/lib/new_relic/recipes.rb +10 -3
  59. data/lib/new_relic/stats.rb +130 -144
  60. data/lib/new_relic/transaction_analysis.rb +7 -8
  61. data/lib/new_relic/transaction_sample.rb +86 -10
  62. data/lib/new_relic/version.rb +41 -160
  63. data/lib/new_relic_api.rb +7 -6
  64. data/lib/newrelic_rpm.rb +30 -17
  65. data/lib/tasks/{agent_tests.rake → tests.rake} +1 -1
  66. data/newrelic.yml +115 -62
  67. data/newrelic_rpm.gemspec +36 -0
  68. data/test/active_record_fixtures.rb +55 -0
  69. data/test/config/newrelic.yml +21 -3
  70. data/test/config/{test_config.rb → test_control.rb} +14 -10
  71. data/test/new_relic/agent/active_record_instrumentation_test.rb +189 -0
  72. data/test/new_relic/agent/agent_test.rb +104 -0
  73. data/test/new_relic/agent/agent_test_controller.rb +18 -1
  74. data/test/new_relic/agent/classloader_patch_test.rb +56 -0
  75. data/test/new_relic/agent/{tc_collection_helper.rb → collection_helper_test.rb} +28 -23
  76. data/test/new_relic/agent/controller_test.rb +107 -0
  77. data/test/new_relic/agent/dispatcher_instrumentation_test.rb +70 -0
  78. data/test/new_relic/agent/error_collector_test.rb +155 -0
  79. data/test/new_relic/agent/{tc_method_tracer.rb → method_tracer_test.rb} +6 -12
  80. data/test/new_relic/agent/metric_data_test.rb +56 -0
  81. data/test/new_relic/agent/stats_engine_test.rb +266 -0
  82. data/test/new_relic/agent/{tc_transaction_sample_builder.rb → transaction_sample_builder_test.rb} +6 -5
  83. data/test/new_relic/agent/{tc_transaction_sample.rb → transaction_sample_test.rb} +9 -13
  84. data/test/new_relic/agent/transaction_sampler_test.rb +317 -0
  85. data/test/new_relic/agent/{tc_worker_loop.rb → worker_loop_test.rb} +1 -1
  86. data/test/new_relic/control_test.rb +97 -0
  87. data/test/new_relic/{tc_deployments_api.rb → deployments_api_test.rb} +8 -4
  88. data/test/new_relic/environment_test.rb +75 -0
  89. data/test/new_relic/metric_parser_test.rb +142 -0
  90. data/test/new_relic/{tc_metric_spec.rb → metric_spec_test.rb} +28 -1
  91. data/test/new_relic/samplers_test.rb +71 -0
  92. data/test/new_relic/{tc_shim_agent.rb → shim_agent_test.rb} +1 -1
  93. data/test/new_relic/stats_test.rb +291 -0
  94. data/test/new_relic/version_number_test.rb +46 -0
  95. data/test/test_helper.rb +7 -30
  96. data/test/ui/newrelic_controller_test.rb +14 -0
  97. data/test/ui/{tc_newrelic_helper.rb → newrelic_helper_test.rb} +16 -7
  98. data/ui/controllers/newrelic_controller.rb +17 -3
  99. data/ui/helpers/newrelic_helper.rb +44 -15
  100. data/ui/views/layouts/newrelic_default.rhtml +7 -8
  101. data/ui/views/newrelic/_sample.rhtml +5 -2
  102. data/ui/views/newrelic/_segment.rhtml +1 -1
  103. data/ui/views/newrelic/_segment_limit_message.rhtml +1 -0
  104. data/ui/views/newrelic/_segment_row.rhtml +4 -4
  105. data/ui/views/newrelic/_show_sample_detail.rhtml +3 -1
  106. data/ui/views/newrelic/_show_sample_sql.rhtml +2 -1
  107. data/ui/views/newrelic/explain_sql.rhtml +2 -5
  108. data/ui/views/newrelic/images/file_icon.png +0 -0
  109. data/ui/views/newrelic/images/new_relic_rpm_desktop.gif +0 -0
  110. data/ui/views/newrelic/index.rhtml +21 -13
  111. data/ui/views/newrelic/javascript/prototype-scriptaculous.js +7288 -0
  112. data/ui/views/newrelic/show_sample.rhtml +18 -3
  113. data/ui/views/newrelic/stylesheets/style.css +39 -0
  114. data/ui/views/newrelic/threads.rhtml +52 -0
  115. metadata +192 -70
  116. data/README +0 -136
  117. data/lib/new_relic/agent/instrumentation/rails/rails.rb +0 -6
  118. data/lib/new_relic/agent/samplers/cpu.rb +0 -29
  119. data/lib/new_relic/agent/samplers/memory.rb +0 -53
  120. data/lib/new_relic/agent/samplers/mongrel.rb +0 -26
  121. data/lib/new_relic/agent/synchronize.rb +0 -40
  122. data/lib/new_relic/config/merb.rb +0 -35
  123. data/lib/new_relic/config/rails.rb +0 -114
  124. data/lib/new_relic/config.rb +0 -279
  125. data/lib/new_relic/shim_agent.rb +0 -96
  126. data/test/new_relic/agent/model_fixture.rb +0 -15
  127. data/test/new_relic/agent/tc_active_record.rb +0 -90
  128. data/test/new_relic/agent/tc_agent.rb +0 -148
  129. data/test/new_relic/agent/tc_controller.rb +0 -77
  130. data/test/new_relic/agent/tc_dispatcher_instrumentation.rb +0 -52
  131. data/test/new_relic/agent/tc_error_collector.rb +0 -127
  132. data/test/new_relic/agent/tc_stats_engine.rb +0 -218
  133. data/test/new_relic/agent/tc_synchronize.rb +0 -37
  134. data/test/new_relic/agent/tc_transaction_sampler.rb +0 -302
  135. data/test/new_relic/tc_config.rb +0 -36
  136. data/test/new_relic/tc_environment.rb +0 -94
  137. data/test/new_relic/tc_stats.rb +0 -141
@@ -27,43 +27,32 @@ module NewRelic::Agent::CollectionHelper
27
27
 
28
28
  def strip_nr_from_backtrace(backtrace)
29
29
  if backtrace
30
+ # this is for 1.9.1, where strings no longer have Enumerable
31
+ backtrace = backtrace.split("\n") if String === backtrace
30
32
  # strip newrelic from the trace
31
33
  backtrace = backtrace.reject {|line| line =~ /new_relic\/agent\// }
32
34
  # rename methods back to their original state
33
- backtrace = backtrace.collect {|line| line.gsub /_without_(newrelic|trace)/, ""}
35
+ backtrace = backtrace.collect {|line| line.gsub(/_without_(newrelic|trace)/, "")}
34
36
  end
35
37
  backtrace
36
38
  end
37
39
 
38
40
  private
39
41
 
40
- # Convert any kind of object to a descriptive string
41
- # Only call this on unknown objects. Otherwise call to_s.
42
+ # Convert any kind of object to a short string.
42
43
  def flatten(object)
43
- s =
44
- if object.respond_to? :inspect
45
- object.inspect
46
- elsif object.respond_to? :to_s
47
- object.to_s
48
- elsif object.nil?
49
- "nil"
50
- else
51
- "#<#{object.class.to_s}>"
52
- end
53
-
54
- if !(s.instance_of? String)
55
- s = "#<#{object.class.to_s}>"
44
+ s = case object
45
+ when nil then ''
46
+ when object.instance_of?(String) then object
47
+ when String then String.new(object) # convert string subclasses to strings
48
+ else "#<#{object.class.to_s}>"
56
49
  end
57
-
58
- s
59
50
  end
60
-
61
51
  def truncate(string, len=256)
62
- if string.instance_of? Symbol
63
- string
64
- elsif string.nil?
65
- ""
66
- elsif string.instance_of? String
52
+ case string
53
+ when Symbol then string
54
+ when nil then ""
55
+ when String
67
56
  string.to_s.gsub(/^(.{#{len}})(.*)/) {$2.blank? ? $1 : $1 + "..."}
68
57
  else
69
58
  truncate(flatten(string), len)
@@ -1,13 +1,10 @@
1
1
 
2
2
  module NewRelic::Agent
3
3
  class ErrorCollector
4
- include Synchronize
5
4
  include CollectionHelper
6
5
 
7
6
  MAX_ERROR_QUEUE_LENGTH = 20 unless defined? MAX_ERROR_QUEUE_LENGTH
8
7
 
9
- attr_accessor :capture_params
10
- attr_accessor :capture_source
11
8
  attr_accessor :enabled
12
9
 
13
10
  def initialize(agent = nil)
@@ -16,9 +13,17 @@ module NewRelic::Agent
16
13
  # lookup of exception class names to ignore. Hash for fast access
17
14
  @ignore = {}
18
15
  @ignore_filter = nil
19
- @capture_params = true
20
- @capture_source = false
21
- @enabled = true
16
+
17
+ config = NewRelic::Control.instance.fetch('error_collector', {})
18
+
19
+ @enabled = config.fetch('enabled', true)
20
+ @capture_source = config.fetch('capture_source', true)
21
+
22
+ ignore_errors = config.fetch('ignore_errors', "")
23
+ ignore_errors = ignore_errors.split(",")
24
+ ignore_errors.each { |error| error.strip! }
25
+ ignore(ignore_errors)
26
+ @lock = Mutex.new
22
27
  end
23
28
 
24
29
  def ignore_error_filter(&block)
@@ -33,7 +38,7 @@ module NewRelic::Agent
33
38
  end
34
39
 
35
40
 
36
- def notice_error(path, request_uri, params, exception)
41
+ def notice_error(exception, request=nil, action_path=nil, filtered_params={})
37
42
 
38
43
  return unless @enabled
39
44
  return if @ignore[exception.class.name]
@@ -48,12 +53,19 @@ module NewRelic::Agent
48
53
 
49
54
  data = {}
50
55
 
51
- data[:request_params] = normalize_params(params) if @capture_params
56
+ action_path ||= ''
57
+
58
+ data[:request_params] = normalize_params(filtered_params) if NewRelic::Control.instance.capture_params
59
+
52
60
  data[:custom_params] = normalize_params(@agent.custom_params) if @agent
53
61
 
54
- data[:request_uri] = request_uri
62
+ data[:request_uri] = request.path if request
63
+ data[:request_uri] ||= ""
64
+
65
+ data[:request_referer] = request.referer if request
66
+ data[:request_referer] ||= ""
55
67
 
56
- data[:rails_root] = NewRelic::Config.instance.root
68
+ data[:rails_root] = NewRelic::Control.instance.root
57
69
 
58
70
  data[:file_name] = exception.file_name if exception.respond_to?('file_name')
59
71
  data[:line_number] = exception.line_number if exception.respond_to?('line_number')
@@ -67,10 +79,12 @@ module NewRelic::Agent
67
79
  else
68
80
  inside_exception = exception
69
81
  end
70
- data[:stack_trace] = strip_nr_from_backtrace(inside_exception.backtrace)
71
- noticed_error = NewRelic::NoticedError.new(path, data, exception)
82
+
83
+ data[:stack_trace] = inside_exception.backtrace
84
+
85
+ noticed_error = NewRelic::NoticedError.new(action_path, data, exception)
72
86
 
73
- synchronize do
87
+ @lock.synchronize do
74
88
  if @errors.length >= MAX_ERROR_QUEUE_LENGTH
75
89
  log.info("The error reporting queue has reached #{MAX_ERROR_QUEUE_LENGTH}. This error will not be reported to RPM: #{exception.message}")
76
90
  else
@@ -86,7 +100,7 @@ module NewRelic::Agent
86
100
  if unsent_errors && !unsent_errors.empty?
87
101
  return unsent_errors
88
102
  else
89
- synchronize do
103
+ @lock.synchronize do
90
104
  errors = @errors
91
105
  @errors = []
92
106
  return errors
@@ -99,7 +113,7 @@ module NewRelic::Agent
99
113
  @error_stat ||= NewRelic::Agent.get_stats("Errors/all")
100
114
  end
101
115
  def log
102
- NewRelic::Config.instance.log
116
+ NewRelic::Control.instance.log
103
117
  end
104
118
  end
105
119
  end
@@ -1,95 +1,82 @@
1
1
 
2
2
  # NewRelic instrumentation for ActiveRecord
3
- if defined? ActiveRecord::Base
3
+ if defined?(ActiveRecord::Base) && !NewRelic::Control.instance['skip_ar_instrumentation']
4
4
 
5
- ActiveRecord::Base.class_eval do
6
- class << self
7
- [:find_by_sql, :count].each do |find_method|
8
- add_method_tracer find_method, 'ActiveRecord/#{self.name}/find'
9
- add_method_tracer find_method, 'ActiveRecord/find', :push_scope => false
10
- add_method_tracer find_method, 'ActiveRecord/all', :push_scope => false
5
+ module NewRelic::Agent::Instrumentation::ActiveRecordInstrumentation
6
+
7
+ def self.included(instrumented_class)
8
+ instrumented_class.class_eval do
9
+ alias_method :log_without_newrelic_instrumentation, :log
10
+ alias_method :log, :log_with_newrelic_instrumentation
11
+ protected :log
11
12
  end
12
13
  end
13
- [:save, :save!].each do |save_method|
14
- add_method_tracer save_method, 'ActiveRecord/#{self.class.name}/save'
15
- add_method_tracer save_method, 'ActiveRecord/save', :push_scope => false
16
- add_method_tracer save_method, 'ActiveRecord/all', :push_scope => false
17
- end
18
14
 
19
- add_method_tracer :destroy, 'ActiveRecord/#{self.class.name}/destroy'
20
- add_method_tracer :destroy, 'ActiveRecord/destroy', :push_scope => false
21
- add_method_tracer :destroy, 'ActiveRecord/all', :push_scope => false
22
- end
23
-
24
- # instrumentation to catch logged SQL statements in sampled transactions
25
-
26
- ActiveRecord::ConnectionAdapters::AbstractAdapter.class_eval do
27
- @@my_sql_defined = defined? ActiveRecord::ConnectionAdapters::MysqlAdapter
28
- @@postgres_defined = defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
15
+ def active_record_all_stats
16
+ # need to lazy init this
17
+ @@active_record_all ||= NewRelic::Agent.instance.stats_engine.get_stats_no_scope("ActiveRecord/all")
18
+ end
29
19
 
30
20
  def log_with_newrelic_instrumentation(sql, name, &block)
31
- # if we aren't in a blamed context, then add one so that we can see that
32
- # controllers are calling SQL directly
33
- # we check scope_depth vs 2 since the controller is 1, and the
34
- #
35
- if NewRelic::Agent.instance.transaction_sampler.scope_depth < 2
36
- self.class.trace_method_execution_with_scope "Database/DirectSQL", true, true do
37
- log_with_capture_sql(sql, name, &block)
21
+ # Capture db config if we are going to try to get the explain plans
22
+ if (defined? ActiveRecord::ConnectionAdapters::MysqlAdapter && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)) ||
23
+ (defined? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter && self.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter))
24
+ supported_config = @config
25
+ end
26
+ if name && (parts = name.split " ") && parts.size == 2
27
+ model = parts.first
28
+ operation = parts.last.downcase
29
+ metric_name = case operation
30
+ when 'load' then 'find'
31
+ when 'indexes', 'columns' then nil # fall back to DirectSQL
32
+ when 'destroy', 'find', 'save', 'create' then operation
33
+ when 'update' then 'save'
34
+ else
35
+ if model == 'Join'
36
+ operation
37
+ end
38
38
  end
39
- else
40
- log_with_capture_sql(sql, name, &block)
39
+ metric = "ActiveRecord/#{model}/#{metric_name}" if metric_name
40
+ end
41
+ if metric.nil? && sql =~ /^(select|update|insert|delete)/i
42
+ # Could not determine the model/operation so let's find a better
43
+ # metric. If it doesn't match the regex, it's probably a show
44
+ # command or some DDL which we'll ignore.
45
+ metric = "Database/SQL/#{$1.downcase}"
41
46
  end
42
- end
43
-
44
- def log_with_capture_sql(sql, name, &block)
45
- if @@my_sql_defined && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)
46
- config = @config
47
- elsif @@postgres_defined && self.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter)
48
- config = @config
47
+
48
+ if !metric
49
+ log_without_newrelic_instrumentation(sql, name, &block)
49
50
  else
50
- config = nil
51
+ self.class.trace_method_execution_with_scope metric, true, true do
52
+ t0 = Time.now.to_f
53
+ result = log_without_newrelic_instrumentation(sql, name, &block)
54
+ duration = Time.now.to_f - t0
55
+
56
+ NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, supported_config, duration)
57
+ # Record in the overall summary metric
58
+ active_record_all_stats.record_data_point(duration)
59
+ # Record in the summary metric for this operation
60
+ NewRelic::Agent.instance.stats_engine.get_stats_no_scope("ActiveRecord/#{metric_name}").record_data_point(duration) if metric_name
61
+ result
62
+ end
51
63
  end
52
-
53
- t0 = Time.now
54
- result = log_without_newrelic_instrumentation(sql, name, &block)
55
-
56
- NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, config, Time.now - t0)
57
-
58
- result
59
64
  end
60
65
 
61
- # Compare with #alias_method_chain, which is not available in
62
- # Rails 1.1:
63
- alias_method :log_without_newrelic_instrumentation, :log
64
- alias_method :log, :log_with_newrelic_instrumentation
65
- protected :log
66
-
67
- add_method_tracer :log, 'Database/#{adapter_name}/#{args[1]}', :metric => false
68
- add_method_tracer :log, 'Database/all', :push_scope => false
69
-
70
- end
71
- ActiveRecord::Associations::ClassMethods.class_eval do
72
- add_method_tracer :find_with_associations, 'ActiveRecord/#{self.name}/find'
73
- add_method_tracer :find_with_associations, 'ActiveRecord/find', :push_scope => false
74
- add_method_tracer :find_with_associations, 'ActiveRecord/all', :push_scope => false
75
66
  end
76
67
 
77
- # instrumentation for associations
78
- ActiveRecord::Associations::AssociationCollection.class_eval do
79
- add_method_tracer :delete, 'ActiveRecord/#{@owner.class.name}/association delete'
68
+ # instrumentation to catch logged SQL statements in sampled transactions
69
+ ActiveRecord::ConnectionAdapters::AbstractAdapter.module_eval do
70
+ include ::NewRelic::Agent::Instrumentation::ActiveRecordInstrumentation
80
71
  end
81
- =begin
82
- # Consider enabling these in the future
83
- class HasAndBelongsToManyAssociation
84
- add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
85
- add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
86
- add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
87
- end
88
-
89
- class HasManyAssociation
90
- add_method_tracer :find, 'ActiveRecord/#{@owner.class.name}/association find'
91
- add_method_tracer :insert_record, 'ActiveRecord/#{@owner.class.name}/association insert'
92
- add_method_tracer :create_record, 'ActiveRecord/#{@owner.class.name}/association create'
72
+
73
+ # This instrumentation will add an extra scope to the transaction traces
74
+ # which will show the code surrounding the query, inside the model find_by_sql
75
+ # method.
76
+ ActiveRecord::Base.class_eval do
77
+ class << self
78
+ add_method_tracer :find_by_sql, 'ActiveRecord/#{self.name}/find_by_sql', :metric => false
93
79
  end
94
- =end
80
+ end
81
+
95
82
  end
@@ -28,38 +28,66 @@
28
28
  module NewRelic::Agent::Instrumentation
29
29
  module ControllerInstrumentation
30
30
 
31
- @@newrelic_apdex_t = NewRelic::Agent.instance.apdex_t
32
- @@newrelic_apdex_overall = NewRelic::Agent.instance.stats_engine.get_stats_no_scope("Apdex")
33
31
  def self.included(clazz)
34
32
  clazz.extend(ClassMethods)
35
33
  end
36
34
 
35
+ # This module is for importing stubs when the agent is disabled
36
+ module ClassMethodsShim
37
+ def newrelic_ignore(*args); end
38
+ def newrelic_ignore_apdex(*args); end
39
+ end
40
+
41
+ module Shim
42
+ def self.included(clazz)
43
+ clazz.extend(ClassMethodsShim)
44
+ end
45
+ def newrelic_notice_error(*args); end
46
+ def new_relic_trace_controller_action(*args); yield; end
47
+ def newrelic_metric_path; end
48
+ def perform_action_with_newrelic_trace(*args); yield; end
49
+ end
50
+
37
51
  module ClassMethods
38
52
  # Have NewRelic ignore actions in this controller. Specify the actions as hash options
39
53
  # using :except and :only. If no actions are specified, all actions are ignored.
40
54
  def newrelic_ignore(specifiers={})
55
+ newrelic_ignore_aspect('do_not_trace', specifiers)
56
+ end
57
+ # Have NewRelic omit apdex measurements on the given actions. Typically used for
58
+ # actions that are not user facing or that skew your overall apdex measurement.
59
+ # Accepts :except and :only options, as with #newrelic_ignore.
60
+ def newrelic_ignore_apdex(specifiers={})
61
+ newrelic_ignore_aspect('ignore_apdex', specifiers)
62
+ end
63
+
64
+ def newrelic_ignore_aspect(property, specifiers={}) # :nodoc:
41
65
  if specifiers.empty?
42
- self.newrelic_ignore_attr = true
66
+ self.newrelic_write_attr property, true
43
67
  elsif ! (Hash === specifiers)
44
- logger.error "newrelic_ignore takes an optional hash with :only and :except lists of actions (illegal argument type '#{specifiers.class}')"
68
+ logger.error "newrelic_#{property} takes an optional hash with :only and :except lists of actions (illegal argument type '#{specifiers.class}')"
45
69
  else
46
- self.newrelic_ignore_attr = specifiers
70
+ self.newrelic_write_attr property, specifiers
47
71
  end
48
72
  end
49
- # Should be implemented in the controller class via the inheritable attribute mechanism.
50
- def newrelic_ignore_attr=(value); end
51
- def newrelic_ignore_attr; end
73
+
74
+ # Should be monkey patched into the controller class implemented with the inheritable attribute mechanism.
75
+ def newrelic_write_attr(attr_name, value) # :nodoc:
76
+ instance_variable_set "@#{attr_name}", value
77
+ end
78
+ def newrelic_read_attr(attr_name) # :nodoc:
79
+ instance_variable_get "@#{attr_name}", value
80
+ end
52
81
  end
53
82
 
54
83
  # Must be implemented in the controller class:
55
84
  # Determine the path that is used in the metric name for
56
85
  # the called controller action. Of the form controller_path/action_name
57
86
  #
58
- def newrelic_metric_path(action_name_override = nil)
87
+ def newrelic_metric_path(action_name_override = nil) # :nodoc:
59
88
  raise "Not implemented!"
60
89
  end
61
90
 
62
- @@newrelic_apdex_t = NewRelic::Agent.instance.apdex_t
63
91
  # Perform the current action with NewRelic tracing. Used in a method
64
92
  # chain via aliasing. Call directly if you want to instrument a specifc
65
93
  # block as if it were an action. Pass the block along with the path.
@@ -69,45 +97,30 @@ module NewRelic::Agent::Instrumentation
69
97
  agent = NewRelic::Agent.instance
70
98
  stats_engine = agent.stats_engine
71
99
 
72
- ignore_actions = self.class.newrelic_ignore_attr
73
100
  # Skip instrumentation based on the value of 'do_not_trace' and if
74
101
  # we aren't calling directly with a block.
75
- should_skip = !block_given? && case ignore_actions
76
- when nil; false
77
- when Hash
78
- only_actions = Array(ignore_actions[:only])
79
- except_actions = Array(ignore_actions[:except])
80
- only_actions.include?(action_name.to_sym) || (except_actions.any? && !except_actions.include?(action_name.to_sym))
81
- else
82
- true
83
- end
84
- if should_skip
85
- begin
86
- return perform_action_without_newrelic_trace(*args)
87
- ensure
88
- # Tell the dispatcher instrumentation that we ignored this action and it shouldn't
89
- # be counted for the overall HTTP operations measurement. The if.. appears here
90
- # because we might be ignoring the top level action instrumenting but instrumenting
91
- # a direct invocation that already happened, so we need to make sure if this var
92
- # has already been set to false we don't reset it.
93
- Thread.current[:controller_ignored] = true if Thread.current[:controller_ignored].nil?
94
- end
102
+ if !block_given? && is_filtered?(self.class.newrelic_read_attr('do_not_trace'))
103
+ # Tell the dispatcher instrumentation that we ignored this action and it shouldn't
104
+ # be counted for the overall HTTP operations measurement.
105
+ Thread.current[:controller_ignored] = true
106
+
107
+ return perform_action_without_newrelic_trace(*args)
95
108
  end
96
109
 
97
- Thread.current[:controller_ignored] = false
110
+ # reset this in case we came through a code path where the top level controller is ignored
111
+ Thread.current[:controller_ignored] = nil
98
112
 
99
113
  start = Time.now.to_f
100
114
  agent.ensure_worker_thread_started
101
115
 
102
116
  # generate metrics for all all controllers (no scope)
103
117
  self.class.trace_method_execution_no_scope "Controller" do
104
- # generate metrics for this specific action
105
118
  # assuming the first argument, if present, is the action name
106
119
  path = newrelic_metric_path(args.size > 0 ? args[0] : nil)
107
- stats_engine.transaction_name ||= "Controller/#{path}" if stats_engine
120
+ controller_metric = "Controller/#{path}"
108
121
 
109
- self.class.trace_method_execution_with_scope "Controller/#{path}", true, true do
110
- # send request and parameter info to the transaction sampler
122
+ self.class.trace_method_execution_with_scope controller_metric, true, true do
123
+ stats_engine.transaction_name = controller_metric
111
124
 
112
125
  local_params = (respond_to? :filter_parameters) ? filter_parameters(params) : params
113
126
 
@@ -115,6 +128,8 @@ module NewRelic::Agent::Instrumentation
115
128
 
116
129
  t = Process.times.utime + Process.times.stime
117
130
 
131
+ failed = false
132
+
118
133
  begin
119
134
  # run the action
120
135
  if block_given?
@@ -122,30 +137,57 @@ module NewRelic::Agent::Instrumentation
122
137
  else
123
138
  perform_action_without_newrelic_trace(*args)
124
139
  end
140
+ rescue Exception => e
141
+ failed = true
142
+ raise e
125
143
  ensure
126
144
  cpu_burn = (Process.times.utime + Process.times.stime) - t
145
+ stats_engine.get_stats_no_scope("ControllerCPU/#{path}").record_data_point(cpu_burn)
127
146
  agent.transaction_sampler.notice_transaction_cpu_time(cpu_burn)
128
147
 
129
- duration = Time.now.to_f - start
130
148
  # do the apdex bucketing
131
- if duration <= @@newrelic_apdex_t
132
- @@newrelic_apdex_overall.record_apdex_s cpu_burn # satisfied
133
- stats_engine.get_stats_no_scope("Apdex/#{path}").record_apdex_s cpu_burn
134
- elsif duration <= (4 * @@newrelic_apdex_t)
135
- @@newrelic_apdex_overall.record_apdex_t cpu_burn # tolerating
136
- stats_engine.get_stats_no_scope("Apdex/#{path}").record_apdex_t cpu_burn
137
- else
138
- @@newrelic_apdex_overall.record_apdex_f cpu_burn # frustrated
139
- stats_engine.get_stats_no_scope("Apdex/#{path}").record_apdex_f cpu_burn
149
+ #
150
+ unless is_filtered?(self.class.newrelic_read_attr('ignore_apdex'))
151
+ duration = Time.now.to_f - start
152
+ controller_stat = stats_engine.get_custom_stats("Apdex/#{path}", NewRelic::ApdexStats)
153
+ case
154
+ when failed
155
+ apdex_overall_stat.record_apdex_f # frustrated
156
+ controller_stat.record_apdex_f
157
+ when duration <= NewRelic::Control.instance['apdex_t']
158
+ apdex_overall_stat.record_apdex_s # satisfied
159
+ controller_stat.record_apdex_s
160
+ when duration <= 4 * NewRelic::Control.instance['apdex_t']
161
+ apdex_overall_stat.record_apdex_t # tolerating
162
+ controller_stat.record_apdex_t
163
+ else
164
+ apdex_overall_stat.record_apdex_f # frustrated
165
+ controller_stat.record_apdex_f
166
+ end
140
167
  end
141
-
142
168
  end
143
169
  end
144
170
  end
145
-
146
171
  ensure
147
172
  # clear out the name of the traced transaction under all circumstances
148
173
  stats_engine.transaction_name = nil
149
174
  end
175
+
176
+ private
177
+ def apdex_overall_stat
178
+ @@newrelic_apdex_overall ||= NewRelic::Agent.instance.stats_engine.get_custom_stats("Apdex", NewRelic::ApdexStats)
179
+ end
180
+
181
+ def is_filtered?(ignore_actions)
182
+ case ignore_actions
183
+ when nil; false
184
+ when Hash
185
+ only_actions = Array(ignore_actions[:only])
186
+ except_actions = Array(ignore_actions[:except])
187
+ only_actions.include?(action_name.to_sym) || (except_actions.any? && !except_actions.include?(action_name.to_sym))
188
+ else
189
+ true
190
+ end
191
+ end
150
192
  end
151
193
  end