newrelic_rpm 3.4.2.1 → 3.5.0

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 (49) hide show
  1. data/CHANGELOG +47 -2
  2. data/lib/new_relic/agent.rb +5 -5
  3. data/lib/new_relic/agent/agent.rb +88 -177
  4. data/lib/new_relic/agent/beacon_configuration.rb +33 -47
  5. data/lib/new_relic/agent/browser_monitoring.rb +26 -33
  6. data/lib/new_relic/agent/configuration/defaults.rb +21 -13
  7. data/lib/new_relic/agent/configuration/manager.rb +28 -14
  8. data/lib/new_relic/agent/configuration/server_source.rb +8 -5
  9. data/lib/new_relic/agent/database.rb +37 -22
  10. data/lib/new_relic/agent/error_collector.rb +32 -31
  11. data/lib/new_relic/agent/instrumentation/unicorn_instrumentation.rb +4 -3
  12. data/lib/new_relic/agent/new_relic_service.rb +21 -19
  13. data/lib/new_relic/agent/pipe_channel_manager.rb +13 -13
  14. data/lib/new_relic/agent/sql_sampler.rb +9 -28
  15. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +21 -24
  16. data/lib/new_relic/agent/stats_engine/transactions.rb +20 -12
  17. data/lib/new_relic/agent/transaction_sample_builder.rb +5 -3
  18. data/lib/new_relic/agent/transaction_sampler.rb +43 -47
  19. data/lib/new_relic/control/frameworks/rails.rb +9 -4
  20. data/lib/new_relic/control/frameworks/rails3.rb +10 -0
  21. data/lib/new_relic/noticed_error.rb +18 -8
  22. data/lib/new_relic/rack.rb +4 -0
  23. data/lib/new_relic/rack/browser_monitoring.rb +2 -0
  24. data/lib/new_relic/rack/error_collector.rb +56 -0
  25. data/lib/new_relic/version.rb +3 -3
  26. data/newrelic.yml +0 -12
  27. data/newrelic_rpm.gemspec +6 -3
  28. data/test/new_relic/agent/agent/connect_test.rb +78 -113
  29. data/test/new_relic/agent/agent/start_test.rb +2 -2
  30. data/test/new_relic/agent/agent/start_worker_thread_test.rb +6 -33
  31. data/test/new_relic/agent/agent_test.rb +20 -6
  32. data/test/new_relic/agent/agent_test_controller_test.rb +7 -5
  33. data/test/new_relic/agent/beacon_configuration_test.rb +54 -60
  34. data/test/new_relic/agent/browser_monitoring_test.rb +88 -74
  35. data/test/new_relic/agent/configuration/manager_test.rb +21 -21
  36. data/test/new_relic/agent/configuration/server_source_test.rb +21 -4
  37. data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +2 -2
  38. data/test/new_relic/agent/mock_scope_listener.rb +3 -0
  39. data/test/new_relic/agent/new_relic_service_test.rb +56 -17
  40. data/test/new_relic/agent/pipe_channel_manager_test.rb +4 -1
  41. data/test/new_relic/agent/rpm_agent_test.rb +1 -0
  42. data/test/new_relic/agent/stats_engine_test.rb +12 -7
  43. data/test/new_relic/agent/transaction_sampler_test.rb +106 -102
  44. data/test/new_relic/agent_test.rb +10 -9
  45. data/test/new_relic/control_test.rb +1 -17
  46. data/test/new_relic/rack/browser_monitoring_test.rb +11 -5
  47. data/test/new_relic/rack/error_collector_test.rb +74 -0
  48. data/test/test_helper.rb +1 -1
  49. metadata +9 -7
@@ -10,22 +10,19 @@ module NewRelic
10
10
  module Shim #:nodoc:
11
11
  def notice_error(*args); end
12
12
  end
13
-
13
+
14
14
  # Maximum possible length of the queue - defaults to 20, may be
15
15
  # made configurable in the future. This is a tradeoff between
16
16
  # memory and data retention
