newrelic_rpm 3.16.3.323 → 3.17.0.325

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: a8454965d0e8d1a99e35caacd91984db198aa5d7
4
- data.tar.gz: 830bcd499e25cf38ee804c656788b29201510b6e
3
+ metadata.gz: 7d92e44197eda26fe761484451437f731e71bbb1
4
+ data.tar.gz: f1c0e0554ed45e78c7d42b6617a1c98dbb7a3d3f
5
5
  SHA512:
6
- metadata.gz: 4c62be289b6de4579c011a88fce9777cbd97f1fb3a4c14a0d6accfc9ef377d58520fd8fb3b256298f3d3615e19cc361f92af7e3d730d3b9de30938ffa019dc5f
7
- data.tar.gz: 989d62082b606f370a55e9a898ae4b28ae1ddec04ad1c75ecff2a2ab77afde56da330523c5f2be566ae080a7eb5e7c091a41b26f881b95dadd99e72bee51cb41
6
+ metadata.gz: 92827c74e5109b6d2fd3ba456af3af0d410328b8ff5224a9ee4858e8139fa328cb5128d2fc43c316bca415b5366dca59f691473222c28c620e000c08bd17a79d
7
+ data.tar.gz: be6bd6121731993cd243c607d21a0eabab546c74c106bcf4a7a37da66a471d68311e5b67df6ca4301a07a0e7253cf20c6aeb4fb32859c34889224e77f26a088b
data/CHANGELOG CHANGED
@@ -1,5 +1,13 @@
1
1
  # New Relic Ruby Agent Release Notes #
2
2
 
3
+ ## v3.17.0 ##
4
+
5
+ * Datastore instance reporting for ActiveRecord
6
+
7
+ The agent now collects database instance information for ActiveRecord operations,
8
+ when using the MySQL and Postgres adapters. This information (database server
9
+ and database name) is displayed in transaction traces and slow query traces.
10
+
3
11
  ## v3.16.3 ##
4
12
 
5
13
  * Add `:trace_only` option to `notice_error` API
data/README.md CHANGED
@@ -78,9 +78,11 @@ edit `environment.rb` and add to the initalizer block:
78
78
 
79
79
  #### Sinatra Installation
80
80
 
81
- To use the Ruby Agent with a Sinatra app, add
81
+ To use the Ruby Agent with a Sinatra app, add:
82
82
 
83
- require 'newrelic_rpm'
83
+ ```ruby
84
+ require 'newrelic_rpm'
85
+ ```
84
86
 
85
87
  in your Sinatra app, below the Sinatra require directive.
86
88
 
@@ -93,13 +95,17 @@ the middleware stack. See the `config.ru` sample below.
93
95
 
94
96
  #### Other Environments
95
97
 
96
- You can use the Ruby Agent to monitor any Ruby application. Add
98
+ You can use the Ruby Agent to monitor any Ruby application. Add:
97
99
 
98
- require 'newrelic_rpm'
100
+ ```ruby
101
+ require 'newrelic_rpm'
102
+ ```
99
103
 
100
- to your startup sequence and then manually start the agent using
104
+ to your startup sequence and then manually start the agent using:
101
105
 
102
- NewRelic::Agent.manual_start
106
+ ```ruby
107
+ NewRelic::Agent.manual_start
108
+ ```
103
109
 
104
110
  For information about instrumenting pure Rack applications, see our
105
111
  [Rack middlewares documentation](http://docs.newrelic.com/docs/ruby/rack-middlewares).
@@ -40,7 +40,7 @@ module NewRelic
40
40
  rescue StandardError, SystemStackError, SystemCallError => e
41
41
  ::NewRelic::Agent.logger.warn("Failed writing to audit log", e)
42
42
  rescue Exception => e
43
- ::NewRelic::Agent.logger.warn("Failed writing to audit log with exception. Re-raising in case of interupt.", e)
43
+ ::NewRelic::Agent.logger.warn("Failed writing to audit log with exception. Re-raising in case of interrupt.", e)
44
44
  raise
45
45
  end
46
46
 
@@ -356,7 +356,7 @@ module NewRelic
356
356
  :public => true,
357
357
  :type => Boolean,
358
358
  :allowed_from_server => false,
359
- :description => 'When <code>true</code>, the agent captures HTTP request parameters and attaches them to transaction traces and traced errors.'
359
+ :description => 'When <code>true</code>, the agent captures HTTP request parameters and attaches them to transaction traces, traced errors, and <a href="https://docs.newrelic.com/docs/insights/new-relic-insights/decorating-events/error-event-default-attributes-insights">TransactionError events</a>.'
360
360
  },
361
361
  :config_path => {
362
362
  :default => DefaultSource.config_path,
@@ -1602,6 +1602,20 @@ module NewRelic
1602
1602
  :type => Fixnum,
1603
1603
  :allowed_from_server => false,
1604
1604
  :description => 'This value represents the total amount of memory available to the host (not the process), in mebibytes (1024 squared or 1,048,576 bytes).'
1605
+ },
1606
+ :'datastore_tracer.instance_reporting.enabled' => {
1607
+ :default => true,
1608
+ :public => true,
1609
+ :type => Boolean,
1610
+ :allowed_from_server => false,
1611
+ :description => 'If <code>false</code>, the agent will not report datastore instance metrics, nor add <code>host</code> or <code>port_path_or_id</code> parameters to transaction or slow sql traces.'
1612
+ },
1613
+ :'datastore_tracer.database_name_reporting.enabled' => {
1614
+ :default => true,
1615
+ :public => true,
1616
+ :type => Boolean,
1617
+ :allowed_from_server => false,
1618
+ :description => 'If <code>false</code>, the agent will not add <code>database_name</code> parameter to transaction or slow sql traces.'
1605
1619
  }
1606
1620
  }.freeze
1607
1621
  end
@@ -181,16 +181,19 @@ module NewRelic
181
181
  class Statement
182
182
  include ExplainPlanHelpers
