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
@@ -1,44 +1,62 @@
1
1
  # This class is for debugging purposes only.
2
2
  # It inserts instrumentation into class loading to verify
3
3
  # that no classes are being loaded on the new relic thread,
4
- # which can cause problems in the class loader code.
4
+ # which can cause problems in the class loader code.
5
+ # It is only loaded by agent.rb when a particular newrelic.yml
6
+ # option is set.
5
7
 
6
- module ClassLoadingWatcher
8
+ module ClassLoadingWatcher # :nodoc: all
7
9
 
8
10
  extend self
9
11
  @@background_thread = nil
12
+ @@flag_const_missing = nil
10
13
 
11
14
  def background_thread
12
15
  @@background_thread
13
16
  end
17
+ def flag_const_missing
18
+ @@flag_const_missing
19
+ end
20
+ def flag_const_missing=(val)
21
+ @@flag_const_missing = val
22
+ end
14
23
 
15
- def set_background_thread(thread)
24
+ def background_thread=(thread)
16
25
  @@background_thread = thread
17
26
 
18
- # these tests that check is working...
19
- # begin
20
- # bad = Bad
21
- # rescue
22
- # end
23
-
24
- # require 'new_relic/agent/patch_const_missing'
25
- # load 'new_relic/agent/patch_const_missing.rb'
27
+ # these tests verify that check is working
28
+ =begin
29
+ @@background_thread = nil
30
+ bad = ConstMissingInForegroundThread rescue nil
31
+ @@background_thread = thread
32
+ bad = ConstMissingInBackgroundThread rescue nil
33
+ require 'new_relic/agent/patch_const_missing'
34
+ load 'new_relic/agent/patch_const_missing.rb'
35
+ =end
26
36
  end
27
37
  module SanityCheck
28
- def new_relic_check_for_badness(*args)
38
+ def nr_check_for_classloading(*args)
29
39
 
30
40
  if Thread.current == ClassLoadingWatcher.background_thread
31
- msg = "Agent background thread shouldn't be loading classes (#{args.inspect})\n"
32
-
33
- exception = NewRelic::Agent::BackgroundLoadingError.new(msg.clone)
34
- exception.set_backtrace(caller)
35
-
36
- NewRelic::Agent.instance.error_collector.notice_error(nil, nil, [], exception)
37
- msg << caller.join("\n")
38
-
39
- NewRelic::Config.instance.log.error msg
41
+ nr_error "Agent background thread shouldn't be loading classes (#{args.inspect})"
40
42
  end
41
43
  end
44
+ #
45
+ def nr_check_for_constmissing(*args)
46
+ if ClassLoadingWatcher.flag_const_missing
47
+ nr_error "Classes in Agent should not be loaded via const_missing (#{args.inspect})"
48
+ end
49
+ end
50
+ private
51
+ def nr_error(msg)
52
+ exception = NewRelic::Agent::BackgroundLoadingError.new(msg)
53
+ backtrace = caller
54
+ backtrace.shift
55
+ exception.set_backtrace(backtrace)
56
+ NewRelic::Agent.instance.error_collector.notice_error(exception, nil)
57
+ msg << "\n" << backtrace.join("\n")
58
+ NewRelic::Control.instance.log.error msg
59
+ end
42
60
  end
43
61
  def enable_warning
44
62
  Object.class_eval do
@@ -81,25 +99,27 @@ module ClassLoadingWatcher
81
99
  end
82
100
  end
83
101
 
84
- class Object
102
+ class Object # :nodoc:
85
103
  include ClassLoadingWatcher::SanityCheck
86
104
 
87
105
  def new_relic_require(*args)
88
- new_relic_check_for_badness("Object require", *args)
106
+ nr_check_for_classloading("Object require", *args)
89
107
  non_new_relic_require(*args)
90
108
  end
91
109
 
92
110
  def new_relic_load(*args)
93
- new_relic_check_for_badness("Object load", *args)
111
+ nr_check_for_classloading("Object load", *args)
94
112
  non_new_relic_load(*args)
95
113
  end
96
114
  end
97
115
 
98
- class Module
116
+
117
+ class Module # :nodoc:
99
118
  include ClassLoadingWatcher::SanityCheck
100
119
 