17
17
  MAX_ERROR_QUEUE_LENGTH = 20 unless defined? MAX_ERROR_QUEUE_LENGTH
18
18
 
19
- attr_accessor :enabled, :errors
20
- attr_reader :config_enabled
21
-
19
+ attr_accessor :errors
20
+
22
21
  # Returns a new error collector
23
22
  def initialize
24
23
  @errors = []
25
24
  # lookup of exception class names to ignore. Hash for fast access
26
25
  @ignore = {}
27
-
28
- @enabled = @config_enabled = Agent.config[:'error_collector.enabled']
29
26
  @capture_source = Agent.config[:'error_collector.capture_source']
30
27
 
31
28
  ignore_errors = Agent.config[:'error_collector.ignore_errors']
@@ -33,6 +30,14 @@ module NewRelic
33
30
  ignore_errors.each { |error| error.strip! }
34
31
  ignore(ignore_errors)
35
32
  @lock = Mutex.new
33
+
34
+ Agent.config.register_callback(:'error_collector.enabled') do |config_enabled|
35
+ log.debug "Errors will #{config_enabled ? '' : 'not '}be sent to the New Relic service."
36
+ end
37
+ end
38
+
39
+ def enabled?
40
+ Agent.config[:'error_collector.enabled']
36
41
  end
37
42
 
38
43
  # Returns the error filter proc that is used to check if an
@@ -57,43 +62,38 @@ module NewRelic
57
62
  log.debug("Ignoring errors of type '#{error}'")
58
63
  end
59
64
  end
60
-
65
+
61
66
  # This module was extracted from the notice_error method - it is
62
67
  # internally tested and can be refactored without major issues.
63
68
  module NoticeError
64
- # Whether the error collector is disabled or not
65
- def disabled?
66
- !@enabled
67
- end
68
-
69
69
  # Checks the provided error against the error filter, if there
70
70
  # is an error filter
71
71
  def filtered_by_error_filter?(error)
72
72
  return unless @ignore_filter
73
73
  !@ignore_filter.call(error)
74
74
  end
75
-
75
+
76
76
  # Checks the array of error names and the error filter against
77
77
  # the provided error
78
78
  def filtered_error?(error)
79
79
  @ignore[error.class.name] || filtered_by_error_filter?(error)
80
80
  end
81
-
81
+
82
82
  # an error is ignored if it is nil or if it is filtered
83
83
  def error_is_ignored?(error)
84
84
  error && filtered_error?(error)
85
85
  end
86
-
86
+
87
87
  # Increments a statistic that tracks total error rate
88
88
  def increment_error_count!
89
89
  NewRelic::Agent.get_stats("Errors/all").increment_count
90
90
  end
91
-
91
+
92
92
  # whether we should return early from the notice_error process
93
93
  # - based on whether the error is ignored or the error
94
94
  # collector is disabled
95
95
  def should_exit_notice_error?(exception)
96
- if @enabled
96
+ if enabled?
97
97
  if !error_is_ignored?(exception)
98
98
  increment_error_count!
99
99
  return exception.nil? # exit early if the exception is nil
@@ -102,12 +102,12 @@ module NewRelic
102
102
  # disabled or an ignored error, per above
103
103
  true
104
104
  end
105
-
105
+
106
106
  # acts just like Hash#fetch, but deletes the key from the hash
107
107
  def fetch_from_options(options, key, default=nil)
108
108
  options.delete(key) || default
109
109
  end
110
-
110
+
111
111
  # returns some basic option defaults pulled from the provided
112
112
  # options hash
113
113
  def uri_ref_and_root(options)
@@ -117,13 +117,13 @@ module NewRelic
117
117
  :rails_root => NewRelic::Control.instance.root
118
118
  }
119
119
  end
120
-
120
+
121
121
  # If anything else is left over, we treat it like a custom param
122
122
  def custom_params_from_opts(options)
123
123
  # If anything else is left over, treat it like a custom param:
124
124
  fetch_from_options(options, :custom_params, {}).merge(options)
125
125
  end
126
-
126
+
127
127
  # takes the request parameters out of the options hash, and