183
183
 
184
- attr_accessor :sql, :config, :explainer, :binds, :name
184
+ attr_accessor :sql, :config, :explainer, :binds, :name, :host, :port_path_or_id, :database_name
185
185
 
186
186
  DEFAULT_QUERY_NAME = "SQL".freeze
187
187
 
188
- def initialize(sql, config={}, explainer=nil, binds=nil, name=DEFAULT_QUERY_NAME)
188
+ def initialize(sql, config={}, explainer=nil, binds=nil, name=DEFAULT_QUERY_NAME, host=nil, port_path_or_id=nil, database_name=nil)
189
189
  @sql = Database.capture_query(sql)
190
190
  @config = config
191
191
  @explainer = explainer
192
192
  @binds = binds
193
193
  @name = name
194
+ @host = host
195
+ @port_path_or_id = port_path_or_id
196
+ @database_name = database_name
194
197
  end
195
198
 
196
199
  # This takes a connection config hash from ActiveRecord or Sequel and
@@ -24,6 +24,10 @@ module NewRelic
24
24
  "Datastore/operation/#{product}/#{operation}"
25
25
  end
26
26
 
27
+ def self.instance_metric_for(product, host, port_path_or_id)
28
+ "Datastore/instance/#{product}/#{host}/#{port_path_or_id}"
29
+ end
30
+
27
31
  def self.product_suffixed_rollup(product, suffix)
28
32
  "Datastore/#{product}/#{suffix}"
29
33
  end
@@ -52,7 +56,7 @@ module NewRelic
52
56
  end
53
57
  end
54
58
 
55
- def self.unscoped_metrics_for product, operation, collection=nil
59
+ def self.unscoped_metrics_for product, operation, collection=nil, host=nil, port_path_or_id=nil
56
60
  suffix = all_suffix
57
61
 
58
62
  metrics = [
@@ -62,6 +66,9 @@ module NewRelic
62
66
  ROLLUP_METRIC
63
67
  ]
64
68
 
69
+ if NewRelic::Agent.config[:'datastore_tracer.instance_reporting.enabled'] && host && port_path_or_id
70
+ metrics.unshift instance_metric_for(product, host, port_path_or_id)
71
+ end
65
72
  metrics.unshift operation_metric_for(product, operation) if collection
66
73
 
67
74
  metrics
@@ -50,7 +50,21 @@ module NewRelic
50
50
  NewRelic::Helper.correctly_encoded(sql),
51
51
  @config && @config[:adapter])
52
52
 
53
- segment = NewRelic::Agent::Transaction.start_datastore_segment(product, operation, collection)
53
+ host = nil
54
+ port_path_or_id = nil
55
+ database = nil
56
+
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
65
+ end
66
+
67
+ segment = NewRelic::Agent::Transaction.start_datastore_segment(product, operation, collection, host, port_path_or_id, database)
54
68
  segment._notice_sql(sql, @config, EXPLAINER)
55
69
 
56
70
  begin
@@ -204,6 +204,89 @@ module NewRelic
204
204
  ACTIVE_RECORD_DEFAULT_PRODUCT_NAME)
205
205
  end
206
206
 
207
+ module InstanceIdentification
208
+ extend self
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
+ PRODUCT_SYMBOLS = {
221
+ "mysql" => :mysql,
222
+ "mysql2" => :mysql,
223
+ "jdbcmysql" => :mysql,
224
+
225
+ "postgresql" => :postgres,
226
+ "jdbcpostgresql" => :postgres
227
+ }.freeze unless defined?(PRODUCT_SYMBOLS)
228
+
229
+ DATASTORE_DEFAULT_PORTS = {
230
+ :mysql => "3306",
231
+ :postgres => "5432"
232
+ }.freeze unless defined?(DATASTORE_DEFAULT_PORTS)
233
+
234
+ DEFAULT = "default".freeze unless defined?(DEFAULT)
235
+ UNKNOWN = "unknown".freeze unless defined?(UNKNOWN)
236
+ SLASH = "/".freeze unless defined?(SLASH)
237
+
238
+ def host(config)
239
+ return UNKNOWN unless config
240
+
241
+ configured_value = config[:host]
242
+ adapter = PRODUCT_SYMBOLS[config[:adapter]]
243
+ if configured_value.nil? ||
244
+ LOCALHOST.include?(configured_value) ||
245
+ postgres_unix_domain_socket_case?(configured_value, adapter)
246
+
247
+ Hostname.get
248
+ elsif configured_value.empty?
249
+ UNKNOWN
250
+ else
251
+ configured_value
252
+ end
253
+ end
254
+
255
+ def port_path_or_id(config)
256
+ return UNKNOWN unless config
257
+
258
+ adapter = PRODUCT_SYMBOLS[config[:adapter]]
259
+ if config[:socket]
260
+ config[:socket].empty? ? UNKNOWN : config[:socket]
261
+ elsif postgres_unix_domain_socket_case?(config[:host], adapter) || mysql_default_case?(config, adapter)
262
+ DEFAULT
263
+ elsif config[:port].nil?
264
+ DATASTORE_DEFAULT_PORTS[adapter] || DEFAULT
265
+ elsif config[:port].is_a?(Fixnum) || config[:port].to_i != 0
266
+ config[:port].to_s
267
+ else
268
+ UNKNOWN
269
+ end
270
+ end
271
+
272
+ SUPPORTED_ADAPTERS = [:mysql, :postgres].freeze unless defined?(SUPPORTED_ADAPTERS)
273
+
274
+ def supported_adapter? config
275
+ config && SUPPORTED_ADAPTERS.include?(PRODUCT_SYMBOLS[config[:adapter]])
276
+ end
277
+
278
+ private
279
+
280
+ def postgres_unix_domain_socket_case?(host, adapter)
281
+ adapter == :postgres && host && host.start_with?(SLASH)
282
+ end
283
+
284
+ def mysql_default_case?(config, adapter)
285
+ (adapter == :mysql2 || adapter == :mysql) &&
286
+ LOCALHOST.include?(config[:host]) &&
287
+ !config[:port]
288
+ end
289
+ end
207
290
  end
