newrelic_rpm 3.6.3.111 → 3.6.4.113.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. data/CHANGELOG +14 -1
  2. data/lib/new_relic/agent/agent.rb +14 -6
  3. data/lib/new_relic/agent/configuration/defaults.rb +8 -3
  4. data/lib/new_relic/agent/configuration/manager.rb +32 -1
  5. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +2 -2
  6. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +7 -11
  7. data/lib/new_relic/agent/instrumentation/resque.rb +2 -1
  8. data/lib/new_relic/agent/method_tracer.rb +4 -6
  9. data/lib/new_relic/agent/pipe_service.rb +4 -0
  10. data/lib/new_relic/agent/request_sampler.rb +46 -1
  11. data/lib/new_relic/agent/stats_engine/metric_stats.rb +11 -10
  12. data/lib/new_relic/agent/stats_engine/stats_hash.rb +12 -0
  13. data/lib/new_relic/agent/stats_engine/transactions.rb +1 -28
  14. data/lib/new_relic/agent/thread_profiler.rb +3 -3
  15. data/lib/new_relic/agent/transaction.rb +29 -15
  16. data/lib/new_relic/build.rb +2 -2
  17. data/lib/new_relic/noticed_error.rb +28 -12
  18. data/lib/new_relic/version.rb +1 -1
  19. data/test/agent_helper.rb +25 -0
  20. data/test/multiverse/suites/agent_only/key_transactions_test.rb +11 -11
  21. data/test/multiverse/suites/rails/request_statistics_test.rb +14 -20
  22. data/test/multiverse/suites/resque/Envfile +0 -1
  23. data/test/multiverse/suites/resque/config/newrelic.yml +1 -1
  24. data/test/multiverse/suites/resque/instrumentation_test.rb +16 -107
  25. data/test/new_relic/agent/agent/connect_test.rb +3 -3
  26. data/test/new_relic/agent/agent/start_test.rb +2 -0
  27. data/test/new_relic/agent/configuration/manager_test.rb +10 -0
  28. data/test/new_relic/agent/error_collector_test.rb +9 -9
  29. data/test/new_relic/agent/method_tracer_test.rb +8 -1
  30. data/test/new_relic/agent/request_sampler_test.rb +10 -0
  31. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +40 -4
  32. data/test/new_relic/agent/transaction_test.rb +23 -0
  33. data/test/new_relic/noticed_error_test.rb +84 -2
  34. data/test/test_helper.rb +2 -16
  35. data.tar.gz.sig +0 -0
  36. metadata +11 -6
  37. metadata.gz.sig +0 -0
data/CHANGELOG CHANGED
@@ -1,6 +1,19 @@
1
1
  # New Relic Ruby Agent Release Notes #
2
2
 
3
- ## v3.6.3 ##
3
+ ## v3.6.4 ##
4
+
5
+ * Exception Whitelist
6
+
7
+ We've improved exception message handling for applications running in
8
+ high security mode. Enabling 'high_security' now removes exception messages
9
+ entirely rather than simply obfuscating any SQL.
10
+
11
+ By default this feature affects all exceptions, though you can configure a
12
+ whitelist of exceptions whose messages should be left intact.
13
+
14
+ More details: https://newrelic.com/docs/ruby/ruby-agent-configuration
15
+
16
+ ## 3.6.3 ##
4
17
 
5
18
  * Better Sinatra Support
6
19
 
@@ -45,8 +45,9 @@ module NewRelic
45
45
  @request_sampler = NewRelic::Agent::RequestSampler.new(@events)
46
46
  @harvest_samplers = NewRelic::Agent::SamplerCollection.new(@events)
47
47
 
48
- @connect_state = :pending
49
- @connect_attempts = 0
48
+ @connect_state = :pending
49
+ @connect_attempts = 0
50
+ @environment_report = nil
50
51
 
51
52
  @last_harvest_time = Time.now