128
128
  # returns them if we are capturing parameters, otherwise
129
129
  # returns nil
@@ -135,7 +135,7 @@ module NewRelic
135
135
  nil
136
136
  end
137
137
  end
138
-
138
+
139
139
  # normalizes the request and custom parameters before attaching
140
140
  # them to the error. See NewRelic::CollectionHelper#normalize_params
141
141
  def normalized_request_and_custom_params(options)
@@ -144,32 +144,32 @@ module NewRelic
144
144
  :custom_params => normalize_params(custom_params_from_opts(options))
145
145
  }
146
146
  end
147
-
147
+
148
148
  # Merges together many of the options into something that can
149
149
  # actually be attached to the error
150
150
  def error_params_from_options(options)
151
151
  uri_ref_and_root(options).merge(normalized_request_and_custom_params(options))
152
152
  end
153
-
153
+
154
154
  # calls a method on an object, if it responds to it - used for
155
155
  # detection and soft fail-safe. Returns nil if the method does
156
156
  # not exist
157
157
  def sense_method(object, method)
158
158
  object.send(method) if object.respond_to?(method)
159
159
  end
160
-
160
+
161
161
  # extracts source from the exception, if the exception supports
162
162
  # that method
163
163
  def extract_source(exception)
164
164
  sense_method(exception, 'source_extract') if @capture_source
165
165
  end
166
-
166
+
167
167
  # extracts a stack trace from the exception for debugging purposes
168
168
  def extract_stack_trace(exception)
169
169
  actual_exception = sense_method(exception, 'original_exception') || exception
170
170
  sense_method(actual_exception, 'backtrace') || '<no stack trace>'
171
171
  end
172
-
172
+
173
173
  # extracts a bunch of information from the exception to include
174
174
  # in the noticed error - some may or may not be available, but
175
175
  # we try to include all of it
@@ -181,7 +181,7 @@ module NewRelic
181
181
  :stack_trace => extract_stack_trace(exception)
182
182
  }
183
183
  end
184
-
184
+
185
185
  # checks the size of the error queue to make sure we are under
186
186
  # the maximum limit, and logs a warning if we are over the limit.
187
187
  def over_queue_limit?(message)
@@ -190,13 +190,14 @@ module NewRelic
190
190
  over_limit
191
191
  end
192
192
 
193
-
194
193
  # Synchronizes adding an error to the error queue, and checks if
195
194
  # the error queue is too long - if so, we drop the error on the
196
195
  # floor after logging a warning.
197
196
  def add_to_error_queue(noticed_error)
198
197
  @lock.synchronize do
199
- @errors << noticed_error unless over_queue_limit?(noticed_error.message)
198
+ if !over_queue_limit?(noticed_error.message) && !@errors.include?(noticed_error)
199
+ @errors << noticed_error
200
+ end
200
201
  end
201
202
  end
202
203
  end
@@ -220,7 +221,7 @@ module NewRelic
220
221
  add_to_error_queue(NewRelic::NoticedError.new(action_path, exception_options, exception))
221
222
  exception
222
223
  rescue => e
223
- log.error("Error capturing an error, yodawg. #{e}")
224
+ log.error("Error capturing an error #{e}")
224
225
  end
225
226
 
226
227
  # Get the errors currently queued up. Unsent errors are left
@@ -4,15 +4,16 @@ DependencyDetection.defer do
4
4
  depends_on do
5
5
  defined?(::Unicorn) && defined?(::Unicorn::HttpServer)
6
6
  end
7
-
7
+
8
8
  executes do
9
9
  NewRelic::Agent.logger.debug 'Installing Unicorn instrumentation'
10
+ NewRelic::Agent.logger.info 'Detected Unicorn, please see additional documentation: https://newrelic.com/docs/troubleshooting/im-using-unicorn-and-i-dont-see-any-data'
10
11
  end
11
-
12
+
12
13
  executes do
13
14
  Unicorn::HttpServer.class_eval do
14
15
  old_worker_loop = instance_method(:worker_loop)