208
291
  end
209
292
  end
@@ -85,7 +85,22 @@ module NewRelic
85
85
  def start_segment
86
86
  product, operation, collection = ActiveRecordHelper.product_operation_collection_for(payload[:name],
87
87
  sql, @config && @config[:adapter])
88
- segment = NewRelic::Agent::Transaction::DatastoreSegment.new product, operation, collection
88
+
89
+ host = nil
90
+ port_path_or_id = nil
91
+ database = nil
92
+
93
+ 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
101
+ end
102
+
103
+ segment = NewRelic::Agent::Transaction::DatastoreSegment.new product, operation, collection, host, port_path_or_id, database
89
104
  if txn = state.current_transaction
90
105
  segment.transaction = txn
91
106
  end
@@ -236,6 +236,20 @@ module NewRelic
236
236
  statement.sql
237
237
  end
238
238
 
239
+ def base_params
240
+ params = {}
241
+
242
+ if NewRelic::Agent.config[:'datastore_tracer.instance_reporting.enabled']
243
+ params[:host] = statement.host if statement.host
244
+ params[:port_path_or_id] = statement.port_path_or_id if statement.port_path_or_id
245
+ end
246
+ if NewRelic::Agent.config[:'datastore_tracer.database_name_reporting.enabled'] && statement.database_name
247
+ params[:database_name] = statement.database_name
248
+ end
249
+
250
+ params
251
+ end
252
+
239
253
  def obfuscate
240
254
  NewRelic::Agent::Database.obfuscate_sql(statement)
241
255
  end
@@ -268,7 +282,7 @@ module NewRelic
268
282
 
269
283
  def initialize(normalized_query, slow_sql, path, uri)
270
284
  super()
271
- @params = {}
285
+ @params = slow_sql.base_params
272
286
  @sql_id = consistent_hash(normalized_query)
273
287
  set_primary slow_sql, path, uri
274
288
  record_data_point(float(slow_sql.duration))
@@ -15,7 +15,7 @@ module NewRelic
15
15
  rescue => e
16
16
  ::NewRelic::Agent.logger.error("Thread #{label} exited with error", e)
17
17
  rescue Exception => e
18
- ::NewRelic::Agent.logger.error("Thread #{label} exited with exception. Re-raising in case of interupt.", e)
18
+ ::NewRelic::Agent.logger.error("Thread #{label} exited with exception. Re-raising in case of interrupt.", e)
19
19
  raise
20
20
  ensure
21
21
  ::NewRelic::Agent.logger.debug("Exiting New Relic thread: #{label}")
@@ -10,15 +10,18 @@ module NewRelic
10
10
  module Agent
11
11
  class Transaction
12
12
  class DatastoreSegment < Segment
13
- attr_reader :product, :operation, :collection, :sql_statement
13
+ attr_reader :product, :operation, :collection, :sql_statement, :host, :port_path_or_id, :database_name
14
14
 
15
- def initialize product, operation, collection = nil
15
+ def initialize product, operation, collection = nil, host = nil, port_path_or_id = nil, database_name=nil
16
16
  @product = product
17
17
  @operation = operation
18
18
  @collection = collection
19
19
  @sql_statement = nil
20
+ @host = host
21
+ @port_path_or_id = port_path_or_id
22
+ @database_name = database_name
20
23
  super Datastores::MetricHelper.scoped_metric_for(product, operation, collection),
21
- Datastores::MetricHelper.unscoped_metrics_for(product, operation, collection)
24
+ Datastores::MetricHelper.unscoped_metrics_for(product, operation, collection, host, port_path_or_id)
22
25
  end
23
26
 
24
27
  def notice_sql sql
@@ -28,14 +31,38 @@ module NewRelic
28
31
  # @api private
29
32
  def _notice_sql sql, config=nil, explainer=nil, binds=nil, name=nil
30
33
  return unless record_sql?
31
- @sql_statement = Database::Statement.new sql, config, explainer, binds, name
34
+ @sql_statement = Database::Statement.new sql, config, explainer, binds, name, host, port_path_or_id, database_name
32
35
  end
33
36
 
34
37
  private
35
38
 
36
39
  def segment_complete
37
- return unless sql_statement
40
+ add_segment_parameters
41
+ notice_sql_statement if sql_statement
42
+ end
43
+
44
+ def add_segment_parameters
45
+ instance_reporting_enabled = NewRelic::Agent.config[:'datastore_tracer.instance_reporting.enabled']
46
+ db_name_reporting_enabled = NewRelic::Agent.config[:'datastore_tracer.database_name_reporting.enabled']
47
+ return unless instance_reporting_enabled || db_name_reporting_enabled
48
+
49
+ params = {}
50
+ add_instance_parameters params if instance_reporting_enabled
51
+ add_database_name_parameter params if db_name_reporting_enabled
52
+
53
+ NewRelic::Agent.instance.transaction_sampler.add_node_parameters params
54
+ end
55
+
56
+ def add_instance_parameters params
57
+ params[:host] = host if host
58
+ params[:port_path_or_id] = port_path_or_id if port_path_or_id
59
+ end
60
+
61
+ def add_database_name_parameter(params)
62
+ params[:database_name] = database_name if database_name
63
+ end
38
64
 
65
+ def notice_sql_statement
39
66
  NewRelic::Agent.instance.transaction_sampler.notice_sql_statement(sql_statement, duration)
40
67
  NewRelic::Agent.instance.sql_sampler.notice_sql_statement(sql_statement.dup, name, duration)
41
68
  end
@@ -17,8 +17,8 @@ module NewRelic
17
17
  segment
18
18
  end
19
19
 