52
53
  @obfuscator = lambda {|sql| NewRelic::Agent::Database.default_sql_obfuscator(sql) }
@@ -203,8 +204,7 @@ module NewRelic
203
204
  # Clear out stats that are left over from parent process
204
205
  reset_stats
205
206
 
206
- # Don't ever check to see if this is a spawner. If we're in a forked process
207
- # I'm pretty sure we're not also forking new instances.
207
+ generate_environment_report
208
208
  start_worker_thread(options)
209
209
  end
210
210
 
@@ -463,6 +463,7 @@ module NewRelic
463
463
  def check_config_and_start_agent
464
464
  return unless monitoring? && has_correct_license_key?
465
465
  return if using_forking_dispatcher?
466
+ generate_environment_report
466
467
  connect_in_foreground if Agent.config[:sync_startup]
467
468
  start_worker_thread
468
469
  install_exit_handler
@@ -711,8 +712,15 @@ module NewRelic
711
712
  shutdown
712
713
  end
713
714
 
715
+ def generate_environment_report
716
+ @environment_report = environment_for_connect
717
+ end
718
+
714
719
  # Checks whether we should send environment info, and if so,
715
- # returns the snapshot from the local environment
720
+ # returns the snapshot from the local environment.
721
+ # Generating the EnvironmentReport has the potential to trigger
722
+ # require calls in Rails environments, so this method should only
723
+ # be called synchronously from on the main thread.
716
724
  def environment_for_connect
717
725
  Agent.config[:send_environment_info] ? Array(EnvironmentReport.new) : []
718
726
  end
@@ -726,7 +734,7 @@ module NewRelic
726
734
  :app_name => Agent.config.app_names,
727
735
  :language => 'ruby',
728
736
  :agent_version => NewRelic::VERSION::STRING,
729
- :environment => environment_for_connect,
737
+ :environment => @environment_report,
730
738
  :settings => Agent.config.to_collector_hash,
731
739
  }
732
740
  end
@@ -57,9 +57,9 @@ module NewRelic
57
57
  ::NewRelic::Agent::Autostart.agent_should_start?
58
58
  end,
59
59
  # Don't autostart the agent if we're in IRB or Rails console.
60
- # This config option accepts a comma seperated list of constants.
60
+ # This config option accepts a comma separated list of constants.
61
61
  :'autostart.blacklisted_constants' => 'Rails::Console',
62
- # Comma seperated list of executables that you don't want to trigger
62
+ # Comma separated list of executables that you don't want to trigger
63
63
  # agents start. e.g. 'rake,my_ruby_script.rb'
64
64
  :'autostart.blacklisted_executables' => 'irb',
65
65
  :'autostart.blacklisted_rake_tasks' => 'about,assets:clean,assets:clobber,assets:environment,assets:precompile,db:create,db:drop,db:fixtures:load,db:migrate,db:migrate:status,db:rollback,db:schema:cache:clear,db:schema:cache:dump,db:schema:dump,db:schema:load,db:seed,db:setup,db:structure:dump,db:version,doc:app,log:clear,middleware,notes,notes:custom,rails:template,rails:update,routes,secret,spec,spec:controllers,spec:helpers,spec:models,spec:rcov,stats,test,test:all,test:all:db,test:recent,test:single,test:uncommitted,time:zones:all,tmp:clear,tmp:create',
@@ -69,6 +69,11 @@ module NewRelic
69
69
  :monitor_daemons => false,
70
70
  :multi_homed => false,
71
71
  :high_security => false,
72
+ # Strip messages from all exceptions that are not specified in the whitelist.
73
+ :'strip_exception_messages.enabled' => Proc.new { self[:high_security] },
74
+ # Comma separated list of exceptions that should show messages when
75
+ # strip_exception_messages is enabled (e.g. 'NewException, RelicException').
76
+ :'strip_exception_messages.whitelist' => '',
72
77
 
73
78
  :host => 'collector.newrelic.com',
74
79
  :api_host => 'rpm.newrelic.com',