101
120
  def new_relic_const_missing(*args)
102
- new_relic_check_for_badness("Module #{self.name} const_missing", *args)
121
+ nr_check_for_constmissing("Module #{self.name} const_missing", *args)
122
+ nr_check_for_classloading("Module #{self.name} const_missing", *args)
103
123
  non_new_relic_const_missing(*args)
104
124
  end
105
125
  end
@@ -0,0 +1,12 @@
1
+ class NewRelic::Agent::Sampler
2
+ attr_accessor :stats_engine
3
+ attr_reader :id
4
+ def initialize(id)
5
+ @id = id
6
+ end
7
+
8
+ def poll
9
+ raise "Implement in the subclass"
10
+ end
11
+
12
+ end
@@ -0,0 +1,44 @@
1
+ module NewRelic::Agent::Samplers
2
+ class CpuSampler < NewRelic::Agent::Sampler
3
+ attr_reader :last_time
4
+ def initialize
5
+ super :cpu
6
+ poll
7
+ end
8
+ def user_util_stats
9
+ @userutil ||= stats_engine.get_stats("CPU/User/Utilization", false)
10
+ end
11
+ def system_util_stats
12
+ @systemutil ||= stats_engine.get_stats("CPU/System/Utilization", false)
13
+ end
14
+ def usertime_stats
15
+ @usertime ||= stats_engine.get_stats("CPU/User Time", false)
16
+ end
17
+ def systemtime_stats
18
+ @systemtime ||= stats_engine.get_stats("CPU/System Time", false)
19
+ end
20
+ def poll
21
+ now = Time.now
22
+ t = Process.times
23
+ if @last_time
24
+ elapsed = now - @last_time
25
+ return if elapsed < 1 # Causing some kind of math underflow
26
+ num_processors = NewRelic::Control.instance.local_env.processors || 1
27
+ usertime = t.utime - @last_utime
28
+ systemtime = t.stime - @last_stime
29
+
30
+ systemtime_stats.record_data_point(systemtime) if systemtime >= 0
31
+ usertime_stats.record_data_point(usertime) if usertime >= 0
32
+
33
+ # Calculate the true utilization by taking cpu times and dividing by
34
+ # elapsed time X num_processors.
35
+ user_util_stats.record_data_point usertime / (elapsed * num_processors)
36
+ system_util_stats.record_data_point systemtime / (elapsed * num_processors)
37
+ end
38
+ @last_utime = t.utime
39
+ @last_stime = t.stime
40
+ @last_time = now
41
+ end
42
+ end
43
+ end
44
+
@@ -0,0 +1,126 @@
1
+ module NewRelic::Agent::Samplers
2
+
3
+ class MemorySampler < NewRelic::Agent::Sampler
4
+ def initialize
5
+ super :memory
6
+
7
+ # macos, linux, solaris
8
+ if defined? Java
9
+ @sampler = JavaHeapSampler.new
10
+ elsif platform =~ /linux/
11
+ @sampler = ProcStatus.new
12
+ if !@sampler.can_run?
13
+ NewRelic::Agent.instance.log.warn "Error attempting to use /proc/$$/status file for reading memory. Using ps command instead."
14
+ @sampler = ShellPS.new("ps -o rsz")
15
+ else
16
+ NewRelic::Agent.instance.log.info "Using /proc/$$/status for reading process memory."
17
+ end
18
+ elsif platform =~ /darwin/
19
+ @sampler = ShellPS.new("ps -o rsz")
20
+ elsif platform =~ /freebsd/
21
+ @sampler = ShellPS.new("ps -o rss")
22
+ elsif platform =~ /solaris/
23
+ @sampler = ShellPS.new("/usr/bin/ps -o rss -p")
24
+ end
25
+
26
+ raise "Unsupported platform for getting memory: #{platform}" if @sampler.nil?
27
+ raise "Unable to run #{@sampler}" unless @sampler.can_run?
28
+ end
29
+ def platform
30
+ if RUBY_PLATFORM =~ /java/
31
+ %x[uname -s].downcase
32
+ else
33
+ RUBY_PLATFORM.downcase
34
+ end
35
+ end
36
+
37
+ def stats
38
+ @stats ||= stats_engine.get_stats("Memory/Physical", false)
39
+ end
40
+ def poll
41
+ sample = @sampler.get_sample
42
+ stats.record_data_point sample if sample
43
+ stats
44
+ end
45
+ class Base
46
+ def can_run?
47
+ return false if @broken
48
+ m = get_memory rescue nil
49
+ m && m > 0
50
+ end
51
+ def get_sample
52
+ return nil if @broken
53
+ begin
54
+ m = get_memory
55
+ if m.nil?
56
+ NewRelic::Agent.instance.log.error "Unable to get the resident memory for process #{$$}. Disabling memory sampler."
57
+ @broken = true
58
+ end
59
+ return m
60
+ rescue => e
61
+ NewRelic::Agent.instance.log.error "Unable to get the resident memory for process #{$$}. (#{e})"
62
+ NewRelic::Agent.instance.log.debug e.backtrace.join("\n ")
63
+ NewRelic::Agent.instance.log.error "Disabling memory sampler."
64
+ @broken = true
65
+ end
66
+ end
67
+ end
68
+
69
+ class JavaHeapSampler < Base
70
+
71
+ def get_memory
72
+ raise "Can't sample Java heap unless running in JRuby" unless defined? Java
73
+ java.lang.Runtime.getRuntime.totalMemory / (1024 * 1024).to_f rescue nil
74
+ end
75
+ def to_s
76
+ "JRuby Java heap sampler"
77
+ end
78
+ end
79
+
80
+ class ShellPS < Base
81
+ def initialize(command)
82
+ super()
83
+ @command = command
84
+ end
85
+ # Returns the amount of resident memory this process is using in MB
86
+ #
87
+ def get_memory
88
+ process = $$
89
+ memory = `#{@command} #{process}`.split("\n")[1].to_f / 1024.0 rescue nil
90
+ # if for some reason the ps command doesn't work on the resident os,
91
+ # then don't execute it any more.
92
+ raise "Faulty command: `#{@command} #{process}`" if memory.nil? || memory <= 0
93
+ memory
94
+ end
95
+ def to_s
96
+ "shell command sampler: #{@command}"
97
+ end
98
+ end
99
+
100
+ # ProcStatus
101
+ #
102
+ # A class that samples memory by reading the file /proc/$$/status, which is specific to linux
103
+ #
104
+ class ProcStatus < Base
105
+
106
+ # Returns the amount of resident memory this process is using in MB
107
+ #
108
+ def get_memory
109
+ File.open(proc_status_file, "r") do |f|
110
+ while !f.eof?
111
+ if f.readline =~ /RSS:\s*(\d+) kB/i
112
+ return $1.to_f / 1024.0
113
+ end
114
+ end
115
+ end
116
+ raise "Unable to find RSS in #{proc_status_file}"
117
+ end
118
+ def proc_status_file
119
+ "/proc/#{$$}/status"
120
+ end
121
+ def to_s
122
+ "proc status file sampler: #{proc_status_file}"
123
+ end
124
+ end
125
+ end
126
+ end
@@ -0,0 +1,22 @@
1
+ module NewRelic::Agent::Samplers
2
+ # NewRelic Instrumentation for Mongrel - tracks the queue length of the mongrel server.
3
+ class MongrelSampler < NewRelic::Agent::Sampler
4
+ def initialize
5
+ super :mongrel
6
+ end
7
+ def queue_stats
8
+ @queue_stats ||= stats_engine.get_stats("Mongrel/Queue Length", false)
9
+ end
10
+ def poll
11
+ mongrel = NewRelic::Control.instance.local_env.mongrel
12
+ if mongrel
13
+ # The mongrel workers list includes workers actively processing requests
14
+ # so you need to subtract what appears to be the active workers from the total
15
+ # number of workers to get the queue size.
16
+ qsize = mongrel.workers.list.length - NewRelic::Agent::Instrumentation::DispatcherInstrumentation::BusyCalculator.busy_count
17
+ qsize = 0 if qsize < 0
18
+ queue_stats.record_data_point qsize
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,11 @@
1
+ # This agent is loaded by the plug when the plug-in is disabled
2
+ # It recreates just enough of the API to not break any clients that
3
+ # invoke the Agent.
4
+ class NewRelic::Agent::ShimAgent < NewRelic::Agent::Agent
5
+ def self.instance
6
+ @instance ||= self.new
7
+ end
8
+ def ensure_worker_thread_started; end
9
+ def start *args; end
10
+ def shutdown; end
11
+ end
@@ -3,32 +3,23 @@ module NewRelic::Agent
3
3
  class StatsEngine
