newrelic_rpm 3.15.1.316 → 3.15.2.317

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +41 -0
  3. data/lib/new_relic/agent/agent.rb +1 -1
  4. data/lib/new_relic/agent/autostart.rb +1 -1
  5. data/lib/new_relic/agent/database.rb +72 -165
  6. data/lib/new_relic/agent/database/explain_plan_helpers.rb +127 -0
  7. data/lib/new_relic/agent/database/obfuscation_helpers.rb +2 -1
  8. data/lib/new_relic/agent/database/postgres_explain_obfuscator.rb +0 -2
  9. data/lib/new_relic/agent/instrumentation/active_record.rb +5 -5
  10. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +10 -8
  11. data/lib/new_relic/agent/instrumentation/data_mapper.rb +2 -1
  12. data/lib/new_relic/agent/instrumentation/grape.rb +23 -9
  13. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +1 -6
  14. data/lib/new_relic/agent/instrumentation/padrino.rb +36 -0
  15. data/lib/new_relic/agent/method_tracer.rb +20 -18
  16. data/lib/new_relic/agent/new_relic_service.rb +3 -33
  17. data/lib/new_relic/agent/pipe_service.rb +0 -4
  18. data/lib/new_relic/agent/sql_sampler.rb +3 -3
  19. data/lib/new_relic/agent/transaction/trace_node.rb +2 -5
  20. data/lib/new_relic/agent/transaction_sampler.rb +2 -2
  21. data/lib/new_relic/metric_data.rb +6 -14
  22. data/lib/new_relic/version.rb +1 -1
  23. data/newrelic_rpm.gemspec +1 -1
  24. data/test/environments/lib/environments/runner.rb +1 -1
  25. data/test/environments/rails40/Gemfile +9 -0
  26. data/test/environments/rails41/Gemfile +9 -0
  27. data/test/environments/rails42/Gemfile +9 -0
  28. data/test/multiverse/lib/multiverse/suite.rb +12 -0
  29. data/test/multiverse/suites/agent_only/harvest_timestamps_test.rb +0 -31
  30. data/test/multiverse/suites/grape/Envfile +1 -1
  31. data/test/multiverse/suites/mongo/Envfile +3 -0
  32. data/test/multiverse/suites/mongo/mongo2_instrumentation_test.rb +3 -2
  33. data/test/multiverse/suites/padrino/Envfile +8 -0
  34. data/test/multiverse/suites/typhoeus/Envfile +21 -0
  35. data/test/new_relic/agent/database_test.rb +216 -144
  36. data/test/new_relic/agent/new_relic_service_test.rb +0 -58
  37. data/test/new_relic/metric_data_test.rb +24 -71
  38. data/test/new_relic/metric_spec_test.rb +3 -4
  39. data/test/new_relic/rack/developer_mode_test.rb +5 -2
  40. metadata +4 -2
@@ -40,6 +40,7 @@ module NewRelic
40
40
  # placeholder.