@@ -143,7 +148,7 @@ module NewRelic
143
148
 
144
149
  :marshaller => Proc.new { NewRelic::Agent::NewRelicService::JsonMarshaller.is_supported? ? 'json' : 'pruby' },
145
150
 
146
- :'request_sampler.enabled' => false,
151
+ :'request_sampler.enabled' => true,
147
152
  :'request_sampler.sample_rate_ms' => 50
148
153
  ].freeze
149
154
  end
@@ -15,12 +15,20 @@ module NewRelic
15
15
  class Manager
16
16
  extend Forwardable
17
17
  def_delegators :@cache, :[], :has_key?
18
- attr_reader :config_stack # mainly for testing
18
+ attr_reader :config_stack, :stripped_exceptions_whitelist
19
19
 
20
20
  def initialize
21
21
  @config_stack = [ EnvironmentSource.new, DEFAULTS ]
22
22
  @cache = Hash.new {|hash,key| hash[key] = self.fetch(key) }
23
23
  @callbacks = Hash.new {|hash,key| hash[key] = [] }
24
+
25
+ register_callback(:'strip_exception_messages.whitelist') do |whitelist|
26
+ if whitelist
27
+ @stripped_exceptions_whitelist = parse_constant_list(whitelist).compact
28
+ else
29
+ @stripped_exceptions_whitelist = []
30
+ end
31
+ end
24
32
  end
25
33
 
26
34
  def apply_config(source, level=0)
@@ -146,6 +154,29 @@ module NewRelic
146
154
  "Updating config (#{direction}) from #{source.class}. Results:",
147
155
  flattened.inspect)
148
156
  end
157
+
158
+ private
159
+
160
+ def parse_constant_list(list)
161
+ list.split(/\s*,\s*/).map do |class_name|
162
+ const = constantize(class_name)
163
+
164
+ unless const
165
+ NewRelic::Agent.logger.warn "Configuration referenced undefined constant: #{class_name}"
166
+ end
167
+
168
+ const
169
+ end
170
+ end
171
+
172
+ def constantize(class_name)
173
+ namespaces = class_name.split('::')
174
+
175
+ namespaces.inject(Object) do |namespace, name|
176
+ return unless namespace
177
+ namespace.const_get(name) if namespace.const_defined?(name)
178
+ end
179
+ end
149
180
  end
150
181
  end
151
182
  end
@@ -72,8 +72,8 @@ module NewRelic
72
72
  end
73
73
 
74
74
  def record_metric_on_parent_transaction(metric, time)
75
- Agent.instance.stats_engine.transaction_stats_stack[-2] \
76
- .record(metric, time)
75
+ txn = NewRelic::Agent::Transaction.current
76
+ txn.parent.stats_hash.record(metric, time)
77
77
  end
78
78
 
79
79
  def record_apdex(event)
@@ -331,20 +331,16 @@ module NewRelic
331
331
  end
332
332
 
333
333
  ensure
334
+ end_time = Time.now
335
+
334
336
  txn.freeze_name
335
- metrics = recorded_metrics(txn)
336
- txn_name = metrics.first
337
+ metric_names = Array(recorded_metrics(txn))
338
+ txn_name = metric_names.shift
337
339
 
338
- metric_names = Array(metrics)
339
- first_name = metric_names.shift
340
- scope = NewRelic::Agent.instance.stats_engine.scope_name
341
- end_time = Time.now
342
- NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScoped.trace_execution_scoped_footer(txn.start_time.to_f, first_name, metric_names, expected_scope, scope, options, end_time.to_f)
343
- NewRelic::Agent::BusyCalculator.dispatcher_finish
344
- # Look for a transaction in the thread local and process it.
345
- # Clear the thread local when finished to ensure it only gets called once.
340
+ NewRelic::Agent::MethodTracer::InstanceMethods::TraceExecutionScoped.trace_execution_scoped_footer(txn.start_time.to_f, txn_name, metric_names, expected_scope, options, end_time.to_f)
341
+ NewRelic::Agent::BusyCalculator.dispatcher_finish(end_time)
342
+ txn.record_apdex(txn_name, end_time) unless ignore_apdex?
346
343
  txn = Transaction.stop(txn_name, end_time)
