newrelic_rpm 3.7.3.204 → 3.8.0.218

Sign up to get free protection for your applications and to get access to all the features.
Files changed (154) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +73 -0
  3. data/README.md +1 -1
  4. data/Rakefile +1 -5
  5. data/lib/new_relic/agent.rb +1 -0
  6. data/lib/new_relic/agent/agent.rb +47 -18
  7. data/lib/new_relic/agent/agent_logger.rb +11 -1
  8. data/lib/new_relic/agent/configuration/default_source.rb +85 -1
  9. data/lib/new_relic/agent/configuration/manager.rb +5 -1
  10. data/lib/new_relic/agent/datastores/mongo.rb +8 -3
  11. data/lib/new_relic/agent/harvester.rb +5 -1
  12. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +1 -0
  13. data/lib/new_relic/agent/instrumentation/active_merchant.rb +7 -3
  14. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +13 -3
  15. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +7 -1
  16. data/lib/new_relic/agent/instrumentation/sidekiq.rb +3 -1
  17. data/lib/new_relic/agent/instrumentation/sinatra.rb +3 -1
  18. data/lib/new_relic/agent/new_relic_service.rb +8 -0
  19. data/lib/new_relic/agent/request_sampler.rb +1 -1
  20. data/lib/new_relic/agent/sampler.rb +22 -2
  21. data/lib/new_relic/agent/sampler_collection.rb +13 -1
  22. data/lib/new_relic/agent/samplers/cpu_sampler.rb +3 -1
  23. data/lib/new_relic/agent/samplers/delayed_job_sampler.rb +2 -1
  24. data/lib/new_relic/agent/samplers/memory_sampler.rb +2 -1
  25. data/lib/new_relic/agent/samplers/object_sampler.rb +1 -3
  26. data/lib/new_relic/agent/samplers/vm_sampler.rb +126 -0
  27. data/lib/new_relic/agent/stats.rb +0 -15
  28. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +66 -75
  29. data/lib/new_relic/agent/stats_engine/stats_hash.rb +1 -1
  30. data/lib/new_relic/agent/supported_versions.rb +2 -2
  31. data/lib/new_relic/agent/transaction.rb +6 -3
  32. data/lib/new_relic/agent/vm/monotonic_gc_profiler.rb +17 -5
  33. data/lib/new_relic/agent/vm/mri_vm.rb +2 -1
  34. data/lib/new_relic/agent/vm/snapshot.rb +5 -1
  35. data/lib/new_relic/control/instance_methods.rb +8 -5
  36. data/lib/new_relic/control/instrumentation.rb +0 -9
  37. data/lib/new_relic/environment_report.rb +1 -1
  38. data/lib/new_relic/language_support.rb +4 -0
  39. data/lib/new_relic/local_environment.rb +39 -14
  40. data/lib/new_relic/noticed_error.rb +7 -4
  41. data/lib/new_relic/rack/browser_monitoring.rb +16 -3
  42. data/lib/new_relic/version.rb +2 -2
  43. data/newrelic_rpm.gemspec +1 -1
  44. data/test/agent_helper.rb +5 -3
  45. data/test/environments/lib/environments/runner.rb +8 -7
  46. data/test/environments/norails/Gemfile +1 -1
  47. data/test/environments/rails21/Gemfile +1 -0
  48. data/test/environments/rails22/Gemfile +1 -0
  49. data/test/environments/rails23/Gemfile +1 -0
  50. data/test/environments/rails30/Gemfile +4 -1
  51. data/test/environments/rails31/Gemfile +4 -1
  52. data/test/environments/rails32/Gemfile +3 -4
  53. data/test/environments/rails40/Gemfile +1 -1
  54. data/test/environments/rails41/Gemfile +1 -1
  55. data/test/flaky_proxy/lib/flaky_proxy/proxy.rb +1 -0
  56. data/test/multiverse/lib/multiverse/output_collector.rb +3 -1
  57. data/test/multiverse/lib/multiverse/runner.rb +2 -10
  58. data/test/multiverse/lib/multiverse/suite.rb +100 -30
  59. data/test/multiverse/suites/activemerchant/Envfile +16 -0
  60. data/test/multiverse/suites/activemerchant/activemerchant_test.rb +65 -0
  61. data/test/multiverse/suites/agent_only/custom_queue_time_test.rb +57 -0
  62. data/test/multiverse/suites/config_file_loading/config_file_loading_test.rb +1 -1
  63. data/test/multiverse/suites/mongo/Envfile +9 -1
  64. data/test/multiverse/suites/rails/Envfile +2 -2
  65. data/test/multiverse/suites/rails/app.rb +3 -0
  66. data/test/multiverse/suites/rails/bad_instrumentation_test.rb +0 -2
  67. data/test/multiverse/suites/rails/error_tracing_test.rb +1 -2
  68. data/test/multiverse/suites/rails/gc_instrumentation_test.rb +17 -8
  69. data/test/multiverse/suites/rails/ignore_test.rb +0 -2
  70. data/test/multiverse/suites/rails/mongrel_queue_depth_test.rb +0 -2
  71. data/test/multiverse/suites/rails/queue_time_test.rb +40 -11
  72. data/test/multiverse/suites/rails/request_statistics_test.rb +0 -3
  73. data/test/multiverse/suites/rails/view_instrumentation_test.rb +0 -2
  74. data/test/multiverse/suites/sidekiq/Envfile +7 -2
  75. data/test/multiverse/suites/sinatra/Envfile +1 -1
  76. data/test/multiverse/suites/sinatra/nested_middleware_test.rb +41 -0
  77. data/test/multiverse/suites/sinatra/sinatra_metric_explosion_test.rb +1 -1
  78. data/test/new_relic/agent/agent/connect_test.rb +32 -4
  79. data/test/new_relic/agent/agent/start_test.rb +9 -1
  80. data/test/new_relic/agent/agent_logger_test.rb +23 -2
  81. data/test/new_relic/agent/agent_test.rb +49 -7
  82. data/test/new_relic/agent/configuration/manager_test.rb +8 -0
  83. data/test/new_relic/agent/configuration/orphan_configuration_test.rb +7 -0
  84. data/test/new_relic/agent/cross_app_monitor_test.rb +5 -6
  85. data/test/new_relic/agent/harvester_test.rb +13 -8
  86. data/test/new_relic/agent/instrumentation/action_controller_subscriber_test.rb +28 -7
  87. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +32 -21
  88. data/test/new_relic/agent/new_relic_service_test.rb +14 -0
  89. data/test/new_relic/agent/request_sampler_test.rb +5 -3
  90. data/test/new_relic/agent/rpm_agent_test.rb +2 -3
  91. data/test/new_relic/agent/sampler_collection_test.rb +15 -5
  92. data/test/new_relic/agent/sampler_test.rb +43 -0
  93. data/test/new_relic/agent/{cpu_sampler_test.rb → samplers/cpu_sampler_test.rb} +1 -1
  94. data/test/new_relic/agent/samplers/vm_sampler_test.rb +349 -0
  95. data/test/new_relic/agent/stats_engine/gc_profiler_test.rb +165 -44
  96. data/test/new_relic/agent/stats_hash_test.rb +1 -1
  97. data/test/new_relic/agent/transaction_test.rb +14 -0
  98. data/test/new_relic/agent/vm/monotonic_gc_profiler_test.rb +5 -5
  99. data/test/new_relic/agent/vm/mri_vm_test.rb +7 -0
  100. data/test/new_relic/agent/vm/snapshot_test.rb +5 -0
  101. data/test/new_relic/agent_test.rb +2 -2
  102. data/test/new_relic/control/instance_methods_test.rb +30 -0
  103. data/test/new_relic/control_test.rb +43 -21
  104. data/test/new_relic/dispatcher_test.rb +5 -0
  105. data/test/new_relic/local_environment_test.rb +3 -26
  106. data/test/new_relic/multiverse_helpers.rb +5 -0
  107. data/test/new_relic/noticed_error_test.rb +7 -0
  108. data/test/new_relic/rack/browser_monitoring_test.rb +13 -14
  109. data/test/test_helper.rb +2 -1
  110. metadata +56 -68
  111. metadata.gz.sig +1 -1
  112. data/lib/new_relic/agent/instrumentation/puma.rb +0 -25
  113. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +0 -26
  114. data/test/multiverse/script/run_one +0 -5
  115. data/test/rum/basic.result.html +0 -10
  116. data/test/rum/basic.source.html +0 -10
  117. data/test/rum/comments1.result.html +0 -24
  118. data/test/rum/comments1.source.html +0 -24
  119. data/test/rum/comments2.result.html +0 -24
  120. data/test/rum/comments2.source.html +0 -24
  121. data/test/rum/gt_in_quotes1.result.html +0 -27
  122. data/test/rum/gt_in_quotes1.source.html +0 -27
  123. data/test/rum/gt_in_quotes2.result.html +0 -24
  124. data/test/rum/gt_in_quotes2.source.html +0 -24
  125. data/test/rum/gt_in_quotes_mismatch.result.html +0 -24
  126. data/test/rum/gt_in_quotes_mismatch.source.html +0 -24
  127. data/test/rum/gt_in_single_quotes1.result.html +0 -25
  128. data/test/rum/gt_in_single_quotes1.source.html +0 -25
  129. data/test/rum/gt_in_single_quotes_mismatch.result.html +0 -25
  130. data/test/rum/gt_in_single_quotes_mismatch.source.html +0 -25
  131. data/test/rum/incomplete_non_meta_tags.result.html +0 -10
  132. data/test/rum/incomplete_non_meta_tags.source.html +0 -10
  133. data/test/rum/no_body.result.html +0 -21
  134. data/test/rum/no_body.source.html +0 -21
  135. data/test/rum/no_header.result.html +0 -7
  136. data/test/rum/no_header.source.html +0 -7
  137. data/test/rum/no_html_and_no_header.result.html +0 -3
  138. data/test/rum/no_html_and_no_header.source.html +0 -3
  139. data/test/rum/no_start_header.result.html +0 -9
  140. data/test/rum/no_start_header.source.html +0 -9
  141. data/test/rum/script1.result.html +0 -19
  142. data/test/rum/script1.source.html +0 -19
  143. data/test/rum/script2.result.html +0 -17
  144. data/test/rum/script2.source.html +0 -17
  145. data/test/rum/x_ua_meta_tag.result.html +0 -10
  146. data/test/rum/x_ua_meta_tag.source.html +0 -10
  147. data/test/rum/x_ua_meta_tag_multiline.result.html +0 -11
  148. data/test/rum/x_ua_meta_tag_multiline.source.html +0 -11
  149. data/test/rum/x_ua_meta_tag_spaces_around_equals.result.html +0 -10
  150. data/test/rum/x_ua_meta_tag_spaces_around_equals.source.html +0 -10
  151. data/test/rum/x_ua_meta_tag_with_others.result.html +0 -11
  152. data/test/rum/x_ua_meta_tag_with_others.source.html +0 -11
  153. data/test/rum/x_ua_meta_tag_with_spaces.result.html +0 -10
  154. data/test/rum/x_ua_meta_tag_with_spaces.source.html +0 -10
