newrelic_rpm 3.17.0.325 → 3.17.1.326

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +21 -0
  3. data/lib/new_relic/agent/datastores/metric_helper.rb +3 -11
  4. data/lib/new_relic/agent/hostname.rb +18 -0
  5. data/lib/new_relic/agent/instrumentation/active_record.rb +3 -7
  6. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +10 -12
  7. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +5 -7
  8. data/lib/new_relic/agent/instrumentation/memcache.rb +2 -45
  9. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +157 -0
  10. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +34 -17
  11. data/lib/new_relic/agent/instrumentation/redis.rb +51 -11
  12. data/lib/new_relic/agent/traced_method_stack.rb +4 -0
  13. data/lib/new_relic/agent/transaction/datastore_segment.rb +27 -5
  14. data/lib/new_relic/agent/transaction/tracing.rb +4 -0
  15. data/lib/new_relic/version.rb +1 -1
  16. data/lib/tasks/config.rake +2 -1
  17. data/test/helpers/mongo_metric_builder.rb +11 -4
  18. data/test/multiverse/suites/memcached/dalli_test.rb +82 -1
  19. data/test/multiverse/suites/memcached/memcache_test_cases.rb +21 -8
  20. data/test/multiverse/suites/memcached/memcached_test.rb +21 -1
  21. data/test/multiverse/suites/mongo/mongo2_instrumentation_test.rb +33 -17
  22. data/test/multiverse/suites/redis/redis_instrumentation_test.rb +99 -7
  23. data/test/new_relic/agent/hostname_test.rb +23 -0
  24. data/test/new_relic/agent/instrumentation/active_record_subscriber_test.rb +11 -0
  25. data/test/new_relic/agent/instrumentation/mongodb_command_subscriber_test.rb +37 -0
  26. data/test/new_relic/agent/transaction/datastore_segment_test.rb +12 -0
  27. data/test/new_relic/agent/transaction/tracing_test.rb +8 -0
  28. metadata +3 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7d92e44197eda26fe761484451437f731e71bbb1
4
- data.tar.gz: f1c0e0554ed45e78c7d42b6617a1c98dbb7a3d3f
3
+ metadata.gz: 96d6b5b5c5bd88e9ac89be8715dfcfafd77ed814
4
+ data.tar.gz: 48b63cdd1ab3637281a7879cf1ec88625819a940
5
5
  SHA512:
6
- metadata.gz: 92827c74e5109b6d2fd3ba456af3af0d410328b8ff5224a9ee4858e8139fa328cb5128d2fc43c316bca415b5366dca59f691473222c28c620e000c08bd17a79d
7
- data.tar.gz: be6bd6121731993cd243c607d21a0eabab546c74c106bcf4a7a37da66a471d68311e5b67df6ca4301a07a0e7253cf20c6aeb4fb32859c34889224e77f26a088b
6
+ metadata.gz: 813384949da5fcfdc76d7ad7a7c1ad610bacccd854f96dcfe80d5078a371598a1769cb4bd6ec9e91b42cfffc0af6f2367ffb9cc3e2588612fd383dc75b64a014
7
+ data.tar.gz: eb33cc2fd802490ebeeb30ed104d41111f2d58484b15bc60d00055c5051b3c5009b1a9a90adcdd2145a7287e75b45af4871429a3ac0a74563fa502e7125691bc
data/CHANGELOG CHANGED
@@ -1,5 +1,26 @@
1
1
  # New Relic Ruby Agent Release Notes #
2
2
 