41
41
  CLEANUP_REGEX = {
42
42
  :mysql => /'|"|\/\*|\*\//,
43
+ :mysql2 => /'|"|\/\*|\*\//,
43
44
  :postgres => /'|\/\*|\*\/|\$(?!\?)/,
44
45
  :sqlite => /'|\/\*|\*\//,
45
46
  :cassandra => /'|\/\*|\*\//,
@@ -68,7 +69,7 @@ module NewRelic
68
69
 
69
70
  def obfuscate(sql, adapter)
70
71
  case adapter
71
- when :mysql
72
+ when :mysql, :mysql2
72
73
  regex = MYSQL_COMPONENTS_REGEX
73
74
  when :postgres
74
75
  regex = POSTGRES_COMPONENTS_REGEX
@@ -2,8 +2,6 @@
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
4
 
5
- require 'new_relic/agent/database/obfuscation_helpers'
6
-
7
5
  module NewRelic
8
6
  module Agent
9
7
  module Database
@@ -6,13 +6,13 @@ module NewRelic
6
6
  module Agent
7
7
  module Instrumentation
8
8
  module ActiveRecord
9
- EXPLAINER = lambda do |config, query|
10
- connection = NewRelic::Agent::Database.get_connection(config) do
11
- ::ActiveRecord::Base.send("#{config[:adapter]}_connection",
12
- config)
9
+ EXPLAINER = lambda do |statement|
10
+ connection = NewRelic::Agent::Database.get_connection(statement.config) do
11
+ ::ActiveRecord::Base.send("#{statement.config[:adapter]}_connection",
12
+ statement.config)
13
13
  end
14
14
  if connection && connection.respond_to?(:execute)
15
- return connection.execute("EXPLAIN #{query}")
15
+ return connection.execute("EXPLAIN #{statement.sql}")
16
16
  end
17
17
  end
18
18
 
@@ -40,13 +40,15 @@ module NewRelic
40
40
  log_notification_error(e, name, 'finish')
41
41
  end
42
42
 
43
- def get_explain_plan( config, query )
44
- connection = NewRelic::Agent::Database.get_connection(config) do
45
- ::ActiveRecord::Base.send("#{config[:adapter]}_connection",
46
- config)
43
+ def get_explain_plan(statement)
44
+ connection = NewRelic::Agent::Database.get_connection(statement.config) do
45
+ ::ActiveRecord::Base.send("#{statement.config[:adapter]}_connection",
46
+ statement.config)
47
47
  end
48
- if connection && connection.respond_to?(:execute)
49
- return connection.execute("EXPLAIN #{query}")
48
+ if connection && connection.respond_to?(:exec_query)
49
+ return connection.exec_query("EXPLAIN #{statement.sql}",
50
+ "Explain #{statement.name}",
51
+ statement.binds)
50
52
  end
51
53
  end
52
54
 
@@ -59,12 +61,12 @@ module NewRelic
59
61
  NewRelic::Agent.instance.transaction_sampler \
60
62
  .notice_sql(event.payload[:sql], config,
61
63
  Helper.milliseconds_to_seconds(event.duration),
62
- state, @explainer)
64
+ state, @explainer, event.payload[:binds], event.payload[:name])
63
65
 
64
66
  NewRelic::Agent.instance.sql_sampler \
65
67
  .notice_sql(event.payload[:sql], metric, config,
66
68
  Helper.milliseconds_to_seconds(event.duration),
67
- state, @explainer)
69
+ state, @explainer, event.payload[:binds], event.payload[:name])
68
70
 
69
71
  # exit transaction trace node
70
72
  stack.pop_frame(state, frame, metric, event.end)
@@ -147,7 +147,8 @@ module NewRelic
147
147
  strategy = NewRelic::Agent::Database.record_sql_method(:slow_sql)
148
148
  case strategy
149
149
  when :obfuscated
150
- statement = NewRelic::Agent::Database::Statement.new(e.query, :adapter => self.options[:adapter])
150
+ adapter_name = self.respond_to?(:options) ? self.options[:adapter] : self.repository.adapter.uri.scheme
151
+ statement = NewRelic::Agent::Database::Statement.new(e.query, :adapter => adapter_name)
151
152
  obfuscated_sql = NewRelic::Agent::Database.obfuscate_sql(statement)
152
153
  e.instance_variable_set(:@query, obfuscated_sql)
153
154
  when :off
@@ -28,15 +28,29 @@ module NewRelic
28
28
  Transaction.set_default_transaction_name(txn_name, :grape, node_name)
29
29
  end
30
30
 