347
- txn.record_apdex(txn_name) unless ignore_apdex?
348
344
 
349
345
  NewRelic::Agent::TransactionInfo.get.ignore_end_user = true if ignore_enduser?
350
346
  end
@@ -34,7 +34,8 @@ DependencyDetection.defer do
34
34
  yield(*args)
35
35
  end
36
36
  ensure
37
- NewRelic::Agent.shutdown if NewRelic::LanguageSupport.can_fork?
37
+ NewRelic::Agent.shutdown if NewRelic::LanguageSupport.can_fork? &&
38
+ (!Resque.respond_to?(:inline) || !Resque.inline)
38
39
  end
39
40
  end
40
41
  end
@@ -206,8 +206,7 @@ module NewRelic
206
206
  end
207
207
 
208
208
  def has_parent?
209
- NewRelic::Agent::Transaction.current &&
210
- NewRelic::Agent::Transaction.current.has_parent?
209
+ !NewRelic::Agent::Transaction.parent.nil?
211
210
  end
212
211
 
213
212
  def metrics_for_parent_transaction(first_name, options)
@@ -226,7 +225,7 @@ module NewRelic
226
225
 
227
226
  parent_metrics = metrics_for_parent_transaction(first_name, options)
228
227
  parent_metrics.each do |metric|
229
- stat_engine.transaction_stats_stack[-2].record(metric) do |stats|
228
+ NewRelic::Agent::Transaction.parent.stats_hash.record(metric) do |stats|
230
229
  stats.record_data_point(duration, exclusive)
231
230
  end
232
231
  end
@@ -240,7 +239,7 @@ module NewRelic
240
239
  # this method fails safely if the header does not manage to
241
240
  # push the scope onto the stack - it simply does not trace
242
241
  # any metrics.
243
- def trace_execution_scoped_footer(t0, first_name, metric_names, expected_scope, scope, options, t1=Time.now.to_f)
242
+ def trace_execution_scoped_footer(t0, first_name, metric_names, expected_scope, options, t1=Time.now.to_f)
244
243
  log_errors("trace_method_execution footer") do
245
244
  pop_flag!(options[:force])
246
245
  if expected_scope
@@ -267,12 +266,11 @@ module NewRelic
267
266
  set_if_nil(options, :deduct_call_time_from_parent)
268
267
  metric_names = Array(metric_names)
269
268
  first_name = metric_names.shift
270
- scope = stat_engine.scope_name
271
269
  start_time, expected_scope = trace_execution_scoped_header(options)
272
270
  begin
273
271
  yield
274
272
  ensure
275
- trace_execution_scoped_footer(start_time, first_name, metric_names, expected_scope, scope, options)
273
+ trace_execution_scoped_footer(start_time, first_name, metric_names, expected_scope, options)
276
274
  end
277
275
  end
278
276
 
@@ -28,6 +28,10 @@ module NewRelic
28
28
  []
29
29
  end
30
30
 
31
+ def analytic_event_data(data)
32
+ nil
33
+ end
34
+
31
35
  def metric_data(last_harvest_time, now, unsent_timeslice_data)
32
36
  write_to_pipe(:stats => unsent_timeslice_data)
33
37
  {}
@@ -22,6 +22,11 @@ class NewRelic::Agent::RequestSampler
22
22
  # :TODO: Get this from the agent instead?
23
23
  DEFAULT_REPORT_FREQUENCY = 60
24
24
 
25
+ # Regardless of whether #throttle is successfully called, we will store
26
+ # at most this many harvest-cycles worth of samples total, to avoid unbounded
27
+ # memory growth when there's a low-level failure talking to the collector.
28
+ MAX_FAILED_REPORT_RETENTION = 10
29
+
25
30
  # The namespace and keys of config values