3
+ ## v3.17.1 ##
4
+
5
+ * Datastore instance reporting for Redis, MongoDB, and memcached
6
+
7
+ The agent now collects datastore instance information for Redis, MongoDB,
8
+ and memcached. This information is displayed in transaction traces and slow
9
+ query traces. For memcached only, multi requests will expand to individual
10
+ server nodes, and the operation and key(s) will show in the trace details
11
+ "Database query" section. Metrics for `get_multi` nodes will change slightly.
12
+ Parent nodes for a `get_multi` will be recorded as generic segments. Their
13
+ children will be recorded as datastore segments under the name
14
+ `get_multi_request` and represent a batch request to a single Memcached
15
+ instance.
16
+
17
+ * Rescue errors from attempts to fetch slow query explain plans
18
+
19
+ For slow queries through ActiveRecord 4+, the agent will attempt to fetch
20
+ an explain plan on SELECT statements. In the event that this causes an
21
+ error, such as being run on an adapter that doesn't implement `exec_query`,
22
+ the agent will now rescue and log those errors.
23
+
3
24
  ## v3.17.0 ##
4
25
 
5
26
  * Datastore instance reporting for ActiveRecord
@@ -84,21 +84,13 @@ module NewRelic
84
84
  [product, operation, collection]
85
85
  end
86
86
 
87
- def self.metrics_for(product, operation, collection = nil, generic_product = nil)
87
+ def self.metrics_for(product, operation, collection = nil, generic_product = nil, host=nil, port_path_or_id=nil)
88
88
  product, operation, collection = product_operation_collection_for(product, operation, collection, generic_product)
89
- suffix = all_suffix
90
89
 
91
90
  # Order of these metrics matters--the first metric in the list will
92
91
  # be treated as the scoped metric in a bunch of different cases.
93
- metrics = [
94
- operation_metric_for(product, operation),
95
- product_suffixed_rollup(product, suffix),
96
- product_rollup(product),
97
- suffixed_rollup(suffix),
98
- ROLLUP_METRIC
99
- ]
100
-
101
- metrics.unshift statement_metric_for(product, collection, operation) if collection
92
+ metrics = unscoped_metrics_for(product, operation, collection, host, port_path_or_id)
93
+ metrics.unshift scoped_metric_for(product, operation, collection)
102
94
 
103
95
  metrics
104
96
  end
@@ -25,6 +25,24 @@ module NewRelic
25
25
  def self.get_dyno_prefixes
26
26
  ::NewRelic::Agent.config[:'heroku.dyno_name_prefixes_to_shorten']
27
27
  end
28
+
29
+ LOCALHOST = %w[
30
+ localhost
31
+ 0.0.0.0
32
+ 127.0.0.1
33
+ 0:0:0:0:0:0:0:1
34
+ 0:0:0:0:0:0:0:0
35
+ ::1
36
+ ::
37
+ ].freeze
38
+
39
+ def self.local? host_or_ip
40
+ LOCALHOST.include?(host_or_ip)
41
+ end
42
+
43
+ def self.get_external host_or_ip
44
+ local?(host_or_ip) ? get : host_or_ip
45
+ end
28
46
  end
29
47
  end
30
48
  end
@@ -55,13 +55,9 @@ module NewRelic
55
55
  database = nil
56
56
 
57
57
  if ActiveRecordHelper::InstanceIdentification.supported_adapter?(@config)
58
- if NewRelic::Agent.config[:'datastore_tracer.instance_reporting.enabled']
59
- host = ActiveRecordHelper::InstanceIdentification.host(@config)
60
- port_path_or_id = ActiveRecordHelper::InstanceIdentification.port_path_or_id(@config)
61
- end
62
- if NewRelic::Agent.config[:'datastore_tracer.database_name_reporting.enabled']
63
- database = @config && @config[:database]
64
- end
58
+ host = ActiveRecordHelper::InstanceIdentification.host(@config)
59
+ port_path_or_id = ActiveRecordHelper::InstanceIdentification.port_path_or_id(@config)
60
+ database = @config && @config[:database]
65
61
  end
66
62
 
67
63
  segment = NewRelic::Agent::Transaction.start_datastore_segment(product, operation, collection, host, port_path_or_id, database)
@@ -207,16 +207,6 @@ module NewRelic
207
207
  module InstanceIdentification
208
208
  extend self
209
209
 