31
- def name_for_transaction(route, class_name)
32
- action_name = route.route_path.sub(FORMAT_REGEX, EMPTY_STRING)
33
- method_name = route.route_method
34
-
35
- if route.route_version
36
- action_name = action_name.sub(VERSION_REGEX, EMPTY_STRING)
37
- "#{class_name}-#{route.route_version}#{action_name} (#{method_name})"
38
- else
39
- "#{class_name}#{action_name} (#{method_name})"
31
+ if defined?(Grape::VERSION) && VersionNumber.new(::Grape::VERSION) >= VersionNumber.new("0.16.0")
32
+ def name_for_transaction(route, class_name)
33
+ action_name = route.path.sub(FORMAT_REGEX, EMPTY_STRING)
34
+ method_name = route.request_method
35
+
36
+ if route.version
37
+ action_name = action_name.sub(VERSION_REGEX, EMPTY_STRING)
38
+ "#{class_name}-#{route.version}#{action_name} (#{method_name})"
39
+ else
40
+ "#{class_name}#{action_name} (#{method_name})"
41
+ end
42
+ end
43
+ else
44
+ def name_for_transaction(route, class_name)
45
+ action_name = route.route_path.sub(FORMAT_REGEX, EMPTY_STRING)
46
+ method_name = route.route_method
47
+
48
+ if route.route_version
49
+ action_name = action_name.sub(VERSION_REGEX, EMPTY_STRING)
50
+ "#{class_name}-#{route.route_version}#{action_name} (#{method_name})"
51
+ else
52
+ "#{class_name}#{action_name} (#{method_name})"
53
+ end
40
54
  end
41
55
  end
42
56
 
@@ -9,7 +9,6 @@ module NewRelic
9
9
  class MongodbCommandSubscriber
10
10
 
11
11
  MONGODB = 'MongoDB'.freeze
12
- GET_MORE = "getMore".freeze
13
12
  COLLECTION = "collection".freeze
14
13
 
15
14
  def started(event)
@@ -45,11 +44,7 @@ module NewRelic
45
44
  private
46
45
 
47
46
  def collection(event)
48
- if event.command_name == GET_MORE
49
- event.command[COLLECTION]
50
- else
51
- event.command.values.first
52
- end
47
+ event.command[COLLECTION] || event.command[:collection] || event.command.values.first
53
48
  end
54
49
 
55
50
  def metrics(event)
@@ -27,6 +27,42 @@ DependencyDetection.defer do
27
27
 
28
28
  alias dispatch_without_newrelic dispatch!
29
29
  alias dispatch! dispatch_with_newrelic
30
+
31
+ # Padrino 0.13 mustermann routing
32
+ if private_method_defined?(:invoke_route)
33
+ include NewRelic::Agent::Instrumentation::Padrino
34
+
35
+ alias invoke_route_without_newrelic invoke_route
36
+ alias invoke_route invoke_route_with_newrelic
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ module NewRelic
43
+ module Agent
44
+ module Instrumentation
45
+ module Padrino
46
+ def invoke_route_with_newrelic(*args, &block)
47
+ begin
48
+ env["newrelic.last_route"] = args[0].original_path
49
+ rescue => e
50
+ ::NewRelic::Agent.logger.debug("Failed determining last route in Padrino", e)
51
+ end
52
+
53
+ begin
54
+ txn_name = ::NewRelic::Agent::Instrumentation::Sinatra::TransactionNamer.transaction_name_for_route(env, request)
55
+ unless txn_name.nil?
56
+ ::NewRelic::Agent::Transaction.set_default_transaction_name(
57
+ "#{self.class.name}/#{txn_name}", :sinatra)
58
+ end
59
+ rescue => e
60
+ ::NewRelic::Agent.logger.debug("Failed during invoke_route to set transaction name", e)
61
+ end
62
+
63
+ invoke_route_without_newrelic(*args, &block)
64
+ end
65
+ end
30
66
  end
31
67
  end
32
68
  end
@@ -202,7 +202,7 @@ module NewRelic
202
202
  # Example:
203
203
  # Foo.default_metric_name_code('bar') #=> "Custom/#{Foo.name}/bar"
204
204
  def default_metric_name_code(method_name)
205
- "Custom/#{class_name}/#{method_name}"
205
+ "Custom/#{derived_class_name}/#{method_name}"
206
206
  end
207
207
 
208
208
  # Checks to see if the method we are attempting to trace
@@ -210,7 +210,7 @@ module NewRelic
210
210
  # anything if the method doesn't exist.
211
211
  def newrelic_method_exists?(method_name)
212
212
  exists = method_defined?(method_name) || private_method_defined?(method_name)