@@ -91,6 +91,7 @@ module NewRelic
91
91
 
92
92
  def record_queue_time(event)
93
93
  return unless event.queue_start
94
+ return unless NewRelic::Agent::Transaction.current.root?
94
95
  QueueTime.record_frontend_metrics(event.queue_start, event.time)
95
96
  end
96
97
 
@@ -16,12 +16,16 @@ DependencyDetection.defer do
16
16
  end
17
17
 
18
18
  executes do
19
+ class ActiveMerchant::Billing::Gateway
20
+ include NewRelic::Agent::MethodTracer
21
+ end
22
+
19
23
  ActiveMerchant::Billing::Gateway.implementations.each do |gateway|
20
24
  gateway.class_eval do
21
- implemented_methods = public_instance_methods(false)
25
+ implemented_methods = public_instance_methods(false).map(&:to_sym)
22
26
  gateway_name = self.name.split('::').last
23
- [:authorize, :purchase, :credit, :void, :capture, :recurring].each do |operation|
24
- if implemented_methods.include?(operation.to_s)
27
+ [:authorize, :purchase, :credit, :void, :capture, :recurring, :store, :unstore, :update].each do |operation|
28
+ if implemented_methods.include?(operation)
25
29
  add_method_tracer operation, "ActiveMerchant/gateway/#{gateway_name}/#{operation}", :scoped_metric_only => true