210
- LOCALHOST = %w[
211
- localhost
212
- 0.0.0.0
213
- 127.0.0.1
214
- 0:0:0:0:0:0:0:1
215
- 0:0:0:0:0:0:0:0
216
- ::1
217
- ::
218
- ].freeze unless defined?(LOCALHOST)
219
-
220
210
  PRODUCT_SYMBOLS = {
221
211
  "mysql" => :mysql,
222
212
  "mysql2" => :mysql,
@@ -241,7 +231,7 @@ module NewRelic
241
231
  configured_value = config[:host]
242
232
  adapter = PRODUCT_SYMBOLS[config[:adapter]]
243
233
  if configured_value.nil? ||
244
- LOCALHOST.include?(configured_value) ||
234
+ Hostname.local?(configured_value) ||
245
235
  postgres_unix_domain_socket_case?(configured_value, adapter)
246
236
 
247
237
  Hostname.get
@@ -250,6 +240,10 @@ module NewRelic
250
240
  else
251
241
  configured_value
252
242
  end
243
+
244
+ rescue => e
245
+ NewRelic::Agent.logger.debug "Failed to retrieve ActiveRecord host: #{e}"
246
+ UNKNOWN
253
247
  end
254
248
 
255
249
  def port_path_or_id(config)
@@ -267,6 +261,10 @@ module NewRelic
267
261
  else
268
262
  UNKNOWN
269
263
  end
264
+
265
+ rescue => e
266
+ NewRelic::Agent.logger.debug "Failed to retrieve ActiveRecord port_path_or_id: #{e}"
267
+ UNKNOWN
270
268
  end
271
269
 
272
270
  SUPPORTED_ADAPTERS = [:mysql, :postgres].freeze unless defined?(SUPPORTED_ADAPTERS)
@@ -283,7 +281,7 @@ module NewRelic
283
281
 
284
282
  def mysql_default_case?(config, adapter)
285
283
  (adapter == :mysql2 || adapter == :mysql) &&
286
- LOCALHOST.include?(config[:host]) &&
284
+ Hostname.local?(config[:host]) &&
287
285
  !config[:port]
288
286
  end
289
287
  end
@@ -50,6 +50,8 @@ module NewRelic
50
50
  "Explain #{statement.name}",
51
51
  statement.binds)
52
52
  end
53
+ rescue => e
54
+ NewRelic::Agent.logger.debug "Couldn't fetch the explain plan for #{statement} due to #{e}"
53
55
  end
54
56
 
55
57
  def active_record_config(payload)
@@ -91,13 +93,9 @@ module NewRelic
91
93
  database = nil
92
94
 
93
95
  if ActiveRecordHelper::InstanceIdentification.supported_adapter?(@config)
94
- if NewRelic::Agent.config[:'datastore_tracer.instance_reporting.enabled']
95
- host = ActiveRecordHelper::InstanceIdentification.host(@config)
96
- port_path_or_id = ActiveRecordHelper::InstanceIdentification.port_path_or_id(@config)
97
- end
98
- if NewRelic::Agent.config[:'datastore_tracer.database_name_reporting.enabled']
99
- database = @config && @config[:database]
100
- end
96
+ host = ActiveRecordHelper::InstanceIdentification.host(@config)
97
+ port_path_or_id = ActiveRecordHelper::InstanceIdentification.port_path_or_id(@config)
98
+ database = @config && @config[:database]
101
99
  end
102
100
 
103
101
  segment = NewRelic::Agent::Transaction::DatastoreSegment.new product, operation, collection, host, port_path_or_id, database
@@ -10,6 +10,7 @@
10
10
  # https://github.com/mperham/dalli (Gem: dalli)
11
11
 
12
12
  require 'new_relic/agent/datastores/metric_helper'
13
+ require 'new_relic/agent/instrumentation/memcache/dalli'
13
14
 
14
15
  module NewRelic
15
16
  module Agent
@@ -45,8 +46,7 @@ module NewRelic
45
46
  send method_name_without, *args, &block
46
47
  ensure
47
48
  if NewRelic::Agent.config[:capture_memcache_keys]