213
- ::NewRelic::Agent.logger.error("Did not trace #{class_name}##{method_name} because that method does not exist") unless exists
213
+ ::NewRelic::Agent.logger.error("Did not trace #{derived_class_name}##{method_name} because that method does not exist") unless exists
214
214
  exists
215
215
  end
216
216
 
@@ -275,6 +275,23 @@ module NewRelic
275
275
  method_without_push_scope(method_name, metric_name_code, options)
276
276
  end
277
277
  end
278
+
279
+ private
280
+
281
+ def derived_class_name
282
+ return self.name if self.name && !self.name.empty?
283
+ return "AnonymousModule" if self.to_s.start_with?("#<Module:")
284
+
285
+ # trying to get the "MyClass" portion of "#<Class:MyClass>"
286
+ name = self.to_s[/^#<Class:(.+)>$/, 1]
287
+ if name.start_with?("0x")
288
+ "AnonymousClass"
289
+ elsif name.start_with?("#<Class:")
290
+ "AnonymousClass/Class"
291
+ else
292
+ "#{name}/Class"
293
+ end
294
+ end
278
295
  end
279
296
  include AddMethodTracer
280
297
 
@@ -345,7 +362,7 @@ module NewRelic
345
362
  alias_method method_name, _traced_method_name(method_name, metric_name_code)
346
363
  send visibility, method_name
347
364
  send visibility, _traced_method_name(method_name, metric_name_code)
348
- ::NewRelic::Agent.logger.debug("Traced method: class = #{class_name},"+
365
+ ::NewRelic::Agent.logger.debug("Traced method: class = #{derived_class_name},"+
349
366
  "method = #{method_name}, "+
350
367
  "metric = '#{metric_name_code}'")
351
368
  end
@@ -383,21 +400,6 @@ module NewRelic
383
400
  def _sanitize_name(name)
384
401
  name.to_s.tr_s('^a-zA-Z0-9', '_')
385
402
  end
386
-
387
- def class_name
388
- return self.name if self.name && !self.name.empty?
389
- return "AnonymousModule" if self.to_s.start_with?("#<Module:")
390
-
391
- # trying to get the "MyClass" portion of "#<Class:MyClass>"
392
- name = self.to_s[/^#<Class:(.+)>$/, 1]
393
- if name.start_with?("0x")
394
- "AnonymousClass"
395
- elsif name.start_with?("#<Class:")
396
- "AnonymousClass/Class"
397
- else
398
- "#{name}/Class"
399
- end
400
- end
401
403
  end
402
404
 
403
405
  # @!parse extend ClassMethods
@@ -30,13 +30,12 @@ module NewRelic
30
30
  CONNECTION_ERRORS = [Timeout::Error, EOFError, SystemCallError, SocketError].freeze
31
31
 
32
32
  attr_accessor :request_timeout, :agent_id
33
- attr_reader :collector, :marshaller, :metric_id_cache
33
+ attr_reader :collector, :marshaller
34
34
 
35
35
  def initialize(license_key=nil, collector=control.server)
36
36
  @license_key = license_key || Agent.config[:license_key]
37
37
  @collector = collector
38
38
  @request_timeout = Agent.config[:timeout]
39
- @metric_id_cache = {}
40
39
  @ssl_cert_store = nil
41
40
  @in_session = nil
42
41
  @agent_id = nil
@@ -80,46 +79,18 @@ module NewRelic
80
79
  invoke_remote(:shutdown, [@agent_id, time.to_i]) if @agent_id
81
80
  end
82
81
 
83
- def reset_metric_id_cache
84
- @metric_id_cache = {}
85
- end
86
-
87
82
  def force_restart
88
- reset_metric_id_cache
89
83
  close_shared_connection
90
84
  end
91
85
 
92
- # takes an array of arrays of spec and id, adds it into the
93
- # metric cache so we can save the collector some work by
94
- # sending integers instead of strings the next time around
95
- def fill_metric_id_cache(pairs_of_specs_and_ids)
96
- Array(pairs_of_specs_and_ids).each do |metric_spec_hash, metric_id|
97
- metric_spec = MetricSpec.new(metric_spec_hash['name'],
98
- metric_spec_hash['scope'])
99
- metric_id_cache[metric_spec] = metric_id
100
- end
101
- rescue => e
102
- # If we've gotten this far, we don't want this error to propagate and
103
- # make this post appear to have been non-successful, which would trigger
104
- # re-aggregation of the same metric data into the next post, so just log
105
- NewRelic::Agent.logger.error("Failed to fill metric ID cache from response, error details follow ", e)
106
- end
107
-
108
- # The collector wants to recieve metric data in a format that's different
86
+ # The collector wants to receive metric data in a format that's different
109
87
  # from how we store it internally, so this method handles the translation.
110
- # It also handles translating metric names to IDs using our metric ID cache.
111
88
  def build_metric_data_array(stats_hash)
112
89
  metric_data_array = []
113
90
  stats_hash.each do |metric_spec, stats|
114
91
  # Omit empty stats as an optimization
115
92
  unless stats.is_reset?
116
- metric_id = metric_id_cache[metric_spec]
117
- metric_data = if metric_id
118
- NewRelic::MetricData.new(nil, stats, metric_id)
119
- else
120
- NewRelic::MetricData.new(metric_spec, stats, nil)
121
- end
122
- metric_data_array << metric_data
93
+ metric_data_array << NewRelic::MetricData.new(metric_spec, stats)
123
94
  end
124
95
  end
125
96
  metric_data_array
@@ -134,7 +105,6 @@ module NewRelic
134
105
  [@agent_id, timeslice_start.to_f, timeslice_end.to_f, metric_data_array],
135
106
  :item_count => metric_data_array.size
136
107
  )
137
- fill_metric_id_cache(result)
138
108
  result
139
109
  end
140
110
 
@@ -68,10 +68,6 @@ module NewRelic
68
68
  yield
69
69
  end
70
70
 
71
- def reset_metric_id_cache
72
- # we don't cache metric IDs, so nothing to do
73
- end
74
-
75
71
  private
76
72
 
77
73
  def marshal_payload(data)
@@ -140,7 +140,7 @@ module NewRelic
140
140
  # @api public
141
141
  # @deprecated Use {Datastores.notice_sql} instead.
142
142
  #
143
- def notice_sql(sql, metric_name, config, duration, state=nil, explainer=nil) #THREAD_LOCAL_ACCESS sometimes
143
+ def notice_sql(sql, metric_name, config, duration, state=nil, explainer=nil, binds=[], name="SQL") #THREAD_LOCAL_ACCESS sometimes
144
144
  state ||= TransactionState.tl_get
145
145
  data = state.sql_sampler_transaction_data
146
146
  return unless data
@@ -148,7 +148,7 @@ module NewRelic
148
148
  if state.is_sql_recorded?
149
149
  if duration > Agent.config[:'slow_sql.explain_threshold']
150
150
  backtrace = caller.join("\n")
151
- statement = Database::Statement.new(sql, config, explainer)
151
+ statement = Database::Statement.new(sql, config, explainer, binds, name)
152
152
  data.sql_data << SlowSql.new(statement, metric_name, duration, backtrace)
153
153
  end
154
154
  end
@@ -234,7 +234,7 @@ module NewRelic
234
234
 
235
235
  def explain
236
236
  if statement.config && statement.explainer
237
- NewRelic::Agent::Database.explain_sql(statement.sql, statement.config, statement.explainer)
237
+ NewRelic::Agent::Database.explain_sql(statement)
238
238
  end
239
239
  end
240
240
 
@@ -165,12 +165,9 @@ module NewRelic
165
165
  return params[:explain_plan] if params.key?(:explain_plan)
166
166
 
167
167
  statement = params[:sql]
168
- return nil unless statement.respond_to?(:config) &&
169
- statement.respond_to?(:explainer)
168
+ return nil unless statement.is_a?(Database::Statement)
170
169
 
171
- NewRelic::Agent::Database.explain_sql(statement.sql,
172
- statement.config,
173
- statement.explainer)
170
+ NewRelic::Agent::Database.explain_sql(statement)
174
171
  end
175
172
 
176
173
  def obfuscated_sql
@@ -183,13 +183,13 @@ module NewRelic
183
183
  # @api public
184
184
  # @deprecated Use {Datastores.notice_sql} instead.
185
185
  #
186
- def notice_sql(sql, config, duration, state=nil, explainer=nil) #THREAD_LOCAL_ACCESS sometimes
186
+ def notice_sql(sql, config, duration, state=nil, explainer=nil, binds=[], name="SQL") #THREAD_LOCAL_ACCESS sometimes
187
187
  # some statements (particularly INSERTS with large BLOBS
188
188
  # may be very large; we should trim them to a maximum usable length
189
189
  state ||= TransactionState.tl_get
190
190
  builder = state.transaction_sample_builder
191
191
  if state.is_sql_recorded?
192
- statement = Database::Statement.new(sql, config, explainer)
192
+ statement = Database::Statement.new(sql, config, explainer, binds, name)
193
193
  notice_extra_data(builder, statement, duration, :sql)
194
194
  end
195
195
  end
@@ -6,17 +6,14 @@ require 'new_relic/coerce'
6
6
 
7
7
  module NewRelic
8
8
  class MetricData
9
- # nil, or a NewRelic::MetricSpec object if we have no cached ID
9
+ # a NewRelic::MetricSpec object
10
10
  attr_reader :metric_spec
11
- # nil or a cached integer ID for the metric from the collector.
12
- attr_accessor :metric_id
13
11
  # the actual statistics object
14
12
  attr_accessor :stats
15
13
 
16
- def initialize(metric_spec, stats, metric_id)
14
+ def initialize(metric_spec, stats)
17
15
  @metric_spec = metric_spec
18
16
  self.stats = stats
19
- self.metric_id = metric_id
20
17
  end
21
18
 
22
19
  def eql?(o)
@@ -38,27 +35,22 @@ module NewRelic
38
35
  metric_spec.hash ^ stats.hash
39
36
  end
40
37
 
41
- # Serialize with all attributes, but if the metric id is not nil, then don't send the metric spec
42
38
  def to_json(*a)
43
- %Q[{"metric_spec":#{metric_id ? 'null' : metric_spec.to_json},"stats":{"total_exclusive_time":#{stats.total_exclusive_time},"min_call_time":#{stats.min_call_time},"call_count":#{stats.call_count},"sum_of_squares":#{stats.sum_of_squares},"total_call_time":#{stats.total_call_time},"max_call_time":#{stats.max_call_time}},"metric_id":#{metric_id ? metric_id : 'null'}}]
39
+ %Q[{"metric_spec":#{metric_spec.to_json},"stats":{"total_exclusive_time":#{stats.total_exclusive_time},"min_call_time":#{stats.min_call_time},"call_count":#{stats.call_count},"sum_of_squares":#{stats.sum_of_squares},"total_call_time":#{stats.total_call_time},"max_call_time":#{stats.max_call_time}}}]
44
40
  end
45
41
 
46
42
  def to_s
47
- if metric_spec
48
- "#{metric_spec.name}(#{metric_spec.scope}): #{stats}"
49
- else
50
- "#{metric_id}: #{stats}"
51
- end
43
+ "#{metric_spec.name}(#{metric_spec.scope}): #{stats}"
52
44
  end
53
45
 
54
46
  def inspect
55
- "#<MetricData metric_spec:#{metric_spec.inspect}, stats:#{stats.inspect}, metric_id:#{metric_id.inspect}>"
47
+ "#<MetricData metric_spec:#{metric_spec.inspect}, stats:#{stats.inspect}>"
56
48
  end
57
49
 
58
50
  include NewRelic::Coerce
59
51
 
60
52
  def to_collector_array(encoder=nil)
61
- stat_key = metric_id || { 'name' => metric_spec.name, 'scope' => metric_spec.scope }
53
+ stat_key = { 'name' => metric_spec.name, 'scope' => metric_spec.scope }
62
54
  [ stat_key,
63
55
  [
64
56
  int(stats.call_count, stat_key),