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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 1bc3b5920b3b7ea0c2997962168a45b01127ea75
4
- data.tar.gz: e48ce51b1f0bb6847fa09ab23dfa005a3f068311
3
+ metadata.gz: 3e8eefedf383e69a41a5dfb8f9c607a8c93f3390
4
+ data.tar.gz: 4aa588cdb66a876439bc666ab40a5dd8bb829a50
5
5
  SHA512:
6
- metadata.gz: 47057832e75c7c3319433fb03ee49475136c1e8c284fa3b238d5e591d185f6f5222cfb7b8c3f6696b0aa99bcbfae827ea964beb7712a0b15954dea8b72b69033
7
- data.tar.gz: 6428ef632749929082e53b93e74df3239c41430bf7688ab6c28822dcf3889cf23b1e17501f1f2eb75ee186327231ca3fbd77f6ca1963f97f8c6041ed7c6aef0e
6
+ metadata.gz: aa28051c2fe6daef0a6adbbb71cb884da9ecb83ec26835ad6e5de7ddbbfd43180bdd220669ca226dbba226668b7f9e1c56274ecc01e0fe3dd9af7a0db901f9da
7
+ data.tar.gz: 6e9e0e4b9183378f59fab486acf5ca1eeae547f0850f6657ba50cf3df7122a9a665bfb5ce4207439c345775286516ac7c48baf3958a9182715cbe68c6ff490a4
data/CHANGELOG CHANGED
@@ -1,6 +1,47 @@
1
1
  # New Relic Ruby Agent Release Notes #
2
2
 
3
+ ## v3.15.2 ##
4
+
5
+ * Run explain plans on parameterized slow queries in AR4
6
+
7
+ In our ActiveRecord 4 instrumentation, we moved to tracing slow queries
8
+ using the payloads from ActiveSupport::Notifications `sql.active_record`
9
+ events. As a result, we were unable to run explain plans on parameterized
10
+ queries. This has now been updated to pass along and use the parameter values,
11
+ when available, to get the explain plans.
12
+
13
+ * Fix getMore metric grouping issue in Mongo 2.2.x instrumentation
14
+
15
+ A metric grouping issue had cropped up when using the most recent Mongo gem
16
+ (2.2.0) with the most recent release of the server (3.2.4). We now have a more
17
+ future-proof setup for preventing these.
18
+
19
+ * Restore older DataMapper support after password obfuscation fix
20
+
21
+ Back in 3.14.3, we released a fix to avoid inadvertently sending sensitive
22
+ information from DataMapper SQLErrors. Our implementation did not account for
23
+ DataMapper versions below v0.10.0 not implementing the #options accessor.
24
+ Thanks Bram de Vries for the fix to our fix!
25
+
26
+ * Padrino 0.13.1 Support
27
+
28
+ Users with Padrino 0.13.x apps were previously seeing the default transaction
29
+ name "(unknown)" for all of their routes. We now provide the full Padrino
30
+ route template in transaction names, including any parameter placeholders.
31
+ Thanks Robert Schulze for the contribution!
32
+
33
+ * Update transaction naming for Grape 0.16.x
34
+
35
+ In Grape 0.16.x, `route_` methods are no longer prefixed. Thanks to Daniel
36
+ Doubrovkine for the contribution!
37
+
38
+ * Fix name collision on method created for default metric name fix
39
+
40
+ We had a name collision with the yard gem, which sets a `class_name` method
41
+ on Module. We've renamed our internal method to `derived_class_name` instead.
42
+
3
43
  ## v3.15.1 ##
44
+
4
45
  * Rack 2 alpha support
5
46
 
6
47
  This release includes experimental support for Rack 2 as of 2.0.0.alpha.
@@ -1033,7 +1033,7 @@ module NewRelic
1033
1033
  # drop any stored data and reset to a clean state.
1034
1034
  #
1035
1035
  # #merge!(payload)
1036
- # merge the given pyalod back into the internal buffer of the
1036
+ # merge the given payload back into the internal buffer of the
1037
1037
  # container, so that it may be harvested again later.
1038
1038
  #