4
4
  POLL_PERIOD = 10
5
5
 
6
- attr_accessor :log
7
-
8
6
  ScopeStackElement = Struct.new(:name, :children_time, :deduct_call_time_from_parent)
9
7
 
10
- class SampledItem
11
- def initialize(stats, &callback)
12
- @stats = stats
13
- @callback = callback
14
- end
15
-
16
- def poll
17
- @callback.call @stats
18
- end
19
- end
20
-
21
- def initialize(log = Logger.new(STDERR))
8
+ def initialize
22
9
  @stats_hash = {}
23
- @sampled_items = []
10
+ @harvest_samplers = []
11
+ @periodic_samplers = []
24
12
  @scope_stack_listener = nil
25
- @log = log
26
13
 
27
14
  # Makes the unit tests happy
28
15
  Thread::current[:newrelic_scope_stack] = nil
29
16
 
30
17
  spawn_sampler_thread
31
18
  end
19
+
20
+ def log
21
+ NewRelic::Control.instance.log
22
+ end
32
23
 
33
24
  def spawn_sampler_thread
34
25
 
@@ -39,20 +30,11 @@ module NewRelic::Agent
39
30
  while true do
40
31
  begin
41
32
  sleep POLL_PERIOD
42
- @sampled_items.each do |sampled_item|
43
- begin
44
- sampled_item.poll
45
- rescue => e
46
- log.error e
47
- @sampled_items.delete sampled_item
48
- log.error "Removing #{sampled_item} from list"
49
- log.debug e.backtrace.to_s
50
- end
51
- end
33
+ poll @periodic_samplers
52
34
  end