20
- def start_datastore_segment product, operation, collection=nil
21
- segment = DatastoreSegment.new product, operation, collection
20
+ def start_datastore_segment product, operation, collection=nil, host=nil, port_path_or_id=nil, database_name=nil
21
+ segment = DatastoreSegment.new product, operation, collection, host, port_path_or_id, database_name
22
22
  segment.start
23
23
  add_segment segment
24
24
  segment
@@ -11,8 +11,8 @@ module NewRelic
11
11
  end
12
12
 
13
13
  MAJOR = 3
14
- MINOR = 16
15
- TINY = 3
14
+ MINOR = 17
15
+ TINY = 0
16
16
 
17
17
  begin
18
18
  require File.join(File.dirname(__FILE__), 'build')
@@ -22,7 +22,7 @@ end
22
22
  # us from inadvertently loading minitest 5.3.0 on rbx (we'll require
23
23
  # minitest/unit instead via a different path).
24
24
  gem 'minitest', '~>4.7.5', :require => false
25
- gem 'mocha', :require => false
25
+ gem 'mocha', '1.1', :require => false
26
26
  gem 'rack', '< 2.0.0'
27
27
  gem 'rack-test'
28
28
 
@@ -14,7 +14,7 @@ elsif RUBY_VERSION < '2'
14
14
  end
15
15
 
16
16
  gem 'minitest', '5.2.3'
17
- gem 'mocha', :require => false
17
+ gem 'mocha', '1.1', :require => false
18
18
  gem 'rack', '< 2.0.0'
19
19
  gem 'rack-test'
20
20
 
@@ -14,7 +14,7 @@ elsif RUBY_VERSION < '2'
14
14
  end
15
15
 
16
16
  gem 'minitest', '5.2.3'
17
- gem 'mocha', :require => false
17
+ gem 'mocha', '1.1', :require => false
18
18
  gem 'rack', '< 2.0.0'
19
19
  gem 'rack-test'
20
20
 
@@ -5,7 +5,7 @@ gem 'rake', '< 11'
5
5
  gem 'rails', '5.0.0'
6
6
 
7
7
  gem 'minitest', '5.2.3'
8
- gem 'mocha', :require => false
8
+ gem 'mocha', '1.1', :require => false
9
9
  gem 'rack'
10
10
  gem 'rack-test'
11
11
 