1039
1039
  def harvest_and_send_from_container(container, endpoint)
@@ -8,7 +8,7 @@ module NewRelic
8
8
  # monitoring.
9
9
  #
10
10
  # If the agent is in a monitored environment (e.g. production) it will
11
- # attempt to avoid starting at "inapproriate" times, for example in an IRB
11
+ # attempt to avoid starting at "inappropriate" times, for example in an IRB
12
12
  # session. On Heroku, logs typically go to STDOUT so agent logs can spam
13
13
  # the console during interactive sessions.
14
14
  #
@@ -3,9 +3,8 @@
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
4
 
5
5
  require 'singleton'
6
- require 'new_relic/agent/database/obfuscation_helpers'
6
+ require 'new_relic/agent/database/explain_plan_helpers'
7
7
  require 'new_relic/agent/database/obfuscator'
8
- require 'new_relic/agent/database/postgres_explain_obfuscator'
9
8
 
10
9
  module NewRelic
11
10
  # columns for a mysql explain plan
@@ -95,157 +94,18 @@ module NewRelic
95
94
  ConnectionManager.instance.close_connections
96
95
  end
97
96
 
98
- # This takes a connection config hash from ActiveRecord or Sequel and
99
- # returns a string describing the associated database adapter
100
- def adapter_from_config(config)
101
- if config[:adapter]
102
- return config[:adapter].to_s
103
- elsif config[:uri] && config[:uri].to_s =~ /^jdbc:([^:]+):/
104
- # This case is for Sequel with the jdbc-mysql, jdbc-postgres, or
105
- # jdbc-sqlite3 gems.
106
- return $1
107
- end
108
- end
109
-
110
97
  # Perform this in the runtime environment of a managed
111
98
  # application, to explain the sql statement executed within a