26
31
  CONFIG_NAMESPACE = 'request_sampler'
27
32
  SAMPLE_RATE_KEY = "#{CONFIG_NAMESPACE}.sample_rate_ms".to_sym
@@ -46,8 +51,13 @@ class NewRelic::Agent::RequestSampler
46
51
  @sample_rate_ms = DEFAULT_SAMPLE_RATE_MS
47
52
  @normal_sample_rate_ms = @sample_rate_ms
48
53
  @last_sample_taken = nil
49
- @last_harvest = nil
50
54
  @samples = []
55
+ @notified_max_samples = false
56
+
57
+ @sample_count = 0
58
+ @request_count = 0
59
+ @sample_count_total = 0
60
+ @request_count_total = 0
51
61
 
52
62
  event_listener.subscribe( :transaction_finished, &method(:on_transaction_finished) )
53
63
  self.register_config_callbacks
@@ -80,13 +90,33 @@ class NewRelic::Agent::RequestSampler
80
90
  def reset
81
91
  NewRelic::Agent.logger.debug "Resetting RequestSampler"
82
92
 
93
+ request_count = nil
94
+ sample_count = nil
95
+
83
96
  self.synchronize do
97
+ sample_count = @samples.size
98
+ request_count = @request_count
99
+ @request_count = 0
84
100
  @samples.clear
85
101
  @sample_rate_ms = @normal_sample_rate_ms
86
102
  @last_sample_taken = Time.now
103
+ @notified_max_samples = false
87
104
  end
105
+
106
+ record_sampling_rate(request_count, sample_count) if @enabled
88
107
  end
89
108
 
109
+ def record_sampling_rate(request_count, sample_count)
110
+ @request_count_total += request_count
111
+ @sample_count_total += sample_count
112
+
113
+ NewRelic::Agent.logger.debug("Sampled #{sample_count} / #{request_count} (%.1f %%) requests this cycle" % (sample_count.to_f / request_count * 100.0))
114
+ NewRelic::Agent.logger.debug("Sampled #{@sample_count_total} / #{@request_count_total} (%.1f %%) requests since startup" % (@sample_count_total.to_f / @request_count_total * 100.0))
115
+
116
+ engine = NewRelic::Agent.instance.stats_engine
117
+ engine.record_supportability_metric_count("RequestSampler/requests", request_count)
118
+ engine.record_supportability_metric_count("RequestSampler/samples", sample_count)
119
+ end
90
120
 
91
121
  #
92
122
  # :group: Event handlers
@@ -103,6 +133,8 @@ class NewRelic::Agent::RequestSampler
103
133
  end
104
134
 
105
135
  @normal_sample_rate_ms = rate_ms
136
+ @max_samples = calculate_max_samples
137
+ NewRelic::Agent.logger.debug "RequestSampler max_samples set to #{@max_samples}"
106
138
  self.reset
107
139
  end
108
140
 
@@ -140,6 +172,7 @@ class NewRelic::Agent::RequestSampler
140
172
  # This method is synchronized.
141
173
  def <<( sample )
142
174
  self.synchronize do
175
+ @request_count += 1
143
176
  self.add_sample( sample ) if should_sample?
144
177
  end
145
178
 
@@ -176,10 +209,22 @@ class NewRelic::Agent::RequestSampler
176
209
  protected
177
210
  #########
178
211
 
212
+ def calculate_max_samples
213
+ max_samples_per_harvest = (DEFAULT_REPORT_FREQUENCY * 1000.0) / @normal_sample_rate_ms
214
+ max_samples_per_harvest * MAX_FAILED_REPORT_RETENTION
215
+ end
216
+
179
217
  # Returns +true+ if a sample added now should be kept based on the sample
180
218
  # frequency.
181
219
  def should_sample?
182
220
  return false unless @last_sample_taken