48
- NewRelic::Agent.instance.transaction_sampler.notice_nosql(args.first.inspect,
49
- (Time.now - segment.start_time).to_f) rescue nil
49
+ segment.notice_nosql_statement "#{method_name} #{args.first.inspect}"
50
50
  end
51
51
  segment.finish
52
52
  end
@@ -96,46 +96,3 @@ DependencyDetection.defer do
96
96
  ::NewRelic::Agent::Instrumentation::Memcache.instrument_methods(::Memcached)
97
97
  end
98
98
  end
99
-
100
- DependencyDetection.defer do
101
- named :dalli
102
-
103
- depends_on do
104
- NewRelic::Agent::Instrumentation::Memcache.enabled?
105
- end
106
-
107
- depends_on do
108
- defined?(::Dalli::Client)
109
- end
110
-
111
- executes do
112
- ::NewRelic::Agent.logger.info 'Installing Memcache instrumentation for dalli gem'
113
- ::NewRelic::Agent::Instrumentation::Memcache.instrument_methods(::Dalli::Client)
114
- end
115
- end
116
-
117
- DependencyDetection.defer do
118
- named :dalli_cas_client
119
-
120
- depends_on do
121
- NewRelic::Agent::Instrumentation::Memcache.enabled?
122
- end
123
-
124
- depends_on do
125
- # These CAS client methods are only optionally defined if users require
126
- # dalli/cas/client. Use a separate dependency block so it can potentially
127
- # re-evaluate after they've done that require.
128
- defined?(::Dalli::Client) &&
129
- ::NewRelic::Agent::Instrumentation::Memcache.supported_methods_for(::Dalli::Client,
130
- CAS_CLIENT_METHODS).any?
131
- end
132
-
133
- CAS_CLIENT_METHODS = [:get_cas, :get_multi_cas, :set_cas, :replace_cas,
134
- :delete_cas]
135
-
136
- executes do
137
- ::NewRelic::Agent.logger.info 'Installing Dalli CAS Client Memcache instrumentation'
138
- ::NewRelic::Agent::Instrumentation::Memcache.instrument_methods(::Dalli::Client,
139
- CAS_CLIENT_METHODS)
140
- end
141
- end
@@ -0,0 +1,157 @@
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
+ module NewRelic
6
+ module Agent
7
+ module Instrumentation
8
+ module Memcache
9
+ module Dalli
10
+ extend self
11
+
12
+ MEMCACHED = "Memcached".freeze
13
+ METHODS = [:get, :set, :add, :incr, :decr, :delete, :replace, :append, :prepend, :cas]
14
+ SEND_MULTIGET_METRIC_NAME = "get_multi_request".freeze
15
+ DATASTORE_INSTANCES_SUPPORTED_VERSION = ::NewRelic::VersionNumber.new '2.6.4'
16
+ SLASH = '/'.freeze
17
+ UNKNOWN = 'unknown'.freeze
18
+
19
+ def supports_datastore_instances?
20
+ DATASTORE_INSTANCES_SUPPORTED_VERSION <= ::Dalli::VERSION
21
+ end
22
+
23
+ def instrument_methods
24
+ if supports_datastore_instances?
25
+ ::NewRelic::Agent::Instrumentation::Memcache.instrument_methods(::Dalli::Client, METHODS)
26
+ instrument_multi_method :get_multi
27
+ instrument_send_multiget
28
+ instrument_server_for_key
29
+ else
30
+ ::NewRelic::Agent::Instrumentation::Memcache.instrument_methods(::Dalli::Client,
31
+ ::NewRelic::Agent::Instrumentation::Memcache::METHODS)
32
+ end
33
+ end
34
+
35
+ def instrument_server_for_key
36
+ ::Dalli::Ring.class_eval do
37
+ alias_method :server_for_key_without_newrelic_trace, :server_for_key
38
+
39
+ def server_for_key key
40
+ server = server_for_key_without_newrelic_trace key
41
+ begin
42
+ if txn = ::NewRelic::Agent::Transaction.tl_current
43
+ segment = txn.current_segment
44
+ if ::NewRelic::Agent::Transaction::DatastoreSegment === segment
45
+ ::NewRelic::Agent::Instrumentation::Memcache::Dalli.assign_instance_to(segment, server)
46
+ end
47
+ end
48
+ rescue => e
49
+ ::NewRelic::Agent.logger.warn "Unable to set instance info on datastore segment: #{e.message}"
50
+ end
51
+ server
52
+ end
53
+ end
54
+ end
55
+
56
+ def instrument_send_multiget
57
+ ::Dalli::Server.class_eval do
58
+ alias_method :send_multiget_without_newrelic_trace, :send_multiget
59
+
60
+ def send_multiget(keys)
61
+ segment = ::NewRelic::Agent::Transaction.start_datastore_segment(MEMCACHED, SEND_MULTIGET_METRIC_NAME)
62
+ ::NewRelic::Agent::Instrumentation::Memcache::Dalli.assign_instance_to(segment, self)
63
+
64
+ begin
65
+ send_multiget_without_newrelic_trace(keys)
66
+ ensure
67
+ if ::NewRelic::Agent.config[:capture_memcache_keys]
68
+ segment.notice_nosql_statement "#{SEND_MULTIGET_METRIC_NAME} #{keys.inspect}"
69
+ end
70
+ segment.finish
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ def instrument_multi_method method_name
77
+ visibility = NewRelic::Helper.instance_method_visibility ::Dalli::Client, method_name
78
+ method_name_without = :"#{method_name}_without_newrelic_trace"
79
+
80
+ ::Dalli::Client.class_eval do
81
+ alias_method method_name_without, method_name
82
+
83
+ define_method method_name do |*args, &block|
84
+ segment = NewRelic::Agent::Transaction.start_segment "Ruby/Memcached/Dalli/#{method_name}"
85
+ begin
86
+ __send__ method_name_without, *args, &block
87
+ ensure
88
+ segment.finish
89
+ end
90
+ end
91
+
92
+ __send__ visibility, method_name
93
+ __send__ visibility, method_name_without
94
+ end
95
+ end
96
+
97
+ def assign_instance_to segment, server
98
+ if server.hostname.start_with? SLASH
99
+ segment.host = ::NewRelic::Agent::Hostname.get
100
+ segment.port_path_or_id = server.hostname
101
+ else
102
+ segment.host = ::NewRelic::Agent::Hostname.get_external server.hostname
103
+ segment.port_path_or_id = server.port
104
+ end
105
+ rescue => e
106
+ ::NewRelic::Agent.logger.debug "Failed to retrieve memcached instance info: #{e.message}"
107
+ segment.host = UNKNOWN
108
+ segment.port_path_or_id = UNKNOWN
109
+ end
110
+
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+
117
+ DependencyDetection.defer do
118
+ named :dalli
119
+
120
+ depends_on do
121
+ NewRelic::Agent::Instrumentation::Memcache.enabled?
122
+ end
123
+
124
+ depends_on do
125
+ defined?(::Dalli::Client)
126
+ end
127
+
128
+ executes do
129
+ ::NewRelic::Agent.logger.info 'Installing Memcache instrumentation for dalli gem'
130
+ ::NewRelic::Agent::Instrumentation::Memcache::Dalli.instrument_methods
131
+ end
132
+ end
133
+
134
+ DependencyDetection.defer do
135
+ named :dalli_cas_client
136
+
137
+ depends_on do
138
+ NewRelic::Agent::Instrumentation::Memcache.enabled?
139
+ end
140
+
141
+ depends_on do
142
+ # These CAS client methods are only optionally defined if users require
143
+ # dalli/cas/client. Use a separate dependency block so it can potentially
144
+ # re-evaluate after they've done that require.
145
+ defined?(::Dalli::Client) &&
146
+ ::NewRelic::Agent::Instrumentation::Memcache.supported_methods_for(::Dalli::Client,
147
+ CAS_CLIENT_METHODS).any?
148
+ end
149
+
150
+ CAS_CLIENT_METHODS = [:get_cas, :set_cas, :replace_cas, :delete_cas]
151
+
152
+ executes do
153
+ ::NewRelic::Agent.logger.info 'Installing Dalli CAS Client Memcache instrumentation'
154
+ ::NewRelic::Agent::Instrumentation::Memcache.instrument_methods(::Dalli::Client, CAS_CLIENT_METHODS)
155
+ ::NewRelic::Agent::Instrumentation::Memcache::Dalli.instrument_multi_method(:get_multi_cas)
156
+ end
157
+ end
@@ -14,7 +14,6 @@ module NewRelic
14
14
  begin