112
- # node of a transaction sample. Returns an array of
113
- # explanations (which is an array rows consisting of an array of
114
- # strings for each column returned by the the explain query)
115
- # Note this happens only for statements whose execution time
116
- # exceeds a threshold (e.g. 500ms) and only within the slowest
117
- # transaction in a report period, selected for shipment to New
118
- # Relic
119
- def explain_sql(sql, connection_config, explainer=nil)
120
- return nil unless sql && explainer && connection_config
121
- statement = sql.split(";\n")[0] # only explain the first
122
- explain_plan = explain_statement(statement, connection_config, explainer)
123
- return explain_plan || []
124
- end
125
-
126
- SUPPORTED_ADAPTERS_FOR_EXPLAIN = %w[postgres postgresql mysql2 mysql sqlite].freeze
127
-
128
- def explain_statement(statement, config, explainer)
129
- return unless explainer && is_select?(statement)
130
-
131
- if statement[-3,3] == '...'
132
- NewRelic::Agent.logger.debug('Unable to collect explain plan for truncated query.')
133
- return
134
- end
135
-
136
- if parameterized?(statement)
137
- NewRelic::Agent.logger.debug('Unable to collect explain plan for parameterized query.')
138
- return
139
- end
140
-
141
- adapter = adapter_from_config(config)
142
- if !SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter)
143
- NewRelic::Agent.logger.debug("Not collecting explain plan because an unknown connection adapter ('#{adapter}') was used.")
144
- return
145
- end
146
-
147
- handle_exception_in_explain do
148
- start = Time.now
149
- plan = explainer.call(config, statement)
150
- ::NewRelic::Agent.record_metric("Supportability/Database/execute_explain_plan", Time.now - start)
151
- return process_resultset(plan, adapter) if plan
152
- end
153
- end
154
-
155
- def process_resultset(results, adapter)
156
- case adapter.to_s
157
- when 'postgres', 'postgresql'
158
- process_explain_results_postgres(results)
159
- when 'mysql2'
160
- process_explain_results_mysql2(results)
161
- when 'mysql'
162
- process_explain_results_mysql(results)
163
- when 'sqlite'
164
- process_explain_results_sqlite(results)
165
- end
166
- end
167
-
168
- QUERY_PLAN = 'QUERY PLAN'.freeze
169
-
170
- def process_explain_results_postgres(results)
171
- if results.is_a?(String)
172
- query_plan_string = results
173
- else
174
- lines = []
175
- results.each { |row| lines << row[QUERY_PLAN] }
176
- query_plan_string = lines.join("\n")
177
- end
178
-
179
- unless record_sql_method == :raw
180
- query_plan_string = NewRelic::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string)
181
- end
182
- values = query_plan_string.split("\n").map { |line| [line] }
183
-
184
- [[QUERY_PLAN], values]
185
- end
186
-
187
- # Sequel returns explain plans as just one big pre-formatted String
188
- # In that case, we send a nil headers array, and the single string
189
- # wrapped in an array for the values.
190
- # Note that we don't use this method for Postgres explain plans, since
191
- # they need to be passed through the explain plan obfuscator first.
192
- def string_explain_plan_results(results)
193
- [nil, [results]]
194
- end
195
-
196
- def process_explain_results_mysql(results)
197
- return string_explain_plan_results(results) if results.is_a?(String)
198
- headers = []
199
- values = []
200
- if results.is_a?(Array)
201
- # We're probably using the jdbc-mysql gem for JRuby, which will give
202
- # us an array of hashes.
203
- headers = results.first.keys
204
- results.each do |row|
205
- values << headers.map { |h| row[h] }
206
- end
207
- else
208
- # We're probably using the native mysql driver gem, which will give us
209
- # a Mysql::Result object that responds to each_hash
210
- results.each_hash do |row|
211
- headers = row.keys
212
- values << headers.map { |h| row[h] }
213
- end
214
- end
215
- [headers, values]
216
- end
217
-
218
- def process_explain_results_mysql2(results)
219
- return string_explain_plan_results(results) if results.is_a?(String)
220
- headers = results.fields
221
- values = []
222
- results.each { |row| values << row }
223
- [headers, values]
224
- end
225
-
226
- SQLITE_EXPLAIN_COLUMNS = %w[addr opcode p1 p2 p3 p4 p5 comment]
227
-
228
- def process_explain_results_sqlite(results)
229
- return string_explain_plan_results(results) if results.is_a?(String)
230
- headers = SQLITE_EXPLAIN_COLUMNS
231
- values = []
232
- results.each do |row|
233
- values << headers.map { |h| row[h] }
234
- end
235
- [headers, values]
236
- end
237
-
238
- def handle_exception_in_explain
239
- yield
240
- rescue => e
241
- begin
242
- # guarantees no throw from explain_sql
243
- ::NewRelic::Agent.logger.error("Error getting query plan:", e)
244
- nil
245
- rescue
246
- # double exception. throw up your hands
247
- nil
248
- end
99
+ # node of a transaction sample. Returns an array of two arrays.
100
+ # The first array contains the headers, while the second consists of
101
+ # arrays of strings for each column returned by the explain query.
102
+ # Note this happens only for statements whose execution time exceeds
103
+ # a threshold (e.g. 500ms) and only within the slowest transaction
104
+ # in a report period, selected for shipment to New Relic
105
+ def explain_sql(statement)
106
+ return nil unless statement.sql && statement.explainer && statement.config
107
+ statement.sql = statement.sql.split(";\n")[0] # only explain the first
108
+ return statement.explain || []
249
109
  end
250
110
 