@@ -0,0 +1,16 @@
1
+ ## Datastore instance tests
2
+
3
+ The datastore instance tests provide attributes similar to what an agent could expect to find regarding a database configuration and specifies the expected [datastore instance metric](https://source.datanerd.us/agents/agent-specs/blob/master/Datastore-Metrics-PORTED.md#datastore-metric-namespace) that should be generated. The table below lists types attributes and whether will will always be included or optionally included in each test case.
4
+
5
+ | Name | Present | Description |
6
+ |---|---|---|
7
+ | system_hostname | always | the hostname of the machine |
8
+ | db_hostname | sometimes | the hostname reported by the database adapter |
9
+ | product | always | the database product for this configuration
10
+ | port | sometimes | the port reported by the database adapter |
11
+ | unix_socket | sometimes |the path to a unix domain socket reported by a database adapter |
12
+ | db_path | sometimes |the path to a filesystem database |
13
+ | expected\_instance\_metric | always | the instance metric expected to be generated from the given attributes |
14
+
15
+ ## Implementing the test cases
16
+ The idea behind these test cases are that you are able to determine a set of configuration properties from a database connection, and based on those properties you should generate the `expected_instance_metric`. Sometimes the properties available are minimal and will mean that you will need to fall back to defaults to obtain some of the information. When there is missing information from a database adapter the guiding principle is to fill in the defaults when they can be inferred, but do not make guesses that could be incorrect or misleading. Some agents may have access to better data and may not need to make inferences. If this applies to your agent then many of these tests will not be applicable.
@@ -0,0 +1,120 @@
1
+ [
2
+ {
3
+ "name": "instance metric uses system hostname when db reports localhost",
4
+ "system_hostname": "datanerd-01",
5
+ "db_hostname": "localhost",
6
+ "product": "Postgres",
7
+ "port": 5432,
8
+ "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5432"
9
+ },
10
+ {
11
+ "name": "instance metric uses system hostname when db reports host as loopback adapter IPv4",
12
+ "system_hostname": "datanerd-01",
13
+ "db_hostname": "127.0.0.1",
14
+ "product": "Postgres",
15
+ "port": 5252,
16
+ "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5252"
17
+ },
18
+ {
19
+ "name": "instance metric uses system hostname when db reports host as loopback adapter IPv6",
20
+ "system_hostname": "datanerd-01",
21
+ "db_hostname": "0:0:0:0:0:0:0:1",
22
+ "product": "Postgres",
23
+ "port": 5252,
24
+ "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5252"
25
+ },
26
+ {
27
+ "name": "instance metric uses system hostname when db reports host as loopback adapter IPv6 shorthand",
28
+ "system_hostname": "datanerd-01",
29
+ "db_hostname": "::1",
30
+ "product": "Postgres",
31
+ "port": 5252,
32
+ "expected_instance_metric": "Datastore/instance/Postgres/datanerd-01/5252"
33
+ },
34
+ {
35
+ "name": "instance metric uses system hostname when db reports default host IPv4",
36
+ "system_hostname": "datanerd-01",
37
+ "db_hostname": "0.0.0.0",
38
+ "product": "MySQL",
39
+ "port": 3600,
40
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/3600"
41
+ },
42
+ {
43
+ "name": "instance metric uses system hostname when db reports default host IPv6",
44
+ "system_hostname": "datanerd-01",
45
+ "db_hostname": "0:0:0:0:0:0:0:0",
46
+ "product": "MySQL",
47
+ "port": 5757,
48
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/5757"
49
+ },
50
+ {
51
+ "name": "instance metric uses system hostname when db reports default host IPv6 shorthand",
52
+ "system_hostname": "datanerd-01",
53
+ "db_hostname": "::",
54
+ "product": "MySQL",
55
+ "port": 5757,
56
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/5757"
57
+ },
58
+ {
59
+ "name": "instance metric uses db host when not local",
60
+ "system_hostname": "datanerd-01",
61
+ "db_hostname": "accounts-db",
62
+ "product": "MySQL",
63
+ "port": 8420,
64
+ "expected_instance_metric": "Datastore/instance/MySQL/accounts-db/8420"
65
+ },
66
+ {
67
+ "name": "detects default UDS case",
68
+ "system_hostname": "datanerd-01",
69
+ "db_hostname": "localhost",
70
+ "product": "MySQL",
71
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/default"
72
+ },
73
+ {
74
+ "name": "instance metric uses unix socket path if provided",
75
+ "system_hostname": "datanerd-01",
76
+ "db_hostname": "localhost",
77
+ "product": "MySQL",
78
+ "unix_socket": "/var/mysql/mysql.sock",
79
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01//var/mysql/mysql.sock"
80
+ },
81
+ {
82
+ "name": "instance metric reports unknown if host empty",
83
+ "system_hostname": "datanerd-01",
84
+ "db_hostname": "",
85
+ "product": "MySQL",
86
+ "port": 3600,
87
+ "expected_instance_metric": "Datastore/instance/MySQL/unknown/3600"
88
+ },
89
+ {
90
+ "name": "instance metric reports unknown if port empty",
91
+ "system_hostname": "datanerd-01",
92
+ "db_hostname": "localhost",
93
+ "product": "MySQL",
94
+ "port": "",
95
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/unknown"
96
+ },
97
+ {
98
+ "name": "instance metric reports unknown if unix_socket empty",
99
+ "system_hostname": "datanerd-01",
100
+ "db_hostname": "localhost",
101
+ "product": "MySQL",
102
+ "unix_socket": "",
103
+ "expected_instance_metric": "Datastore/instance/MySQL/datanerd-01/unknown"
104
+ },
105
+ {
106
+ "name": "instance metric with ip v6 host",
107
+ "system_hostname": "datanerd-01",
108
+ "db_hostname": "2001:0DB8:AC10:FE01:0000:0000:0000:0000",
109
+ "product": "Postgres",
110
+ "port": 5432,
111
+ "expected_instance_metric": "Datastore/instance/Postgres/2001:0DB8:AC10:FE01:0000:0000:0000:0000/5432"
112
+ },
113
+ {
114
+ "name": "instance metric for filesystem database",
115
+ "system_hostname": "datanerd-01",
116
+ "product": "SQLite",
117
+ "database_path": "/db/all.sqlite3",
118
+ "expected_instance_metric": "Datastore/instance/SQLite/datanerd-01//db/all.sqlite3"
119
+ }
120
+ ]
@@ -26,6 +26,15 @@ module NewRelic
26
26
  assert_equal expected, result
27
27
  end
28
28
 
29
+ def test_instance_metric_for
30
+ instance_id = "localhost/1337807"
31
+ host = "localhost"
32
+ port = "1337807"
33
+ expected = "Datastore/instance/JonanDB/#{host}/#{port}"
34
+ result = Datastores::MetricHelper.instance_metric_for(@product, host, port)
35
+ assert_equal expected, result
36
+ end
37
+
29
38
  def test_metrics_for_in_web_context
30
39
  Transaction.stubs(:recording_web_transaction?).returns(true)
31
40
  expected = [
@@ -109,6 +118,35 @@ module NewRelic
109
118
  assert_equal expected, result
110
119
  end
111
120
 
121
+ def test_unscoped_metrics_for_with_instance_identifier
122
+ Transaction.stubs(:recording_web_transaction?).returns(false)
123
+ expected = [
124
+ "Datastore/instance/JonanDB/localhost/1337807",
125
+ "Datastore/JonanDB/allOther",
126
+ "Datastore/JonanDB/all",
127
+ "Datastore/allOther",
128
+ "Datastore/all"
129
+ ]
130
+
131
+ result = Datastores::MetricHelper.unscoped_metrics_for(@product, @operation, nil, "localhost", "1337807")
132
+ assert_equal expected, result
133
+ end
134
+
135
+ def test_unscoped_metrics_for_with_instance_identifier_and_instance_reporting_disabled
136
+ with_config(:'datastore_tracer.instance_reporting.enabled' => false) do
137
+ Transaction.stubs(:recording_web_transaction?).returns(false)
138
+ expected = [
139
+ "Datastore/JonanDB/allOther",
140
+ "Datastore/JonanDB/all",
141
+ "Datastore/allOther",
142
+ "Datastore/all"
143
+ ]
144
+
145
+ result = Datastores::MetricHelper.unscoped_metrics_for(@product, @operation, nil, "localhost/1337807")
146
+ assert_equal expected, result
147
+ end
148
+ end
149
+
112
150
  def test_product_operation_collection_for_obeys_collection_and_operation_overrides
113
151
  in_transaction do
114
152
  NewRelic::Agent.with_database_metric_name("Model", "new_method") do
@@ -52,6 +52,46 @@ class NewRelic::Agent::Instrumentation::ActiveRecordSubscriberTest < Minitest::T
52
52
  )
53
53
  end
54
54
 
55
+ def test_records_datastore_instance_metric_for_supported_adapter
56
+ config = { :adapter => "mysql", :host => "jonan.gummy_planet", :port => 3306 }
57
+ @subscriber.stubs(:active_record_config).returns(config)
58
+
59
+ simulate_query(2)
60
+
61
+ assert_metrics_recorded('Datastore/instance/MySQL/jonan.gummy_planet/3306')
62
+ end
63
+
64
+ def test_does_not_record_datastore_instance_metric_for_unsupported_adapter
65
+ config = { :adapter => "JonanDB", :host => "jonan.gummy_planet" }
66
+ @subscriber.stubs(:active_record_config).returns(config)
67
+
68
+ simulate_query(2)
69
+
70
+ assert_metrics_not_recorded('Datastore/instance/JonanDB/jonan.gummy_planet/default')
71
+ end
72
+
73
+ def test_does_not_record_datastore_instance_metric_if_disabled
74
+ with_config('datastore_tracer.instance_reporting.enabled' => false) do
75
+ config = { :host => "jonan.gummy_planet" }
76
+ @subscriber.stubs(:active_record_config).returns(config)
77
+
78
+ simulate_query(2)
79
+
80
+ assert_metrics_not_recorded('Datastore/instance/ActiveRecord/jonan.gummy_planet/default')
81
+ end
82
+ end
83
+
84
+ def test_does_not_record_database_name_if_disabled
85
+ config = { :host => "jonan.gummy_planet", :database => "pizza_cube" }
86
+ @subscriber.stubs(:active_record_config).returns(config)
87
+ with_config('datastore_tracer.database_name_reporting.enabled' => false) do
88
+ in_transaction { simulate_query(2) }
89
+ end
90
+ sample = NewRelic::Agent.instance.transaction_sampler.last_sample
91
+ node = find_node_with_name_matching sample, /Datastore\//
92
+ refute node.params.key?(:database_name)
93
+ end
94
+
55
95
  def test_records_nothing_if_tracing_disabled
56
96
  freeze_time
57
97
 
@@ -0,0 +1,186 @@
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
+ require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
5
+ require 'new_relic/agent/instrumentation/active_record_helper'
6
+
7
+ module NewRelic
8
+ module Agent
9
+ module Instrumentation
10
+ module ActiveRecordHelper
11
+ class InstanceIdentificationTest < Minitest::Test
12
+
13
+ def test_for_constructs_id_with_configured_host_and_port
14
+ config = {
15
+ :host => "jonan.local",
16
+ :port => 42
17
+ }
18
+
19
+ host = InstanceIdentification.host(config)
20
+ ppid = InstanceIdentification.port_path_or_id(config)
21
+
22
+ assert_equal "jonan.local", host
23
+ assert_equal "42", ppid
24
+ end
25
+
26
+ def test_for_constructs_id_with_unspecified_configuration
27
+ NewRelic::Agent::Hostname.stubs(:get).returns("jonan.pizza_cube")
28
+ config = {}
29
+ host = InstanceIdentification.host(config)
30
+ ppid = InstanceIdentification.port_path_or_id(config)
31
+
32
+ assert_equal "jonan.pizza_cube", host
33
+ assert_equal "default", ppid
34
+ end
35
+
36
+ def test_for_constructs_id_with_weird_configs
37
+ config = {
38
+ :host => "",
39
+ :port => ""
40
+ }
41
+
42
+ host = InstanceIdentification.host(config)
43
+ ppid = InstanceIdentification.port_path_or_id(config)
44
+
45
+ assert_equal "unknown", host
46
+ assert_equal "unknown", ppid
47
+ end
48
+
49
+ def test_for_constructs_id_with_configured_host_without_port
50
+ config = { :host => "jonan.gummy_planet" }
51
+
52
+ host = InstanceIdentification.host(config)
53
+ ppid = InstanceIdentification.port_path_or_id(config)
54
+
55
+ assert_equal "jonan.gummy_planet", host
56
+ assert_equal "default", ppid
57
+ end
58
+
59
+ def test_for_constructs_id_with_port_without_host
60
+ NewRelic::Agent::Hostname.stubs(:get).returns("jonan.pizza_cube")
61
+ config = { :port => 1337 }
62
+
63
+ host = InstanceIdentification.host(config)
64
+ ppid = InstanceIdentification.port_path_or_id(config)
65
+
66
+ assert_equal "jonan.pizza_cube", host
67
+ assert_equal "1337", ppid
68
+ end
69
+
70
+ def test_for_constructs_id_with_detected_localhost
71
+ NewRelic::Agent::Hostname.stubs(:get).returns("jonan.pizza_cube")
72
+
73
+ %w[localhost 0.0.0.0 127.0.0.1 0:0:0:0:0:0:0:1 0:0:0:0:0:0:0:0 ::1 ::].each do |host|
74
+ config = { :host => host }
75
+
76
+ host = InstanceIdentification.host(config)
77
+ ppid = InstanceIdentification.port_path_or_id(config)
78
+
79
+ assert_equal "jonan.pizza_cube", host
80
+ assert_equal "default", ppid
81
+ end
82
+ end
83
+
84
+ def test_for_constructs_id_with_default_port
85
+ config = {
86
+ :adapter => "mysql",
87
+ :host => "jonan.gummy_planet"
88
+ }
89
+
90
+ host = InstanceIdentification.host(config)
91
+ ppid = InstanceIdentification.port_path_or_id(config)
92
+
93
+ assert_equal "jonan.gummy_planet", host
94
+ assert_equal "3306", ppid
95
+ end
96
+
97
+ def test_for_constructs_id_with_postgres_directory
98
+ NewRelic::Agent::Hostname.stubs(:get).returns("jonan.pizza_cube")
99
+ config = {
100
+ :adapter => "postgresql",
101
+ :host => "/tmp"
102
+ }
103
+
104
+ host = InstanceIdentification.host(config)
105
+ ppid = InstanceIdentification.port_path_or_id(config)
106
+
107
+ assert_equal "jonan.pizza_cube", host
108
+ assert_equal "default", ppid
109
+ end
110
+
111
+ def test_for_constructs_id_with_mysql_socket
112
+ NewRelic::Agent::Hostname.stubs(:get).returns("jonan.pizza_cube")
113
+ %w[ mysql mysql2 jdbcmysql ].each do |adapter|
114
+ config = {
115
+ :adapter => adapter,
116
+ :socket => "/var/run/mysqld.sock"
117
+ }
118
+
119
+ host = InstanceIdentification.host(config)
120
+ ppid = InstanceIdentification.port_path_or_id(config)
121
+
122
+ assert_equal "jonan.pizza_cube", host
123
+ assert_equal "/var/run/mysqld.sock", ppid
124
+ end
125
+ end
126
+
127
+ def test_supports_supported_adapters
128
+ %w(mysql mysql2 postgresql).each do |adapter|
129
+ assert InstanceIdentification.supported_adapter?({:adapter => adapter })
130
+ end
131
+ end
132
+
133
+ SUPPORTED_PRODUCTS = ["Postgres", "MySQL"]
134
+
135
+ load_cross_agent_test('datastores/datastore_instances').each do |test|
136
+ next unless SUPPORTED_PRODUCTS.include?(test['product'])
137
+
138
+ define_method :"test_#{test['name'].tr(' ', '_')}" do
139
+ NewRelic::Agent.drop_buffered_data
140
+ NewRelic::Agent::Hostname.stubs(:get).returns(test['system_hostname'])
141
+
142
+ config = convert_test_case_to_config test
143
+
144
+ product, operation, collection = ActiveRecordHelper.product_operation_collection_for "Blog Find", nil , config[:adapter]
145
+ host = ActiveRecordHelper::InstanceIdentification.host(config)
146
+ port_path_or_id = ActiveRecordHelper::InstanceIdentification.port_path_or_id(config)
147
+
148
+ segment = NewRelic::Agent::Transaction.start_datastore_segment product, operation, collection, host, port_path_or_id
149
+ segment.finish
150
+
151
+ assert_metrics_recorded test['expected_instance_metric']
152
+ end
153
+ end
154
+
155
+ CONFIG_NAMES = {
156
+ "db_hostname" => :host,
157
+ "unix_socket" => :socket,
158
+ "port" => :port,
159
+ "product" => :adapter
160
+ }
161
+
162
+ def convert_test_case_to_config test_case
163
+ config = test_case.inject({}) do |memo, (k,v)|
164
+ if config_key = CONFIG_NAMES[k]
165
+ memo[config_key] = v
166
+ end
167
+ memo
168
+ end
169
+ convert_product_to_adapter config
170
+ config
171
+ end
172
+
173
+ PRODUCT_TO_ADAPTER_NAMES = {
174
+ "Postgres" => "postgresql",
175
+ "MySQL" => "mysql",
176
+ "SQLite" => "sqlite3"
177
+ }
178
+
179
+ def convert_product_to_adapter config
180
+ config[:adapter] = PRODUCT_TO_ADAPTER_NAMES[config[:adapter]]
181
+ end
182
+ end
183
+ end
184
+ end
185
+ end
186
+ end
@@ -370,6 +370,49 @@ class NewRelic::Agent::SqlSamplerTest < Minitest::Test
370
370
  assert_equal expected, trace.to_collector_array(marshaller.default_encoder)
371
371
  end
372
372
 
373
+ def test_to_collector_array_with_database_instance_params
374
+ statement = NewRelic::Agent::Database::Statement.new("query", nil, nil, nil, nil, "jonan.gummy_planet", "1337", "pizza_cube")
375
+ slow = NewRelic::Agent::SlowSql.new(statement, "transaction", 1.0)
376
+ trace = NewRelic::Agent::SqlTrace.new("query", slow, "path", "uri")
377
+ encoder = NewRelic::Agent::NewRelicService::Encoders::Identity
378
+
379
+ params = trace.to_collector_array(encoder).last
380
+
381
+ assert_equal "jonan.gummy_planet", params[:host]
382
+ assert_equal "1337", params[:port_path_or_id]
383
+ assert_equal "pizza_cube", params[:database_name]
384
+ end
385
+
386
+ def test_to_collector_array_with_instance_reporting_disabled
387
+ with_config(:'datastore_tracer.instance_reporting.enabled' => false) do
388
+ statement = NewRelic::Agent::Database::Statement.new("query", nil, nil, nil, nil, "jonan.gummy_planet", "1337", "pizza_cube")
389
+ slow = NewRelic::Agent::SlowSql.new(statement, "transaction", 1.0)
390
+ trace = NewRelic::Agent::SqlTrace.new("query", slow, "path", "uri")
391
+ encoder = NewRelic::Agent::NewRelicService::Encoders::Identity
392
+
393
+ params = trace.to_collector_array(encoder).last
394
+
395
+ refute params.key? :host
396
+ refute params.key? :port_path_or_id
397
+ assert_equal "pizza_cube", params[:database_name]
398
+ end
399
+ end
400
+
401
+ def test_to_collector_array_with_database_name_reporting_disabled
402
+ with_config(:'datastore_tracer.database_name_reporting.enabled' => false) do
403
+ statement = NewRelic::Agent::Database::Statement.new("query", nil, nil, nil, nil, "jonan.gummy_planet", "1337", "pizza_cube")
404
+ slow = NewRelic::Agent::SlowSql.new(statement, "transaction", 1.0)
405
+ trace = NewRelic::Agent::SqlTrace.new("query", slow, "path", "uri")
406
+ encoder = NewRelic::Agent::NewRelicService::Encoders::Identity
407
+
408
+ params = trace.to_collector_array(encoder).last
409
+
410
+ assert_equal "jonan.gummy_planet", params[:host]
411
+ assert_equal "1337", params[:port_path_or_id]
412
+ refute params.key? :database_name
413
+ end
414
+ end
415
+
373
416
  def test_merge_without_existing_trace
374
417
  query = "select * from test"
375
418
  statement = NewRelic::Agent::Database::Statement.new(query, {})
@@ -63,6 +63,103 @@ module NewRelic
63
63
  ]