15
15
  return unless NewRelic::Agent.tl_is_execution_traced?
16
16
  segments[event.operation_id] = start_segment event
17
- operations[event.operation_id] = event
18
17
  rescue Exception => e
19
18
  log_notification_error('started', e)
20
19
  end
@@ -24,9 +23,7 @@ module NewRelic
24
23
  begin
25
24
  state = NewRelic::Agent::TransactionState.tl_get
26
25
  return unless state.is_execution_traced?
27
- started_event = operations.delete(event.operation_id)
28
26
  segment = segments.delete(event.operation_id)
29
- notice_nosql_statement(state, started_event, segment.name, event.duration)
30
27
  segment.finish
31
28
  rescue Exception => e
32
29
  log_notification_error('completed', e)
@@ -39,28 +36,24 @@ module NewRelic
39
36
  private
40
37
 
41
38
  def start_segment event
42
- NewRelic::Agent::Transaction.start_datastore_segment(
43
- MONGODB, event.command_name, collection(event)
39
+ host = host_from_address event.address
40
+ port_path_or_id = port_path_or_id_from_address event.address
41
+ segment = NewRelic::Agent::Transaction.start_datastore_segment(
42
+ MONGODB, event.command_name, collection(event), host, port_path_or_id, event.database_name
44
43
  )
44
+ segment.notice_nosql_statement(generate_statement(event))
45
+ segment
45
46
  end
46
47
 
47
48
  def collection(event)
48
49
  event.command[COLLECTION] || event.command[:collection] || event.command.values.first
49
50
  end
50
51
 
51
- def metrics(event)
52
- NewRelic::Agent::Datastores::MetricHelper.metrics_for(MONGODB, event.command_name, collection(event))
53
- end
54
-
55
52
  def log_notification_error(event_type, error)
56
53
  NewRelic::Agent.logger.error("Error during MongoDB #{event_type} event:")
57
54
  NewRelic::Agent.logger.log_exception(:error, error)
58
55
  end
59
56
 
60
- def operations
61
- @operations ||= {}
62
- end
63
-
64
57
  def segments
65
58
  @segments ||= {}
66
59
  end
@@ -73,10 +66,34 @@ module NewRelic
73
66
  )
74
67
  end
75
68
 
76
- def notice_nosql_statement(state, event, metric, duration)
77
- NewRelic::Agent.instance.transaction_sampler.notice_nosql_statement(
78
- generate_statement(event), duration
79
- )
69
+ UNKNOWN = "unknown".freeze
70
+
71
+ def host_from_address(address)
72
+ if unix_domain_socket? address.host
73
+ Hostname.get
74
+ else
75
+ Hostname.get_external address.host
76
+ end
77
+ rescue => e
78
+ NewRelic::Agent.logger.debug "Failed to retrieve Mongo host: #{e}"
79
+ UNKNOWN
80
+ end
81
+
82
+ def port_path_or_id_from_address(address)
83
+ if unix_domain_socket? address.host
84
+ address.host
85
+ else
86
+ address.port
87
+ end
88
+ rescue => e
89
+ NewRelic::Agent.logger.debug "Failed to retrieve Mongo port_path_or_id: #{e}"
90
+ UNKNOWN
91
+ end
92
+
93
+ SLASH = "/".freeze
94
+
95
+ def unix_domain_socket?(host)
96
+ host.start_with? SLASH
80
97
  end
81
98
  end
82
99
  end