15
- define_method(:worker_loop) do | worker |
16
+ define_method(:worker_loop) do |worker|
16
17
  NewRelic::Agent.after_fork(:force_reconnect => true)
17
18
  old_worker_loop.bind(self).call(worker)
18
19
  end
@@ -3,8 +3,8 @@ module NewRelic
3
3
  class NewRelicService
4
4
  # Specifies the version of the agent's communication protocol with
5
5
  # the NewRelic hosted site.
6
-
7
- PROTOCOL_VERSION = 8
6
+
7
+ PROTOCOL_VERSION = 9
8
8
  # 14105: v8 (tag 2.10.3)
9
9
  # (no v7)
10
10
  # 10379: v6 (not tagged)
@@ -12,17 +12,17 @@ module NewRelic
12
12
  # 2292: v4 (tag 2.3.6)
13
13
  # 1754: v3 (tag 2.3.0)
14
14
  # 534: v2 (shows up in 2.1.0, our first tag)
15
-
15
+
16
16
  attr_accessor :request_timeout
17
17
  attr_reader :collector
18
18
  attr_accessor :agent_id
19
-
19
+
20
20
  def initialize(license_key=nil, collector=control.server)
21
21
  @license_key = license_key || Agent.config[:license_key]
22
22
  @collector = collector
23
23
  @request_timeout = Agent.config[:timeout]
24
24
  end
25
-
25
+
26
26
  def connect(settings={})
27
27
  if host = get_redirect_host
28
28
  @collector = NewRelic::Control.instance.server_from_host(host)
@@ -44,7 +44,7 @@ module NewRelic
44
44
  invoke_remote(:metric_data, @agent_id, last_harvest_time, now,
45
45
  unsent_timeslice_data)
46
46
  end
47
-
47
+
48
48
  def error_data(unsent_errors)
49
49
  invoke_remote(:error_data, @agent_id, unsent_errors)
50
50
  end
@@ -58,24 +58,24 @@ module NewRelic
58
58
  end
59
59
 
60
60
  private
61
-
61
+
62
62
  # A shorthand for NewRelic::Control.instance
63
63
  def control
64
64
  NewRelic::Control.instance
65
65
  end
66
-
66
+
67
67
  # Shorthand to the NewRelic::Agent.logger method
68
68
  def log
69
69
  NewRelic::Agent.logger
70
70
  end
71
-
71
+
72
72
  # The path on the server that we should post our data to
73
73
  def remote_method_uri(method)
74
74
  uri = "/agent_listener/#{PROTOCOL_VERSION}/#{@license_key}/#{method}"
75
75
  uri << "?run_id=#{@agent_id}" if @agent_id
76
76
  uri
77
77
  end
78
-
78
+
79
79
  # send a message via post to the actual server. This attempts
80
80
  # to automatically compress the data via zlib if it is large
81
81
  # enough to be worth compressing, and handles any errors the
@@ -84,7 +84,7 @@ module NewRelic
84
84
  now = Time.now
85
85
  #determines whether to zip the data or send plain
86
86
  post_data, encoding = compress_data(args)