53
35
  end
54
36
  end
55
-
37
+ @sampler_thread['newrelic_label'] = 'Sampler Tasks'
56
38
  @sampler_process = $$
57
39
  end
58
40
 
@@ -106,9 +88,19 @@ module NewRelic::Agent
106
88
  scope_stack.last
107
89
  end
108
90
 
109
- def add_sampled_metric(metric_name, &sampler_callback)
110
- stats = get_stats(metric_name, false)
111
- @sampled_items << SampledItem.new(stats, &sampler_callback)
91
+ # Add an instance of Sampler to be invoked about every 10 seconds on a background
92
+ # thread.
93
+ def add_sampler sampler
94
+ @periodic_samplers << sampler
95
+ sampler.stats_engine = self
96
+ log.debug "Adding sampler #{sampler.id.to_s}"
97
+ end
98
+
99
+ # Add a sampler to be invoked just before each harvest.
100
+ def add_harvest_sampler sampler
101
+ @harvest_samplers << sampler
102
+ sampler.stats_engine = self
103
+ log.debug "Adding harvest time sampler: #{sampler.id.to_s}"
112
104
  end
113
105
 
114
106
  # set the name of the transaction for the current thread, which will be used
@@ -144,33 +136,65 @@ module NewRelic::Agent
144
136
  stats
145
137
  end
146
138
 
147
- def get_stats(metric_name, use_scope = true)
139
+ # This version allows a caller to pass a stat class to use
140
+ #
141
+ def get_custom_stats(metric_name, stat_class)
148
142
  stats = @stats_hash[metric_name]
149
143
  if stats.nil?
150
- stats = NewRelic::MethodTraceStats.new
144
+ stats = stat_class.new
151
145
  @stats_hash[metric_name] = stats
152
146
  end
147
+ stats
148
+ end
149
+
150
+ # If use_scope is true, two chained metrics are created, one with scope and one without
151
+ # If scoped_metric_only is true, only a scoped metric is created (used by rendering metrics which by definition are per controller only)
152
+ def get_stats(metric_name, use_scope = true, scoped_metric_only = false)
153
153
 
154
- if use_scope && transaction_name
154
+ if scoped_metric_only
155
155
  spec = NewRelic::MetricSpec.new metric_name, transaction_name
156
156
 