64
64
  end
65
65
 
66
+ def test_segment_records_expected_metrics_with_instance_identifier
67
+ Transaction.stubs(:recording_web_transaction?).returns(true)
68
+
69
+ segment = DatastoreSegment.new "SQLite", "select", nil, "localhost", "1337807"
70
+ segment.start
71
+ advance_time 1
72
+ segment.finish
73
+
74
+ assert_metrics_recorded [
75
+ "Datastore/instance/SQLite/localhost/1337807",
76
+ "Datastore/operation/SQLite/select",
77
+ "Datastore/SQLite/allWeb",
78
+ "Datastore/SQLite/all",
79
+ "Datastore/allWeb",
80
+ "Datastore/all"
81
+ ]
82
+ end
83
+
84
+ def test_segment_does_not_record_instance_id_metrics_when_disabled
85
+ with_config(:'datastore_tracer.instance_reporting.enabled' => false) do
86
+ Transaction.stubs(:recording_web_transaction?).returns(true)
87
+
88
+ segment = DatastoreSegment.new "SQLite", "select", nil, "localhost/1337807"
89
+ segment.start
90
+ advance_time 1
91
+ segment.finish
92
+
93
+ assert_metrics_not_recorded "Datastore/instance/SQLite/localhost/1337807"
94
+ end
95
+ end
96
+
97
+ def test_add_instance_identifier_segment_parameter
98
+ segment = nil
99
+
100
+ in_transaction do
101
+ segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select", nil, "localhost", "1337807"
102
+ advance_time 1
103
+ segment.finish
104
+ end
105
+
106
+ sample = NewRelic::Agent.agent.transaction_sampler.last_sample
107
+ node = find_node_with_name(sample, segment.name)
108
+
109
+ assert_equal "localhost", node.params[:host]
110
+ assert_equal "1337807", node.params[:port_path_or_id]
111
+ end
112
+
113
+ def test_does_not_add_instance_identifier_segment_parameter_when_disabled
114
+ with_config(:'datastore_tracer.instance_reporting.enabled' => false) do
115
+ segment = nil
116
+
117
+ in_transaction do
118
+ segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select", nil, "localhost", "1337807"
119
+ advance_time 1
120
+ segment.finish
121
+ end
122
+
123
+ sample = NewRelic::Agent.agent.transaction_sampler.last_sample
124
+ node = find_node_with_name(sample, segment.name)
125
+
126
+ refute node.params.key? :host
127
+ refute node.params.key? :port_path_or_id
128
+ end
129
+ end
130
+
131
+ def test_add_database_name_segment_parameter
132
+ segment = nil
133
+
134
+ in_transaction do
135
+ segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select", nil, nil, nil, "pizza_cube"
136
+ advance_time 1
137
+ segment.finish
138
+ end
139
+
140
+ sample = NewRelic::Agent.agent.transaction_sampler.last_sample
141
+ node = find_node_with_name(sample, segment.name)
142
+
143
+ assert_equal node.params[:database_name], "pizza_cube"
144
+ end
145
+
146
+ def test_does_not_add_database_name_segment_parameter_when_disabled
147
+ with_config(:'datastore_tracer.database_name_reporting.enabled' => false) do
148
+ segment = nil
149
+
150
+ in_transaction do
151
+ segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select", nil, nil, nil, "pizza_cube"
152
+ advance_time 1
153
+ segment.finish
154
+ end
155
+
156
+ sample = NewRelic::Agent.agent.transaction_sampler.last_sample
157
+ node = find_node_with_name(sample, segment.name)
158
+
159
+ refute node.params.key? :database_name
160
+ end
161
+ end
162
+
66
163
  def test_notice_sql