26
30
  add_method_tracer operation, "ActiveMerchant/gateway/#{gateway_name}", :push_scope => false
27
31
  add_method_tracer operation, "ActiveMerchant/operation/#{operation}", :push_scope => false
@@ -45,6 +45,7 @@ module NewRelic
45
45
  def perform_action_with_newrelic_trace(*args); yield; end
46
46
  end
47
47
 
48
+ # @api public
48
49
  module ClassMethods
49
50
  # Have NewRelic ignore actions in this controller. Specify the actions as hash options
50
51
  # using :except and :only. If no actions are specified, all actions are ignored.
@@ -385,7 +386,14 @@ module NewRelic
385
386
  def newrelic_response_code; end
386
387
 
387
388
  def newrelic_request_headers
388
- self.respond_to?(:request) && self.request.respond_to?(:headers) && self.request.headers
389
+ request = NewRelic::Agent::TransactionState.get.request
390
+ if request
391
+ if request.respond_to?(:headers)
392
+ request.headers
393
+ elsif request.respond_to?(:env)
394
+ request.env
395
+ end
396
+ end
389
397
  end
390
398
 
391
399
  # overrideable method to determine whether to trace an action
@@ -431,7 +439,7 @@ module NewRelic
431
439
  txn = Transaction.start(category, options)
432
440
  txn.name = TransactionNamer.new(self).name(options)
433
441
 
434
- txn.apdex_start = _detect_upstream_wait(txn.start_time)
442
+ txn.apdex_start = _detect_upstream_wait(txn)
435
443
  _record_queue_length
436
444
 
437
445
  return txn
@@ -468,7 +476,9 @@ module NewRelic
468
476
  # Return a Time instance representing the upstream start time.