87
-
87
+
88
88
  response = send_request(:uri => remote_method_uri(method),
89
89
  :encoding => encoding,
90
90
  :collector => @collector,
@@ -140,13 +140,13 @@ module NewRelic
140
140
  raise
141
141
  end
142
142
 
143
- # Raises a PostTooBigException if the post_string is longer
143
+ # Raises an UnrecoverableServerException if the post_string is longer
144
144
  # than the limit configured in the control object
145
145
  def check_post_size(post_string)
146
146
  # TODO: define this as a config option on the server side
147
147
  return if post_string.size < Agent.config[:post_size_limit]
148
- log.warn "Tried to send too much data: #{post_string.size} bytes"
149
- raise PostTooBigException
148
+ log.debug "Tried to send too much data: #{post_string.size} bytes"
149
+ raise UnrecoverableServerException.new('413 Request Entity Too Large')
150
150
  end
151
151
 
152
152
  # Posts to the specified server
@@ -164,9 +164,9 @@ module NewRelic
164
164
  request['user-agent'] = user_agent
165
165
  request.content_type = "application/octet-stream"
166
166
  request.body = opts[:data]
167
-
167
+
168
168
  log.debug "Connect to #{opts[:collector]}#{opts[:uri]}"
169
-
169
+
170
170
  response = nil
171
171
  http = control.http_connection(@collector)
172
172
  http.read_timeout = nil
@@ -179,14 +179,16 @@ module NewRelic
179
179
  raise
180
180
  end
181
181
  if response.is_a? Net::HTTPServiceUnavailable
182
- raise NewRelic::Agent::ServerConnectionException, "Service unavailable (#{response.code}): #{response.message}"
182
+ raise ServerConnectionException, "Service unavailable (#{response.code}): #{response.message}"
183
183
  elsif response.is_a? Net::HTTPGatewayTimeOut
184
184
  log.debug("Timed out getting response: #{response.message}")
185
185
  raise Timeout::Error, response.message
186
186
  elsif response.is_a? Net::HTTPRequestEntityTooLarge
187
- raise PostTooBigException
187
+ raise UnrecoverableServerException, '413 Request Entity Too Large'
188
+ elsif response.is_a? Net::HTTPUnsupportedMediaType
189
+ raise UnrecoverableServerException, '415 Unsupported Media Type'
188
190
  elsif !(response.is_a? Net::HTTPSuccess)
189
- raise NewRelic::Agent::ServerConnectionException, "Unexpected response from server (#{response.code}): #{response.message}"
191
+ raise ServerConnectionException, "Unexpected response from server (#{response.code}): #{response.message}"
190
192
  end
191
193
  response
192
194
  end
@@ -17,15 +17,15 @@ module NewRelic
17
17
  def channels
18
18
  listener.pipes
19
19
  end
20
-
20
+
21
21
  def listener
22
22
  @listener ||= Listener.new
23
23
  end
24
-
24
+
25
25
  class Pipe
26
26
  attr_accessor :in, :out
27
27
  attr_reader :last_read
28
-
28
+
29
29
  def initialize
30
30
  @out, @in = IO.pipe
31
31
  if defined?(::Encoding::ASCII_8BIT)
@@ -33,7 +33,7 @@ module NewRelic
33
33
  end
34
34
  @last_read = Time.now
35
35
  end
36
-
36
+
37
37
  def close
38
38
  @out.close unless @out.closed?
39
39
  @in.close unless @in.closed?
@@ -46,7 +46,7 @@ module NewRelic
46
46
  end
47
47
  @in << "\n\n"
48
48
  end
49
-
49
+
50
50
  def read
51
51
  @in.close unless @in.closed?
52
52
  @last_read = Time.now
@@ -57,7 +57,7 @@ module NewRelic
57
57
  @out.closed? && @in.closed?
58
58
  end
59
59
  end
60
-
60
+
61
61
  class Listener
62
62
  attr_reader :thread
63
63
  attr_accessor :pipes, :timeout, :select_timeout
@@ -80,7 +80,7 @@ module NewRelic
80
80
  loop do
81
81
  clean_up_pipes
82
82
  pipes_to_listen_to = @pipes.values.map{|pipe| pipe.out} + [wake.out]
83
- if ready = IO.select(pipes_to_listen_to, [], [], @select_timeout)
83
+ if ready = IO.select(pipes_to_listen_to, [], [], @select_timeout)
84
84
  pipe = ready[0][0]
85
85
  if pipe == wake.out
86
86
  pipe.read(1)
@@ -88,14 +88,14 @@ module NewRelic
88
88
  merge_data_from_pipe(pipe)
89
89
  end
90
90
  end
91
-
91
+
92
92
  break if !should_keep_listening?
93
93
  end
94
94
  end
95
95
  @thread #.abort_on_exception = true
96
96
  sleep 0.001 # give time for the thread to spawn
97
97
  end
98
-
98
+
99
99
  def stop
100
100
  return unless @started == true
101
101
  @started = false
@@ -112,7 +112,7 @@ module NewRelic
112
112
  end
113
113
  @pipes = {}
114
114
  end
115
-
115
+
116
116
  def wake
117
117
  @wake ||= Pipe.new
118
118
  end
@@ -126,7 +126,7 @@ module NewRelic
126
126
  def merge_data_from_pipe(pipe_handle)
127
127
  pipe = find_pipe_for_handle(pipe_handle)
128
128
  got = pipe.read
129
-
129
+
130
130
  if got && !got.empty?
131
131
  payload = unmarshal(got)
132
132
  if payload == 'EOF'
@@ -148,11 +148,11 @@ module NewRelic
148
148
  NewRelic::Control.instance.log.debug(msg)
149
149
  nil
150
150
  end
151
-
151
+
152
152
  def should_keep_listening?
153
153
  @started || @pipes.values.find{|pipe| !pipe.in.closed?}
154
154
  end
155
-
155
+
156
156
  def clean_up_pipes
157
157
  @pipes.values.each do |pipe|
158
158
  if pipe.last_read.to_f + @timeout < Time.now.to_f
@@ -18,7 +18,6 @@ module NewRelic
18
18
  attr_reader :sql_traces
19
19
 
20
20
  def initialize
21
- configure!
22
21
  @sql_traces = {}
23
22
  clear_transaction_data
24
23
 
@@ -28,38 +27,20 @@ module NewRelic
28
27
  @samples_lock = Mutex.new
29
28
  end
30
29
 
31
- def configure!
32
- @explain_threshold = Agent.config[:'slow_sql.explain_threshold']
33
- @explain_enabled = Agent.config[:'sloq_sql.explain_enabled']
34
- @stack_trace_threshold = Agent.config[:'slow_sql.stack_trace_threshold']
35
- if Agent.config[:'slow_sql.enabled']
36
- enable
37
- else
38
- disable
39
- end
40
- end
41
-
42
- # Enable the sql sampler - this also registers it with
43
- # the statistics engine.
44
- def enable
45
- @disabled = false
46
- end
47
-
48
- # Disable the sql sampler - this also deregisters it
49
- # with the statistics engine.
50
- def disable
51
- @disabled = true
52
- end
53
-
54
30
  def enabled?
55
- !@disabled
31
+ Agent.config[:'slow_sql.enabled'] &&
32
+ (Agent.config[:'slow_sql.record_sql'] == 'raw' ||
33
+ Agent.config[:'slow_sql.record_sql'] == 'obfuscated') &&
34
+ Agent.config[:'transaction_tracer.enabled']
56
35
  end
57
36
 
58
37
  def notice_transaction(path, uri=nil, params={})
59
38
  if NewRelic::Agent.instance.transaction_sampler.builder
60
39
  guid = NewRelic::Agent.instance.transaction_sampler.builder.sample.guid
61
40
  end
62
- transaction_data.set_transaction_info(path, uri, params, guid) if !disabled && transaction_data
41
+ if Agent.config[:'slow_sql.enabled'] && transaction_data
42
+ transaction_data.set_transaction_info(path, uri, params, guid)
43
+ end
63
44
  end
64
45
 
65
46
  def notice_first_scope_push(time)
@@ -111,7 +92,7 @@ module NewRelic
111
92
  def notice_sql(sql, metric_name, config, duration)
112
93
  return unless transaction_data
113
94
  if NewRelic::Agent.is_sql_recorded?
114
- if duration > @explain_threshold
95
+ if duration > Agent.config[:'slow_sql.explain_threshold']
115
96
  backtrace = caller.join("\n")
116
97
  transaction_data.sql_data << SlowSql.new(sql, metric_name, config,
117
98
  duration, backtrace)
@@ -127,7 +108,7 @@ module NewRelic
127
108
  end
128
109
 
129
110
  def harvest
130
- return [] if disabled
111
+ return [] if !Agent.config[:'slow_sql.enabled']
131
112
  result = []
132
113
  @samples_lock.synchronize do
133
114
  result = @sql_traces.values