67
164
  in_transaction do
68
165
  segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select"
@@ -78,6 +175,27 @@ module NewRelic
78
175
  end
79
176
  end
80
177
 
178
+ def test_notice_sql_creates_database_statement_with_identifier
179
+ in_transaction do
180
+ segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select", nil, "jonan.gummy_planet", "1337"
181
+ segment.notice_sql "select * from blogs"
182
+ segment.finish
183
+
184
+ assert_equal "jonan.gummy_planet", segment.sql_statement.host
185
+ assert_equal "1337", segment.sql_statement.port_path_or_id
186
+ end
187
+ end
188
+
189
+ def test_notice_sql_creates_database_statement_with_database_name
190
+ in_transaction do
191
+ segment = NewRelic::Agent::Transaction.start_datastore_segment "SQLite", "select", nil, nil, nil, "pizza_cube"
192
+ segment.notice_sql "select * from blogs"
193
+ segment.finish
194
+
195
+ assert_equal "pizza_cube", segment.sql_statement.database_name
196
+ end
197
+ end
198
+
81
199
  def test_internal_notice_sql
82
200
  explainer = stub(:explainer)
83
201
  in_transaction do
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: newrelic_rpm
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.16.3.323
4
+ version: 3.17.0.325
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tim Krajcar
@@ -11,7 +11,7 @@ authors:
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2016-09-21 00:00:00.000000000 Z
14
+ date: 2016-10-11 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: rake
@@ -548,6 +548,8 @@ files:
548
548
  - test/fixtures/cross_agent_tests/cat_map.json