469
477
  # now is a Time instance to fall back on if no other candidate
470
478
  # for the start time is found.
471
- def _detect_upstream_wait(now)
479
+ def _detect_upstream_wait(txn)
480
+ now = txn.start_time
481
+ return now unless txn.root?
472
482
  if newrelic_request_headers
473
483
  queue_start = QueueTime.parse_frontend_timestamp(newrelic_request_headers, now)
474
484
  QueueTime.record_frontend_metrics(queue_start, now) if queue_start
@@ -17,10 +17,13 @@ DependencyDetection.defer do
17
17
 
18
18
  executes do
19
19
  ActionView::PartialTemplate.class_eval do
20
+ include NewRelic::Agent::MethodTracer
20
21
  add_method_tracer :render, 'View/#{path_without_extension[%r{^(/.*/)?(.*)$},2]}.#{@view.template_format}.#{extension}/Partial'
21
22
  end
23
+
22
24
  # this is for template rendering, as opposed to partial rendering.
23
25
  ActionView::Template.class_eval do
26
+ include NewRelic::Agent::MethodTracer
24
27
  add_method_tracer :render, 'View/#{(path_without_extension || @view.controller.newrelic_metric_path)[%r{^(/.*/)?(.*)$},2]}.#{@view.template_format}.#{extension}/Rendering'
25
28
  end
26
29
  end
@@ -41,9 +44,9 @@ DependencyDetection.defer do
41
44
 
42
45
  executes do
43
46
  ActionController::Base.class_eval do
47
+ include NewRelic::Agent::MethodTracer
44
48
  add_method_tracer :render, 'View/#{newrelic_metric_path}/Rendering'
45
49
  end
46
-
47
50
  end
48
51
  end
49
52
 
@@ -62,9 +65,12 @@ DependencyDetection.defer do
62
65
 
63
66
  executes do
64
67
  ActionView::RenderablePartial.module_eval do
68
+ include NewRelic::Agent::MethodTracer
65
69
  add_method_tracer :render_partial, 'View/#{path[%r{^(/.*/)?(.*)$},2]}/Partial'
66
70
  end
71
+
67
72
  ActionView::Template.class_eval do
73
+ include NewRelic::Agent::MethodTracer
68
74
  add_method_tracer :render, 'View/#{path[%r{^(/.*/)?(.*)$},2]}/Rendering'
69
75
  end
70
76
  end
@@ -17,7 +17,9 @@ DependencyDetection.defer do
17
17
  class NewRelic::SidekiqInstrumentation
18
18
  include NewRelic::Agent::Instrumentation::ControllerInstrumentation
19
19
 