251
111
  KNOWN_OPERATIONS = [
@@ -273,14 +133,6 @@ module NewRelic
273
133
  end
274
134
  end
275
135
 
276
- def is_select?(statement)
277
- parse_operation_from_query(statement) == 'select'
278
- end
279
-
280
- def parameterized?(statement)
281
- Obfuscator.instance.obfuscate_single_quote_literals(statement) =~ /\$\d+/
282
- end
283
-
284
136
  class ConnectionManager
285
137
  include Singleton
286
138
 
@@ -318,33 +170,88 @@ module NewRelic
318
170
  end
319
171
 
320
172
  class Statement
321
- attr_accessor :sql, :config, :explainer
173
+ include ExplainPlanHelpers
174
+
175
+ attr_accessor :sql, :config, :explainer, :binds, :name
322
176
 
323
- def initialize(sql, config={}, explainer=nil)
177
+ DEFAULT_QUERY_NAME = "SQL".freeze
178
+
179
+ def initialize(sql, config={}, explainer=nil, binds=[], name=DEFAULT_QUERY_NAME)
324
180
  @sql = Database.capture_query(sql)
325
181
  @config = config
326
182
  @explainer = explainer
183
+ @binds = binds
184
+ @name = name
327
185
  end
328
186
 
187
+ # This takes a connection config hash from ActiveRecord or Sequel and
188
+ # returns a symbol describing the associated database adapter
329
189
  def adapter
330
- config && config[:adapter] && symbolized_adapter(config[:adapter].to_s.downcase)
190
+ return unless @config
191
+
192
+ @adapter ||= if @config[:adapter]
193
+ symbolized_adapter(@config[:adapter].to_s.downcase)
194
+ elsif @config[:uri] && @config[:uri].to_s =~ /^jdbc:([^:]+):/
195
+ # This case is for Sequel with the jdbc-mysql, jdbc-postgres, or jdbc-sqlite3 gems.
196
+ symbolized_adapter($1)
197
+ else
198
+ nil
199
+ end
200
+ end
201
+
202
+ def explain
203
+ return unless explainable?
204
+ handle_exception_in_explain do
205
+ start = Time.now
206
+ plan = @explainer.call(self)
207
+ ::NewRelic::Agent.record_metric("Supportability/Database/execute_explain_plan", Time.now - start)
208
+ return process_resultset(plan, adapter) if plan
209
+ end
331
210
  end
332
211
 
212
+ private
213
+
333
214
  POSTGRES_PREFIX = 'postgres'.freeze
334
- MYSQL_PREFIX = 'mysql'.freeze
335
- SQLITE_PREFIX = 'sqlite'.freeze
215
+ MYSQL_PREFIX = 'mysql'.freeze
216
+ MYSQL2_PREFIX = 'mysql2'.freeze
217
+ SQLITE_PREFIX = 'sqlite'.freeze
336
218
 
337
219
  def symbolized_adapter(adapter)
338
220
  if adapter.start_with? POSTGRES_PREFIX
339
221
  :postgres
340
- elsif adapter.start_with? MYSQL_PREFIX
222
+ elsif adapter == MYSQL_PREFIX
341
223
  :mysql
224
+ # For the purpose of fetching explain plans, we need to maintain the distinction
225
+ # between usage of mysql and mysql2. Obfuscation is the same, though.
226
+ elsif adapter == MYSQL2_PREFIX
227
+ :mysql2
342
228
  elsif adapter.start_with? SQLITE_PREFIX
343
229
  :sqlite
344
230
  else
345
231
  adapter.to_sym
346
232
  end
347
233
  end
234
+
235
+ def explainable?
236
+ return false unless @explainer && is_select?(@sql)
237
+
238
+ if @sql[-3,3] == '...'
239
+ NewRelic::Agent.logger.debug('Unable to collect explain plan for truncated query.')
240
+ return false
241
+ end
242
+
243
+ if parameterized?(@sql) && @binds.empty?
244
+ NewRelic::Agent.logger.debug('Unable to collect explain plan for parameter-less parameterized query.')
245
+ return false
246
+ end
247
+
248
+ if !SUPPORTED_ADAPTERS_FOR_EXPLAIN.include?(adapter)
249
+ NewRelic::Agent.logger.debug("Not collecting explain plan because an unknown connection adapter ('#{adapter}') was used.")
250
+ return false
251
+ end
252
+
253
+ true
254
+ end
348
255
  end
349
256
  end
350
257
  end
@@ -0,0 +1,127 @@
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
+ require 'new_relic/agent/database/obfuscator'
6
+ require 'new_relic/agent/database/postgres_explain_obfuscator'
7
+
8
+ module NewRelic
9
+ module Agent
10
+ module Database
11
+ module ExplainPlanHelpers
12
+
13
+ SUPPORTED_ADAPTERS_FOR_EXPLAIN = [:postgres, :mysql2, :mysql, :sqlite]
14
+
15
+ def is_select?(sql)
16
+ NewRelic::Agent::Database.parse_operation_from_query(sql) == 'select'
17
+ end
18
+
19
+ def parameterized?(sql)
20
+ Obfuscator.instance.obfuscate_single_quote_literals(sql) =~ /\$\d+/
21
+ end
22
+
23
+ def handle_exception_in_explain
24
+ yield
25
+ rescue => e
26
+ begin
27
+ # guarantees no throw from explain_sql
28
+ ::NewRelic::Agent.logger.error("Error getting query plan:", e)
29
+ nil
30
+ rescue
31
+ # double exception. throw up your hands
32
+ nil
33
+ end
34
+ end
35
+
36
+ def process_resultset(results, adapter)
37
+ if adapter == :postgres
38
+ return process_explain_results_postgres(results)
39
+ elsif defined?(::ActiveRecord::Result) && results.is_a?(::ActiveRecord::Result)
40
+ # Note if adapter is mysql, will only have headers, not values
41
+ return [results.columns, results.rows]
42
+ elsif results.is_a?(String)
43
+ return string_explain_plan_results(results)
44
+ end
45
+
46
+ case adapter
47
+ when :mysql2
48
+ process_explain_results_mysql2(results)
49
+ when :mysql
50
+ process_explain_results_mysql(results)
51
+ when :sqlite
52
+ process_explain_results_sqlite(results)
53
+ end
54
+ end
55
+
56
+ QUERY_PLAN = 'QUERY PLAN'.freeze
57
+
58
+ def process_explain_results_postgres(results)
59
+ if defined?(::ActiveRecord::Result) && results.is_a?(::ActiveRecord::Result)
60
+ query_plan_string = results.rows.join("\n")
61
+ elsif results.is_a?(String)
62
+ query_plan_string = results
63
+ else
64
+ lines = []
65
+ results.each { |row| lines << row[QUERY_PLAN] }
66
+ query_plan_string = lines.join("\n")
67
+ end
68
+
69
+ unless NewRelic::Agent::Database.record_sql_method == :raw
70
+ query_plan_string = NewRelic::Agent::Database::PostgresExplainObfuscator.obfuscate(query_plan_string)
71
+ end
72
+ values = query_plan_string.split("\n").map { |line| [line] }
73
+
74
+ [[QUERY_PLAN], values]
75
+ end
76
+
77
+ # Sequel returns explain plans as just one big pre-formatted String
78
+ # In that case, we send a nil headers array, and the single string
79
+ # wrapped in an array for the values.
80
+ # Note that we don't use this method for Postgres explain plans, since
81
+ # they need to be passed through the explain plan obfuscator first.
82
+ def string_explain_plan_results(results)
83
+ [nil, [results]]
84
+ end
85
+
86
+ def process_explain_results_mysql(results)
87
+ headers = []
88
+ values = []
89
+ if results.is_a?(Array)
90
+ # We're probably using the jdbc-mysql gem for JRuby, which will give
91
+ # us an array of hashes.
92
+ headers = results.first.keys
93
+ results.each do |row|
94
+ values << headers.map { |h| row[h] }
95
+ end
96
+ else
97
+ # We're probably using the native mysql driver gem, which will give us
98
+ # a Mysql::Result object that responds to each_hash
99
+ results.each_hash do |row|
100
+ headers = row.keys
101
+ values << headers.map { |h| row[h] }
102
+ end
103
+ end
104
+ [headers, values]
105
+ end
106
+
107
+ def process_explain_results_mysql2(results)
108
+ headers = results.fields
109
+ values = []
110
+ results.each { |row| values << row }
111
+ [headers, values]
112
+ end
113
+
114
+ SQLITE_EXPLAIN_COLUMNS = %w[addr opcode p1 p2 p3 p4 p5 comment]
115
+
116
+ def process_explain_results_sqlite(results)
117
+ headers = SQLITE_EXPLAIN_COLUMNS
118
+ values = []
119
+ results.each do |row|
120
+ values << headers.map { |h| row[h] }
121
+ end
122
+ [headers, values]
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end