549
549
  - test/fixtures/cross_agent_tests/data_transport/data_transport.json
550
550
  - test/fixtures/cross_agent_tests/data_transport/data_transport.md
551
+ - test/fixtures/cross_agent_tests/datastores/README.md
552
+ - test/fixtures/cross_agent_tests/datastores/datastore_instances.json
551
553
  - test/fixtures/cross_agent_tests/docker_container_id/README.md
552
554
  - test/fixtures/cross_agent_tests/docker_container_id/cases.json
553
555
  - test/fixtures/cross_agent_tests/docker_container_id/docker-0.9.1.txt
@@ -1033,6 +1035,7 @@ files:
1033
1035
  - test/new_relic/agent/instrumentation/active_record_subscriber_test.rb
1034
1036
  - test/new_relic/agent/instrumentation/controller_instrumentation_test.rb
1035
1037
  - test/new_relic/agent/instrumentation/delayed_job_instrumentation_test.rb
1038
+ - test/new_relic/agent/instrumentation/instance_identification_test.rb
1036
1039
  - test/new_relic/agent/instrumentation/instrumentation_test.rb
1037
1040
  - test/new_relic/agent/instrumentation/metric_frame_test.rb
1038
1041
  - test/new_relic/agent/instrumentation/middleware_proxy_test.rb