20
- def call(worker, msg, queue)
20
+ # Client middleware has additional parameters, and our tests use the
21
+ # middleware client-side to work inline.
22
+ def call(worker, msg, queue, *_)
21
23
  perform_action_with_newrelic_trace(
22
24
  :name => 'perform',
23
25
  :class_name => msg['class'],
@@ -125,7 +125,7 @@ module NewRelic
125
125
  def route_eval_with_newrelic(*args, &block)
126
126
  begin
127
127
  txn_name = TransactionNamer.transaction_name_for_route(env, request)
128
- ::NewRelic::Agent.set_transaction_name("#{self.class.name}/#{txn_name}") unless txn_name.nil?
128
+ ::NewRelic::Agent.set_transaction_name("#{self.class.name}/#{txn_name}", :category => :sinatra) unless txn_name.nil?
129
129
  rescue => e
130
130
  ::NewRelic::Agent.logger.debug("Failed during route_eval to set transaction name", e)
131
131
  end
@@ -137,6 +137,8 @@ module NewRelic
137
137
  if ignore_request?
138
138
  env['newrelic.ignored'] = true
139
139
  return dispatch_without_newrelic
140
+ elsif NewRelic::Agent::Transaction.current
141
+ return dispatch_and_notice_errors_with_newrelic
140
142
  end
141
143
 
142
144
  name = TransactionNamer.initial_transaction_name(request)
@@ -255,6 +255,14 @@ module NewRelic
255
255
  File.expand_path(File.join(control.newrelic_root, 'cert', 'cacert.pem'))
256
256
  end
257
257
 
258
+ def valid_to_marshal?(data)
259
+ @marshaller.dump(data)
260
+ true
261
+ rescue StandardError, SystemStackError => e
262
+ NewRelic::Agent.logger.warn("Unable to marshal environment report on connect.", e)
263
+ false
264
+ end
265
+
258
266
  private
259
267
 
260
268
  # A shorthand for NewRelic::Control.instance
@@ -140,10 +140,10 @@ class NewRelic::Agent::RequestSampler
140
140
  # scoped to just one transaction, so Datastore/all has what we want.
141
141
  map_metric('Datastore/all', :total_call_time => "databaseDuration")
142
142
  map_metric('Datastore/all', :call_count => "databaseCallCount")
143
+ map_metric('GC/Transaction/all', :total_call_time => "gcCumulative")
143
144
 
144
145
  # Web Metrics
145
146
  map_metric('WebFrontend/QueueTime', :total_call_time => "queueDuration")
146
- map_metric("GC/cumulative", :total_call_time => "gcCumulative")
147
147
  map_metric('Memcache/allWeb', :total_call_time => "memcacheDuration")
148
148
 
149
149
  map_metric('External/allWeb', :total_call_time => "externalDuration")
@@ -19,6 +19,14 @@ module NewRelic
19
19
  attr_reader :id
20
20
  @sampler_classes = []
21
21
 
22
+ def self.named(new_name)
23
+ @name = new_name
24
+ end
25
+
26
+ def self.name
27
+ @name
28
+ end
29
+
22
30
  def self.inherited(subclass)
23
31
  @sampler_classes << subclass
24
32
  end
@@ -28,12 +36,24 @@ module NewRelic
28
36
  true
29
37
  end
30
38
 
39
+ def self.enabled?
40
+ if @name
41
+ config_key = "disable_#{@name}_sampler"
42
+ !(Agent.config[config_key])
43
+ else
44
+ true
45
+ end
46
+ end
47
+
31
48
  def self.sampler_classes
32
49
  @sampler_classes
33
50
  end
34
51
 
35
- def initialize(id)
36
- @id = id
52
+ # The ID passed in here is unused by our code, but is preserved in case
53
+ # we have clients who are defining their own subclasses of this class, and
54
+ # expecting to be able to call super with an ID.
55
+ def initialize(id=nil)
56
+ @id = id || self.class.name
37
57
  end
38
58
 
39
59
  def poll
@@ -25,6 +25,16 @@ module NewRelic
25
25
  self.any? { |s| s.class == sampler_class }
26
26
  end
27
27
 
28
+ # adds samplers to the sampler collection so that they run every
29
+ # minute. This is dynamically recognized by any class that
30
+ # subclasses NewRelic::Agent::Sampler
31
+ def load_samplers
32
+ Sampler.sampler_classes.each do |subclass|
33
+ add_sampler(subclass)
34
+ end
35
+ end
36
+
37
+
28
38
  def poll_samplers
29
39
  @samplers.delete_if do |sampler|
30
40
  begin
@@ -38,7 +48,9 @@ module NewRelic
38
48
  end
39
49
 
40
50
  def add_sampler(sampler_class)
41
- if sampler_class.supported_on_this_platform?
51
+ supported = sampler_class.supported_on_this_platform?
52
+ enabled = sampler_class.enabled?
53
+ if supported && enabled
42
54
  if !sampler_class_registered?(sampler_class)
43
55
  sampler = sampler_class.new
44
56
  sampler.setup_events(@event_listener) if sampler.respond_to?(:setup_events)
@@ -9,8 +9,10 @@ module NewRelic
9
9
  module Samplers
10
10
  class CpuSampler < NewRelic::Agent::Sampler
11
11
  attr_reader :last_time
12
+
13
+ named :cpu
14
+
12
15
  def initialize
13
- super :cpu
14
16
  @processor_count = NewRelic::Agent::SystemInfo.processor_count
15
17
  if @processor_count.nil?
16
18
  NewRelic::Agent.logger.warn("Failed to determine processor count, assuming 1")
@@ -16,8 +16,9 @@ module NewRelic
16
16
  # versions of DJ where distinct queues are supported, it breaks it out by queue name.
17
17
  #
18
18
  class DelayedJobSampler < NewRelic::Agent::Sampler
19
+ named :delayed_job
20
+
19
21
  def initialize
20
- super :delayed_job_queue
21
22
  raise Unsupported, "DJ instrumentation disabled" if Agent.config[:disable_dj]
22
23
  raise Unsupported, "No DJ worker present" unless NewRelic::DelayedJobInjection.worker_name
23
24
  end
@@ -9,10 +9,11 @@ module NewRelic
9
9
  module Samplers
10
10
 
11
11
  class MemorySampler < NewRelic::Agent::Sampler
12
+ named :memory
13
+
12
14
  attr_accessor :sampler
13
15
 
14
16
  def initialize
15
- super :memory
16
17
  # macos, linux, solaris
17
18
  if defined? JRuby
18
19
  @sampler = JavaHeapSampler.new
@@ -8,9 +8,7 @@ module NewRelic
8
8
  module Agent
9
9
  module Samplers
10
10
  class ObjectSampler < NewRelic::Agent::Sampler
11
- def initialize
12
- super :objects
13
- end
11
+ named :object
14
12
 
15
13
  def self.supported_on_this_platform?
16
14
  NewRelic::LanguageSupport.object_space_usable? && ObjectSpace.respond_to?(:live_objects)
@@ -0,0 +1,126 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require 'new_relic/agent/sampler'
6
+ require 'new_relic/agent/vm'
7
+
8
+ module NewRelic
9
+ module Agent
10
+ module Samplers
11
+ class VMSampler < Sampler
12
+ GC_RUNS_METRIC = 'RubyVM/GC/runs'.freeze
13
+ HEAP_LIVE_METRIC = 'RubyVM/GC/heap_live'.freeze
14
+ HEAP_FREE_METRIC = 'RubyVM/GC/heap_free'.freeze
15
+ THREAD_COUNT_METRIC = 'RubyVM/Threads/all'.freeze
16
+ OBJECT_ALLOCATIONS_METRIC = 'RubyVM/GC/total_allocated_object'.freeze
17
+ MAJOR_GC_METRIC = 'RubyVM/GC/major_gc_count'.freeze
18
+ MINOR_GC_METRIC = 'RubyVM/GC/minor_gc_count'.freeze
19
+ METHOD_INVALIDATIONS_METRIC = 'RubyVM/CacheInvalidations/method'.freeze
20
+ CONSTANT_INVALIDATIONS_METRIC = 'RubyVM/CacheInvalidations/constant'.freeze
21
+
22
+ attr_reader :transaction_count
23
+
24
+ named :vm
25
+
26
+ def initialize
27
+ @lock = Mutex.new
28
+ @transaction_count = 0
29
+ @last_snapshot = take_snapshot
30
+ end
31
+
32
+ def take_snapshot
33
+ NewRelic::Agent::VM.snapshot
34
+ end
35
+
36
+ def setup_events(event_listener)
37
+ event_listener.subscribe(:transaction_finished, &method(:on_transaction_finished))
38
+ end
39
+
40
+ def on_transaction_finished(*_)
41
+ @lock.synchronize { @transaction_count += 1 }
42
+ end
43
+
44
+ def reset_transaction_count
45
+ @lock.synchronize do
46
+ old_count = @transaction_count
47
+ @transaction_count = 0
48
+ old_count
49
+ end
50
+ end
51
+
52
+ def record_gc_runs_metric(snapshot, txn_count)
53
+ if snapshot.gc_total_time || snapshot.gc_runs
54
+ if snapshot.gc_total_time
55
+ gc_time = snapshot.gc_total_time - @last_snapshot.gc_total_time.to_f
56
+ end
57
+ if snapshot.gc_runs
58
+ gc_runs = snapshot.gc_runs - @last_snapshot.gc_runs
59
+ end
60
+ wall_clock_time = snapshot.taken_at - @last_snapshot.taken_at
61
+ NewRelic::Agent.agent.stats_engine.record_metrics(GC_RUNS_METRIC) do |stats|
62
+ stats.call_count += txn_count
63
+ stats.total_call_time += gc_runs if gc_runs
64
+ stats.total_exclusive_time += gc_time if gc_time
65
+ stats.max_call_time = (gc_time.nil? ? 0 : 1)
66
+ stats.sum_of_squares += wall_clock_time
67
+ end
68
+ end
69
+ end
70
+
71
+ def record_delta(snapshot, key, metric, txn_count)
72
+ value = snapshot.send(key)
73
+ if value
74
+ delta = value - @last_snapshot.send(key)
75
+ NewRelic::Agent.agent.stats_engine.record_metrics(metric) do |stats|
76
+ stats.call_count += txn_count
77
+ stats.total_call_time += delta
78
+ end
79
+ end
80
+ end
81
+
82
+ def record_gauge_metric(metric_name, value)
83
+ NewRelic::Agent.agent.stats_engine.record_metrics(metric_name) do |stats|
84
+ stats.call_count = value
85
+ stats.sum_of_squares = 1
86
+ end
87
+ end
88
+
89
+ def record_heap_live_metric(snapshot)
90
+ if snapshot.heap_live
91
+ record_gauge_metric(HEAP_LIVE_METRIC, snapshot.heap_live)
92
+ end
93
+ end
94
+
95
+ def record_heap_free_metric(snapshot)
96
+ if snapshot.heap_free
97
+ record_gauge_metric(HEAP_FREE_METRIC, snapshot.heap_free)
98
+ end
99
+ end
100
+
101
+ def record_thread_count_metric(snapshot)
102
+ if snapshot.thread_count
103
+ record_gauge_metric(THREAD_COUNT_METRIC, snapshot.thread_count)
104
+ end
105
+ end
106
+
107
+ def poll
108
+ snap = take_snapshot
109
+ tcount = reset_transaction_count
110
+
111
+ record_gc_runs_metric(snap, tcount)
112
+ record_delta(snap, :total_allocated_object, OBJECT_ALLOCATIONS_METRIC, tcount)
113
+ record_delta(snap, :major_gc_count, MAJOR_GC_METRIC, tcount)
114
+ record_delta(snap, :minor_gc_count, MINOR_GC_METRIC, tcount)
115
+ record_delta(snap, :method_cache_invalidations, METHOD_INVALIDATIONS_METRIC, tcount)
116
+ record_delta(snap, :constant_cache_invalidations, CONSTANT_INVALIDATIONS_METRIC, tcount)
117
+ record_heap_live_metric(snap)
118
+ record_heap_free_metric(snap)
119
+ record_thread_count_metric(snap)
120
+
121
+ @last_snapshot = snap
122
+ end
123
+ end
124
+ end
125
+ end
126
+ end
@@ -76,21 +76,6 @@ module NewRelic
76
76
 
77
77
  alias trace_call record_data_point
78
78
 
79
- # Records multiple data points as one method call - this handles
80
- # all the aggregation that would be done with multiple
81
- # record_data_point calls
82
- def record_multiple_data_points(total_value, count=1)
83
- return record_data_point(total_value) if count == 1
84
- @call_count += count
85
- @total_call_time += total_value
86
- avg_val = total_value / count
87
- @min_call_time = avg_val if avg_val < @min_call_time || @call_count == count
88
- @max_call_time = avg_val if avg_val > @max_call_time
89
- @total_exclusive_time += total_value
90
- @sum_of_squares += (avg_val * avg_val) * count
91
- self
92
- end
93
-
94
79
  # increments the call_count by one
95
80
  def increment_count(value = 1)
96
81
  @call_count += value
@@ -7,68 +7,83 @@ module NewRelic
7
7
  module Agent
8
8
  class StatsEngine
9
9
  module GCProfiler
10
+ GCSnapshot = Struct.new(:gc_time_s, :gc_call_count)
11
+
10
12
  def self.init
11
- @profiler = RailsBenchProfiler.new if RailsBenchProfiler.enabled?
12
- @profiler = CoreGCProfiler.new if CoreGCProfiler.enabled?
13
- @profiler = LegacyRubiniusProfiler.new if LegacyRubiniusProfiler.enabled?
13
+ return @profiler if @initialized
14
+ @profiler = if RailsBenchProfiler.enabled?
15
+ RailsBenchProfiler.new
16
+ elsif CoreGCProfiler.enabled?
17
+ CoreGCProfiler.new
18
+ end
19
+ @initialized = true
14
20
  @profiler
15
21
  end
16
22
 
17
- def self.capture
18
- @profiler.capture if @profiler
23
+ def self.reset
24
+ @profiler = nil
25
+ @initialized = nil
19
26
  end
20
27
 
21
- class Profiler
22
- def initialize
23
- if self.class.enabled?
24
- @last_timestamp = call_time
25
- @last_count = call_count
26
- end
28
+ def self.take_snapshot
29
+ init
30
+ if @profiler
31
+ GCSnapshot.new(@profiler.call_time_s, @profiler.call_count)
32
+ else
33
+ nil
27
34
  end
35
+ end
28
36
 
29
- def capture
30
- return unless self.class.enabled?
31
- return if !scope_stack.empty? && scope_stack.last.name == "GC/cumulative"
37
+ def self.record_delta(start_snapshot, end_snapshot)
38
+ if @profiler && start_snapshot && end_snapshot
39
+ elapsed_gc_time_s = end_snapshot.gc_time_s - start_snapshot.gc_time_s
40
+ num_calls = end_snapshot.gc_call_count - start_snapshot.gc_call_count
41
+ record_gc_metric(num_calls, elapsed_gc_time_s)
32
42
 
33
- num_calls = call_count - @last_count
34
- # microseconds to seconds
35
- elapsed = (call_time - @last_timestamp).to_f / 1_000_000.0
36
- @last_timestamp = call_time
37
- @last_count = call_count
38
- reset
43
+ @profiler.reset
44
+ elapsed_gc_time_s
45
+ end
46
+ end
39
47
 
40
- record_gc_metric(num_calls, elapsed)
41
- elapsed
48
+ def self.record_gc_metric(call_count, elapsed)
49
+ NewRelic::Agent.agent.stats_engine.record_metrics(gc_metric_specs) do |stats|
50
+ stats.call_count += call_count
51
+ stats.total_call_time += elapsed
52
+ stats.total_exclusive_time += elapsed
42
53
  end
54
+ end
43
55
 
44
- def reset; end
56
+ GC_ROLLUP = 'GC/Transaction/all'.freeze
57
+ GC_OTHER = 'GC/Transaction/allOther'.freeze
58
+ GC_WEB = 'GC/Transaction/allWeb'.freeze
45
59
 
46
- protected
47
-
48
- def record_gc_metric(num_calls, elapsed)
49
- if num_calls > 0
50
- # GC stats are collected into a blamed metric which allows
51
- # us to show the stats controller by controller
52
- NewRelic::Agent.instance.stats_engine \
53
- .record_metrics('GC/cumulative', nil, :scoped => true) do |stat|
54
- stat.record_multiple_data_points(elapsed, num_calls)
55
- end
56
- end
57
- end
60
+ SCOPE_PLACEHOLDER = NewRelic::Agent::StatsEngine::MetricStats::SCOPE_PLACEHOLDER
61
+
62
+ GC_ROLLUP_SPEC = NewRelic::MetricSpec.new(GC_ROLLUP)
63
+ GC_OTHER_SPEC = NewRelic::MetricSpec.new(GC_OTHER)
64
+ GC_OTHER_SCOPED_SPEC = NewRelic::MetricSpec.new(GC_OTHER, SCOPE_PLACEHOLDER)
65
+ GC_WEB_SPEC = NewRelic::MetricSpec.new(GC_WEB)
66
+ GC_WEB_SCOPED_SPEC = NewRelic::MetricSpec.new(GC_WEB, SCOPE_PLACEHOLDER)
58
67
 
59
- def scope_stack
60
- NewRelic::Agent::TransactionState.get.stats_scope_stack
68
+ def self.gc_metric_specs
69
+ # The .dup call on the scoped MetricSpec here is necessary because
70
+ # metric specs with non-empty scopes will have their scopes mutated
71
+ # when the metrics are merged into the global stats hash, and we don't
72
+ # want to mutate the original MetricSpec.
73
+ if NewRelic::Agent::Transaction.recording_web_transaction?
74
+ [GC_ROLLUP_SPEC, GC_WEB_SPEC, GC_WEB_SCOPED_SPEC.dup]
75
+ else
76
+ [GC_ROLLUP_SPEC, GC_OTHER_SPEC, GC_OTHER_SCOPED_SPEC.dup]
61
77
  end
62
78
  end
63
79
 
64
- class RailsBenchProfiler < Profiler
80
+ class RailsBenchProfiler
65
81
  def self.enabled?
66
82
  ::GC.respond_to?(:time) && ::GC.respond_to?(:collections)
67
83
  end
68
84
 
69
- # microseconds spent in GC
70
- def call_time
71
- ::GC.time # this should already be microseconds
85
+ def call_time_s
86
+ ::GC.time.to_f / 1_000_000 # this value is reported in us, so convert to s
72
87
  end
73
88
 
74
89
  def call_count
@@ -80,49 +95,25 @@ module NewRelic
80
95
  end
81
96
  end
82
97
 
83
- class CoreGCProfiler < Profiler
98
+ class CoreGCProfiler
84
99
  def self.enabled?
85
- !NewRelic::LanguageSupport.using_engine?('jruby') &&
86
- defined?(::GC::Profiler) && ::GC::Profiler.enabled?
100
+ NewRelic::LanguageSupport.gc_profiler_enabled?
87
101
  end
88
102
 
89
- # microseconds spent in GC
90
- # 1.9 total_time returns seconds. Don't trust the docs. It's seconds.
91
- def call_time
92
- ::GC::Profiler.total_time * 1_000_000.0 # convert seconds to microseconds
103
+ def call_time_s
104
+ NewRelic::Agent.instance.monotonic_gc_profiler.total_time_s
93
105
  end
94
106
 
95
107
  def call_count
96
108
  ::GC.count
97
109
  end
98
110
 
99
- def reset
100
- ::GC::Profiler.clear
101
- @last_timestamp = 0
102
- end
103
- end
104
-
105
- # Only present for legacy support of Rubinius < 2.0.0
106
- class LegacyRubiniusProfiler < Profiler
107
- def self.enabled?
108
- self.has_rubinius_profiler? && !has_core_profiler?
109
- end
110
-
111
- def self.has_rubinius_profiler?
112
- defined?(::Rubinius) && defined?(::Rubinius::GC) && ::Rubinius::GC.respond_to?(:count)
113
- end
114
-
115
- def self.has_core_profiler?
116
- defined?(::GC::Profiler)
117
- end
118
-
119
- def call_time
120
- ::Rubinius::GC.time * 1000
121
- end
122
-
123
- def call_count
124
- ::Rubinius::GC.count
125
- end
111
+ # When using GC::Profiler, it's important to periodically call
112
+ # GC::Profiler.clear in order to avoid unbounded growth in the number
113
+ # of GC recordds that are stored. However, we actually do this
114
+ # internally within MonotonicGCProfiler on calls to #total_time_s,
115
+ # so the reset here is a no-op.
116
+ def reset; end
126
117
  end
127
118
  end
128
119
  end