157
- scoped_stats = @stats_hash[spec]
158
- if scoped_stats.nil?
159
- scoped_stats = NewRelic::ScopedMethodTraceStats.new stats
160
- @stats_hash[spec] = scoped_stats
157
+ stats = @stats_hash[spec]
158
+ if stats.nil?
159
+ stats = NewRelic::MethodTraceStats.new
160
+ @stats_hash[spec] = stats
161
+ end
162
+ else
163
+ stats = @stats_hash[metric_name]
164
+ if stats.nil?
165
+ stats = NewRelic::MethodTraceStats.new
166
+ @stats_hash[metric_name] = stats
161
167
  end
162
168
 
163
- stats = scoped_stats
169
+ if use_scope && transaction_name
170
+ spec = NewRelic::MetricSpec.new metric_name, transaction_name
171
+
172
+ scoped_stats = @stats_hash[spec]
173
+ if scoped_stats.nil?
174
+ scoped_stats = NewRelic::ScopedMethodTraceStats.new stats
175
+ @stats_hash[spec] = scoped_stats
176
+ end
177
+
178
+ stats = scoped_stats
179
+ end
164
180
  end
165
- return stats
181
+
182
+ stats
166
183
  end
167
184
 
185
+ # Harvest the timeslice data. First recombine current statss
186
+ # with any previously
187
+ # unsent metrics, clear out stats cache, and return the current
188
+ # stats.
189
+ # ---
168
190
  # Note: this is not synchronized. There is still some risk in this and
169
191
  # we will revisit later to see if we can make this more robust without
170
192
  # sacrificing efficiency.
193
+ # +++
171
194
  def harvest_timeslice_data(previous_timeslice_data, metric_ids)
172
195
  timeslice_data = {}
173
- @stats_hash.keys.each do |metric_spec|
196
+ poll @harvest_samplers
197
+ @stats_hash.keys.each do | metric_spec |
174
198
 
175
199
 
176
200
  # get a copy of the stats collected since the last harvest, and clear
@@ -194,12 +218,11 @@ module NewRelic::Agent
194
218
  # data
195
219
  previous_metric_data = previous_timeslice_data[metric_spec]
196
220
  stats_copy.merge! previous_metric_data.stats unless previous_metric_data.nil?
197
-
198
221
  stats_copy.round!
199
222
 
200
223
  # don't bother collecting and reporting stats that have zero-values for this timeslice.
201
224
  # significant performance boost and storage savings.
202
- unless stats_copy.call_count == 0 && stats_copy.total_call_time == 0.0 && stats_copy.total_exclusive_time == 0.0
225
+ unless stats_copy.is_reset?
203
226
 
204
227
  metric_spec_for_transport = (metric_ids[metric_spec].nil?) ? metric_spec : nil
205
228
 
@@ -212,12 +235,10 @@ module NewRelic::Agent
212
235
  timeslice_data
213
236
  end
214
237
 
215
-
216
238
  def start_transaction
217
239
  Thread::current[:newrelic_scope_stack] = []
218
240
  end
219
241
 
220
-
221
242
  # Try to clean up gracefully, otherwise we leave things hanging around on thread locals
222
243
  #
223
244
  def end_transaction
@@ -231,10 +252,28 @@ module NewRelic::Agent
231
252
  Thread::current[:newrelic_transaction_name] = nil
232
253
  end
233
254
 
255
+ def clear_stats # :nodoc: for test code only
256
+ @stats_hash.clear
257
+ end
234
258
  private
235
259
 
236
- def scope_stack
237
- Thread::current[:newrelic_scope_stack] ||= []
260
+ # Call poll on each of the samplers. Remove
261
+ # the sampler if it raises.
262
+ def poll(samplers)
263
+ samplers.delete_if do |sampled_item|
264
+ begin
265
+ sampled_item.poll
266
+ false # it's okay. don't delete it.
267
+ rescue => e
268
+ log.error "Removing #{sampled_item} from list"
269
+ log.error e
270
+ log.debug e.backtrace.to_s
271
+ true # remove the sampler
272
+ end
238
273
  end
274
+ end
275
+ def scope_stack
276
+ Thread::current[:newrelic_scope_stack] ||= []
277
+ end
239
278
  end
240
279
  end