221
+ if @samples.size >= @max_samples
222
+ unless @notified_max_samples
223
+ NewRelic::Agent.logger.warn("Reached maximum of #{@max_samples} samples, ceasing collection")
224
+ @notified_max_samples = true
225
+ end
226
+ return false
227
+ end
183
228
  return ((Time.now - @last_sample_taken) * 1000).ceil >= @sample_rate_ms
184
229
  end
185
230
 
@@ -74,27 +74,28 @@ module NewRelic
74
74
  end
75
75
 
76
76
  # Helper method for timing supportability metrics
77
- def record_supportability_metrics_timed(metric)
77
+ def record_supportability_metric_timed(metric)
78
78
  start_time = Time.now
79
79
  yield
80
- end_time = Time.now
81
- duration = (end_time - start_time).to_f
82
80
  ensure
83
- ::NewRelic::Agent.record_metric(metric, duration)
81
+ duration = (Time.now - start_time).to_f
82
+ record_supportability_metric(metric, duration)
84
83
  end
85
84
 
86
85
  # Helper for recording a straight value into the count
87
- def record_supportability_metrics_count(value, *metrics)
88
- record_metrics(metrics) do |stat|
86
+ def record_supportability_metric_count(metric, value)
87
+ record_supportability_metric(metric) do |stat|
89
88
  stat.call_count = value
90
89
  end
91
90
  end
92
91
 
93
92
  # Helper method for recording supportability metrics consistently
94
- def record_supportability_metrics(value, *metrics)
95
- real_names = metrics.map { |name| "Supportability/#{name}" }
96
- NewRelic::Agent.agent.record_metric(real_names) do |stat|
97
- yield stat
93
+ def record_supportability_metric(metric, value=nil)
94
+ real_name = "Supportability/#{metric}"
95
+ if block_given?
96
+ record_metrics(real_name) { |stat| yield stat }
97
+ else
98
+ record_metrics(real_name, value)
98
99
  end
99
100
  end
100
101
 
@@ -57,6 +57,18 @@ module NewRelic
57
57
  end
58
58
  self
59
59
  end
60
+
61
+ def resolve_scopes(resolved_scope)
62
+ new_stats = self.class.new
63
+ self.each do |spec, stats|
64
+ if spec.scope != '' &&
65
+ spec.scope.to_sym == StatsEngine::SCOPE_PLACEHOLDER
66
+ spec.scope = resolved_scope
67
+ end
68
+ new_stats[spec] = stats
69
+ end
70
+ return new_stats
71
+ end
60
72
  end
61
73
  end
62
74
  end
@@ -121,40 +121,13 @@ module Agent
121
121
  end
122
122
 
123
123
  def transaction_stats_hash
124
- transaction_stats_stack.last
125
- end
126
-
127
- def push_transaction_stats
128
- transaction_stats_stack << StatsHash.new
129
- end
130
-
131
- def pop_transaction_stats(transaction_name)
132
- Thread::current[:newrelic_scope_stack] ||= []
133
- stats = transaction_stats_stack.pop
134
- merge!(apply_scopes(stats, transaction_name)) if stats
135
- stats
136
- end
137
-
138
- def apply_scopes(stats_hash, resolved_name)
139
- new_stats = StatsHash.new
140
- stats_hash.each do |spec, stats|
141
- if spec.scope != '' &&
142
- spec.scope.to_sym == StatsEngine::SCOPE_PLACEHOLDER
143
- spec.scope = resolved_name
144
- end
145
- new_stats[spec] = stats
146
- end
147
- return new_stats
124
+ Transaction.current && Transaction.current.stats_hash
148
125
  end
149
126
 
150
127
  # Returns the current scope stack, memoized to a thread local variable
151
128
  def scope_stack
152
129
  Thread::current[:newrelic_scope_stack] ||= []
153
130
  end
154
-
155
- def transaction_stats_stack
156
- Thread.current[:newrelic_transaction_stack] ||= []
157
- end
158
131
  end
159
132
  end
160
133
  end