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.
- checksums.yaml +4 -4
- data/CHANGELOG +41 -0
- data/lib/new_relic/agent/agent.rb +1 -1
- data/lib/new_relic/agent/autostart.rb +1 -1
- data/lib/new_relic/agent/database.rb +72 -165
- data/lib/new_relic/agent/database/explain_plan_helpers.rb +127 -0
- data/lib/new_relic/agent/database/obfuscation_helpers.rb +2 -1
- data/lib/new_relic/agent/database/postgres_explain_obfuscator.rb +0 -2
- data/lib/new_relic/agent/instrumentation/active_record.rb +5 -5
- data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +10 -8
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +2 -1
- data/lib/new_relic/agent/instrumentation/grape.rb +23 -9
- data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +1 -6
- data/lib/new_relic/agent/instrumentation/padrino.rb +36 -0
- data/lib/new_relic/agent/method_tracer.rb +20 -18
- data/lib/new_relic/agent/new_relic_service.rb +3 -33
- data/lib/new_relic/agent/pipe_service.rb +0 -4
- data/lib/new_relic/agent/sql_sampler.rb +3 -3
- data/lib/new_relic/agent/transaction/trace_node.rb +2 -5
- data/lib/new_relic/agent/transaction_sampler.rb +2 -2
- data/lib/new_relic/metric_data.rb +6 -14
- data/lib/new_relic/version.rb +1 -1
- data/newrelic_rpm.gemspec +1 -1
- data/test/environments/lib/environments/runner.rb +1 -1
- data/test/environments/rails40/Gemfile +9 -0
- data/test/environments/rails41/Gemfile +9 -0
- data/test/environments/rails42/Gemfile +9 -0
- data/test/multiverse/lib/multiverse/suite.rb +12 -0
- data/test/multiverse/suites/agent_only/harvest_timestamps_test.rb +0 -31
- data/test/multiverse/suites/grape/Envfile +1 -1
- data/test/multiverse/suites/mongo/Envfile +3 -0
- data/test/multiverse/suites/mongo/mongo2_instrumentation_test.rb +3 -2
- data/test/multiverse/suites/padrino/Envfile +8 -0
- data/test/multiverse/suites/typhoeus/Envfile +21 -0
- data/test/new_relic/agent/database_test.rb +216 -144
- data/test/new_relic/agent/new_relic_service_test.rb +0 -58
- data/test/new_relic/metric_data_test.rb +24 -71
- data/test/new_relic/metric_spec_test.rb +3 -4
- data/test/new_relic/rack/developer_mode_test.rb +5 -2
- metadata +4 -2
data/lib/new_relic/version.rb
CHANGED
data/newrelic_rpm.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |s|
|
|
10
10
|
s.version = NewRelic::VERSION::STRING
|
11
11
|
s.required_ruby_version = '>= 1.8.7'
|
12
12
|
s.required_rubygems_version = Gem::Requirement.new("> 1.3.1") if s.respond_to? :required_rubygems_version=
|
13
|
-
s.authors = [ "Tim Krajcar", "Matthew Wear", "Katherine Wu", "Karl Sandwich" ]
|
13
|
+
s.authors = [ "Tim Krajcar", "Matthew Wear", "Katherine Wu", "Karl Sandwich", "Caito Scherr" ]
|
14
14
|
s.date = Time.now.strftime('%Y-%m-%d')
|
15
15
|
s.licenses = ['New Relic', 'MIT', 'Ruby']
|
16
16
|
s.description = <<-EOS
|
@@ -97,7 +97,7 @@ module Environments
|
|
97
97
|
bundling = `cd #{dir} && bundle install --local`
|
98
98
|
unless $?.success?
|
99
99
|
puts "Failed local bundle, trying again with full bundle..."
|
100
|
-
bundling = `cd #{dir} && bundle install`
|
100
|
+
bundling = `cd #{dir} && bundle install --retry 3`
|
101
101
|
end
|
102
102
|
|
103
103
|
bundling = red(bundling) unless $?.success?
|
@@ -4,6 +4,15 @@ gem 'rake', '< 11'
|
|
4
4
|
|
5
5
|
gem 'rails', '~>4.0.13'
|
6
6
|
|
7
|
+
# mime-types 2.99.1 and 3.x introduce mime-types-data
|
8
|
+
# mime-types-data requires Ruby version >= 2.0
|
9
|
+
# mime-types 2.99 requires Ruby version >= 1.9.3
|
10
|
+
if RUBY_VERSION < '1.9.3'
|
11
|
+
gem 'mime-types', '1.25.1'
|
12
|
+
elsif RUBY_VERSION < '2'
|
13
|
+
gem 'mime-types', '< 3'
|
14
|
+
end
|
15
|
+
|
7
16
|
# Do not automatically require minitest, since this will break with rbx-2.2.5.
|
8
17
|
# rbx-2.2.5 helpfully bundles minitest-5.3.0.
|
9
18
|
# Our tests don't work with minitest-5.3.0.
|
@@ -4,6 +4,15 @@ gem 'rake', '< 11'
|
|
4
4
|
|
5
5
|
gem 'rails', '~>4.1.10'
|
6
6
|
|
7
|
+
# mime-types 2.99.1 and 3.x introduce mime-types-data
|
8
|
+
# mime-types-data requires Ruby version >= 2.0
|
9
|
+
# mime-types 2.99 requires Ruby version >= 1.9.3
|
10
|
+
if RUBY_VERSION < '1.9.3'
|
11
|
+
gem 'mime-types', '1.25.1'
|
12
|
+
elsif RUBY_VERSION < '2'
|
13
|
+
gem 'mime-types', '< 3'
|
14
|
+
end
|
15
|
+
|
7
16
|
gem 'minitest', '5.2.3'
|
8
17
|
gem 'mocha', :require => false
|
9
18
|
gem 'rack'
|
@@ -4,6 +4,15 @@ gem 'rake', '< 11'
|
|
4
4
|
|
5
5
|
gem 'rails', '~>4.2.1'
|
6
6
|
|
7
|
+
# mime-types 2.99.1 and 3.x introduce mime-types-data
|
8
|
+
# mime-types-data requires Ruby version >= 2.0
|
9
|
+
# mime-types 2.99 requires Ruby version >= 1.9.3
|
10
|
+
if RUBY_VERSION < '1.9.3'
|
11
|
+
gem 'mime-types', '1.25.1'
|
12
|
+
elsif RUBY_VERSION < '2'
|
13
|
+
gem 'mime-types', '< 3'
|
14
|
+
end
|
15
|
+
|
7
16
|
gem 'minitest', '5.2.3'
|
8
17
|
gem 'mocha', :require => false
|
9
18
|
gem 'rack'
|
@@ -173,6 +173,7 @@ module Multiverse
|
|
173
173
|
f.print gemfile_text
|
174
174
|
f.puts newrelic_gemfile_line unless gemfile_text =~ /^\s*gem .newrelic_rpm./
|
175
175
|
f.puts jruby_openssl_line unless gemfile_text =~ /^\s*gem .jruby-openssl./ || (defined?(JRUBY_VERSION) && JRUBY_VERSION > '1.7')
|
176
|
+
f.puts mime_types_line unless gemfile_text =~ /^\s*gem .mime-types[^_-]./
|
176
177
|
f.puts minitest_line unless gemfile_text =~ /^\s*gem .minitest[^_]./
|
177
178
|
f.puts rake_line unless gemfile_text =~ /^\s*gem .rake[^_]./ || suite == 'rake'
|
178
179
|
if RUBY_VERSION == "1.8.7"
|
@@ -219,6 +220,17 @@ module Multiverse
|
|
219
220
|
"gem 'jruby-openssl', '~> 0.9.10', :require => false, :platforms => [:jruby]"
|
220
221
|
end
|
221
222
|
|
223
|
+
# mime-types got a new dependency after 2.99 that breaks compatibility with ruby < 2.0
|
224
|
+
def mime_types_line
|
225
|
+
if RUBY_VERSION <= '1.9.3'
|
226
|
+
"gem 'mime-types', '1.25.1'"
|
227
|
+
elsif RUBY_VERSION < '2'
|
228
|
+
"gem 'mime-types', '2.99'"
|
229
|
+
else
|
230
|
+
''
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
222
234
|
def minitest_line
|
223
235
|
"gem 'minitest', '~> 4.7.5', :require => false"
|
224
236
|
end
|
@@ -63,37 +63,6 @@ class HarvestTimestampsTest < Minitest::Test
|
|
63
63
|
end
|
64
64
|
end
|
65
65
|
|
66
|
-
def test_timestamps_updated_even_if_filling_metric_id_cache_fails
|
67
|
-
t0 = freeze_time.to_f
|
68
|
-
|
69
|
-
simulate_fork
|
70
|
-
NewRelic::Agent.after_fork
|
71
|
-
|
72
|
-
# Induce a failure in filling the metric ID cache by handing back a bogus
|
73
|
-
# response to the first metric_data post.
|
74
|
-
$collector.stub('metric_data', [[[[]]]], 200)
|
75
|
-
t1 = advance_time(10).to_f
|
76
|
-
trigger_metric_data_post
|
77
|
-
first_post = last_metric_data_post
|
78
|
-
|
79
|
-
$collector.reset
|
80
|
-
t2 = advance_time(10).to_f
|
81
|
-
trigger_metric_data_post
|
82
|
-
second_post = last_metric_data_post
|
83
|
-
|
84
|
-
if RUBY_VERSION == '1.8.7'
|
85
|
-
# 1.8 + JSON is finicky about comparing floats.
|
86
|
-
# If the timestamps are within 0.001 seconds, it's Good Enough.
|
87
|
-
assert_in_delta(t0, first_post[1], 0.001)
|
88
|
-
assert_in_delta(t1, first_post[2], 0.001)
|
89
|
-
assert_in_delta(t1, second_post[1], 0.001)
|
90
|
-
assert_in_delta(t2, second_post[2], 0.001)
|
91
|
-
else
|
92
|
-
assert_equal([t0, t1], first_post[1..2])
|
93
|
-
assert_equal([t1, t2], second_post[1..2])
|
94
|
-
end
|
95
|
-
end
|
96
|
-
|
97
66
|
def trigger_metric_data_post
|
98
67
|
NewRelic::Agent.agent.send(:transmit_data)
|
99
68
|
end
|
@@ -2,7 +2,7 @@ suite_condition("Grape is only supported for versions >= 1.9.3") do
|
|
2
2
|
RUBY_VERSION >= '1.9.3'
|
3
3
|
end
|
4
4
|
|
5
|
-
versions = %w(0.14.0 0.13.0 0.12.0 0.11.0 0.10.0 0.9.0 0.8.0 0.7.0 0.6.1 0.5.0 0.4.1 0.3.2 0.2.6 0.2.0 0.1.5)
|
5
|
+
versions = %w(0.16.1 0.15.0 0.14.0 0.13.0 0.12.0 0.11.0 0.10.0 0.9.0 0.8.0 0.7.0 0.6.1 0.5.0 0.4.1 0.3.2 0.2.6 0.2.0 0.1.5)
|
6
6
|
|
7
7
|
versions.each do |version|
|
8
8
|
gemfile <<-RB
|
@@ -270,12 +270,13 @@ if NewRelic::Agent::Datastores::Mongo.is_supported_version? &&
|
|
270
270
|
:collection => @collection_name,
|
271
271
|
'insert' => @collection_name,
|
272
272
|
:operation => :insert,
|
273
|
-
'writeConcern' => { :w => 1 },
|
274
273
|
'ordered' => true
|
275
274
|
}
|
276
275
|
|
277
276
|
result = node.params[:statement]
|
278
|
-
|
277
|
+
# the writeConcern is added by some, but not all versions of the mongo driver,
|
278
|
+
# we don't care if it's present or not, just that the statement is noticed
|
279
|
+
result.delete('writeConcern')
|
279
280
|
assert_equal expected, result
|
280
281
|
end
|
281
282
|
|
@@ -1,3 +1,11 @@
|
|
1
|
+
if RUBY_VERSION >= '1.9.3' # padrino v0.12.0 dropped support for ruby 1.8.7 and requres ruby 1.9.3 or greater
|
2
|
+
gemfile <<-RB
|
3
|
+
gem 'activesupport', '~> 3'
|
4
|
+
gem 'padrino', '~> 0.13.0'
|
5
|
+
gem 'rack-test', :require => 'rack/test'
|
6
|
+
RB
|
7
|
+
end
|
8
|
+
|
1
9
|
if RUBY_VERSION > '1.8.7' # padrino-core 0.11.0 requires http_router 0.11.0 which has syntax errors in 1.8.7.
|
2
10
|
gemfile <<-RB
|
3
11
|
gem 'activesupport', '~> 3'
|
@@ -31,6 +31,13 @@ gemfile <<-RB
|
|
31
31
|
# Compatibility issues with ethon 0.5.12 https://github.com/typhoeus/ethon/issues/51
|
32
32
|
gem 'ethon', '0.5.11'
|
33
33
|
|
34
|
+
# ethon 0.5.11 requires mime-types ~> 1.18
|
35
|
+
# normally, this wouldn't be a problem, but
|
36
|
+
# we have other checks to work around other
|
37
|
+
# mime-type dependency issues, so we need
|
38
|
+
# to specify it here.
|
39
|
+
gem 'mime-types', '~> 1.18'
|
40
|
+
|
34
41
|
gem 'typhoeus', '~> 0.5.4'
|
35
42
|
gem 'rack'
|
36
43
|
gem 'json', :platforms => [:rbx, :mri_18]
|
@@ -41,6 +48,13 @@ gemfile <<-RB
|
|
41
48
|
# Compatibility issues with ethon 0.5.12 https://github.com/typhoeus/ethon/issues/51
|
42
49
|
gem 'ethon', '0.5.11'
|
43
50
|
|
51
|
+
# ethon 0.5.11 requires mime-types ~> 1.18
|
52
|
+
# normally, this wouldn't be a problem, but
|
53
|
+
# we have other checks to work around other
|
54
|
+
# mime-type dependency issues, so we need
|
55
|
+
# to specify it here.
|
56
|
+
gem 'mime-types', '~> 1.18'
|
57
|
+
|
44
58
|
gem 'typhoeus', '0.5.3'
|
45
59
|
gem 'rack'
|
46
60
|
gem 'json', :platforms => [:rbx, :mri_18]
|
@@ -51,6 +65,13 @@ gemfile <<-RB
|
|
51
65
|
# Compatibility issues with ethon 0.5.12 https://github.com/typhoeus/ethon/issues/51
|
52
66
|
gem 'ethon', '0.5.11'
|
53
67
|
|
68
|
+
# ethon 0.5.11 requires mime-types ~> 1.18
|
69
|
+
# normally, this wouldn't be a problem, but
|
70
|
+
# we have other checks to work around other
|
71
|
+
# mime-type dependency issues, so we need
|
72
|
+
# to specify it here.
|
73
|
+
gem 'mime-types', '~> 1.18'
|
74
|
+
|
54
75
|
gem 'typhoeus', '0.5.2'
|
55
76
|
gem 'rack'
|
56
77
|
gem 'json', :platforms => [:rbx, :mri_18]
|
@@ -6,85 +6,113 @@ require File.expand_path(File.join(File.dirname(__FILE__), '..', '..',
|
|
6
6
|
'test_helper'))
|
7
7
|
require 'new_relic/agent/database'
|
8
8
|
class NewRelic::Agent::DatabaseTest < Minitest::Test
|
9
|
-
def setup
|
10
|
-
@explainer = NewRelic::Agent::Instrumentation::ActiveRecord::EXPLAINER
|
11
|
-
end
|
12
|
-
|
13
9
|
def teardown
|
14
10
|
NewRelic::Agent::Database::Obfuscator.instance.reset
|
15
11
|
end
|
16
12
|
|
17
13
|
def test_adapter_from_config_string
|
18
14
|
config = { :adapter => 'mysql' }
|
19
|
-
|
15
|
+
statement = NewRelic::Agent::Database::Statement.new('some query', config)
|
16
|
+
assert_equal(:mysql, statement.adapter)
|
20
17
|
end
|
21
18
|
|
22
19
|
def test_adapter_from_config_symbol
|
23
20
|
config = { :adapter => :mysql }
|
24
|
-
|
21
|
+
statement = NewRelic::Agent::Database::Statement.new('some query', config)
|
22
|
+
assert_equal(:mysql, statement.adapter)
|
25
23
|
end
|
26
24
|
|
27
25
|
def test_adapter_from_config_uri_jdbc_postgresql
|
28
26
|
config = { :uri=>"jdbc:postgresql://host/database?user=posgres" }
|
29
|
-
|
27
|
+
statement = NewRelic::Agent::Database::Statement.new('some query', config)
|
28
|
+
assert_equal(:postgres, statement.adapter)
|
30
29
|
end
|
31
30
|
|
32
31
|
def test_adapter_from_config_uri_jdbc_mysql
|
33
32
|
config = { :uri=>"jdbc:mysql://host/database" }
|
34
|
-
|
33
|
+
statement = NewRelic::Agent::Database::Statement.new('some query', config)
|
34
|
+
assert_equal(:mysql, statement.adapter)
|
35
35
|
end
|
36
36
|
|
37
37
|
def test_adapter_from_config_uri_jdbc_sqlite
|
38
38
|
config = { :uri => "jdbc:sqlite::memory" }
|
39
|
-
|
39
|
+
statement = NewRelic::Agent::Database::Statement.new('some query', config)
|
40
|
+
assert_equal(:sqlite, statement.adapter)
|
41
|
+
end
|
42
|
+
|
43
|
+
# An ActiveRecord::Result is what you get back when executing a
|
44
|
+
# query using exec_query on the connection, which is what we're
|
45
|
+
# doing now for explain plans in AR4 instrumentation
|
46
|
+
def test_explain_sql_with_mysql2_activerecord_result
|
47
|
+
return unless defined?(::ActiveRecord::Result)
|
48
|
+
config = {:adapter => 'mysql2'}
|
49
|
+
sql = 'SELECT * FROM spells where id=1'
|
50
|
+
|
51
|
+
columns = ["id", "select_type", "table", "type", "possible_keys", "key", "key_len", "ref", "rows", "Extra"]
|
52
|
+
rows = [["1", "SIMPLE", "spells", "const", "PRIMARY", "PRIMARY", "4", "const", "1", ""]]
|
53
|
+
activerecord_result = ::ActiveRecord::Result.new(columns, rows)
|
54
|
+
explainer = lambda { |statement| activerecord_result}
|
55
|
+
|
56
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
57
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
58
|
+
|
59
|
+
assert_equal([columns, rows], result)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_explain_sql_obfuscates_for_postgres_activerecord_result
|
63
|
+
return unless defined?(::ActiveRecord::Result)
|
64
|
+
config = {:adapter => 'postgres'}
|
65
|
+
sql = "SELECT * FROM blogs WHERE blogs.id=1234 AND blogs.title='sensitive text'"
|
66
|
+
|
67
|
+
columns = ["stuffs"]
|
68
|
+
rows = [[" Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540)"],
|
69
|
+
[" Index Cond: (id = 1234)"],
|
70
|
+
[" Filter: ((title)::text = 'sensitive text'::text)"]]
|
71
|
+
activerecord_result = ::ActiveRecord::Result.new(columns, rows)
|
72
|
+
explainer = lambda { |statement| activerecord_result}
|
73
|
+
|
74
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
75
|
+
expected_result = [['QUERY PLAN'],
|
76
|
+
[[" Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540)"],
|
77
|
+
[" Index Cond: ?"],
|
78
|
+
[" Filter: ?"]
|
79
|
+
]]
|
80
|
+
|
81
|
+
with_config(:'transaction_tracer.record_sql' => 'obfuscated') do
|
82
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
83
|
+
assert_equal(expected_result, result)
|
84
|
+
end
|
40
85
|
end
|
41
86
|
|
42
|
-
|
87
|
+
# The following tests in the format _with_##_explain_result go
|
88
|
+
# through the different kinds of results when using an explainer
|
89
|
+
# that calls .execute on the connection
|
90
|
+
|
91
|
+
def test_explain_sql_select_with_mysql_explain_result
|
43
92
|
config = {:adapter => 'mysql'}
|
44
|
-
config.default('val')
|
45
93
|
sql = 'SELECT foo'
|
46
|
-
|
94
|
+
|
47
95
|
plan = {
|
48
96
|
"select_type"=>"SIMPLE", "key_len"=>nil, "table"=>"blogs", "id"=>"1",
|
49
97
|
"possible_keys"=>nil, "type"=>"ALL", "Extra"=>"", "rows"=>"2",
|
50
98
|
"ref"=>nil, "key"=>nil
|
51
99
|
}
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
connection.expects(:execute).with('EXPLAIN SELECT foo').returns(result)
|
56
|
-
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
57
|
-
result = NewRelic::Agent::Database.explain_sql(sql, config, @explainer)
|
58
|
-
assert_equal(plan.keys.sort, result[0].sort)
|
59
|
-
assert_equal(plan.values.compact.sort, result[1][0].compact.sort)
|
60
|
-
end
|
100
|
+
explainer_result = mock('explain plan')
|
101
|
+
explainer_result.expects(:each_hash).yields(plan)
|
102
|
+
explainer = lambda { |statement| explainer_result}
|
61
103
|
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
# Sequel returns explain plans to us as one giant preformatted string rather
|
68
|
-
# than individual rows.
|
69
|
-
plan_string = [
|
70
|
-
"+--+-----------+-----+----+-------------+---+-------+---+----+-----+",
|
71
|
-
"|id|select_type|table|type|possible_keys|key|key_len|ref|rows|Extra|",
|
72
|
-
"+--+-----------+-----+----+-------------+---+-------+---+----+-----+",
|
73
|
-
"| 1|SIMPLE |items|ALL | | | | | 3| |",
|
74
|
-
"+--+-----------+-----+----+-------------+---+-------+---+----+-----+"
|
75
|
-
].join("\n")
|
104
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
105
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
106
|
+
expected_result = [["select_type", "key_len", "table", "id", "possible_keys", "type",
|
107
|
+
"Extra", "rows", "ref", "key"],
|
108
|
+
[["SIMPLE", nil, "blogs", "1", nil, "ALL", "", "2", nil, nil]]]
|
76
109
|
|
77
|
-
|
78
|
-
|
79
|
-
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
80
|
-
result = NewRelic::Agent::Database.explain_sql(sql, config, @explainer)
|
81
|
-
assert_nil(result[0])
|
82
|
-
assert_equal([plan_string], result[1])
|
110
|
+
assert_equal(expected_result[0].sort, result[0].sort, "Headers don't match")
|
111
|
+
assert_equal(expected_result[1][0].compact.sort, result[1][0].compact.sort, "Values don't match")
|
83
112
|
end
|
84
113
|
|
85
|
-
def
|
86
|
-
config = { :adapter => '
|
87
|
-
config.default('val')
|
114
|
+
def test_explain_sql_select_with_sequel
|
115
|
+
config = { :adapter => 'mysql2' }
|
88
116
|
sql = 'SELECT * FROM items'
|
89
117
|
|
90
118
|
# Sequel returns explain plans to us as one giant preformatted string rather
|
@@ -96,114 +124,124 @@ class NewRelic::Agent::DatabaseTest < Minitest::Test
|
|
96
124
|
"| 1|SIMPLE |items|ALL | | | | | 3| |",
|
97
125
|
"+--+-----------+-----+----+-------------+---+-------+---+----+-----+"
|
98
126
|
].join("\n")
|
127
|
+
explainer = lambda { |statement| plan_string}
|
128
|
+
|
129
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
130
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
99
131
|
|
100
|
-
connection = mock('mysql connection')
|
101
|
-
connection.expects(:execute).with('EXPLAIN SELECT * FROM items').returns(plan_string)
|
102
|
-
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
103
|
-
result = NewRelic::Agent::Database.explain_sql(sql, config, @explainer)
|
104
132
|
assert_nil(result[0])
|
105
133
|
assert_equal([plan_string], result[1])
|
106
134
|
end
|
107
135
|
|
108
|
-
def
|
136
|
+
def test_explain_sql_select_with_mysql2_explain_result
|
109
137
|
config = {:adapter => 'mysql2'}
|
110
|
-
config.default('val')
|
111
138
|
sql = 'SELECT foo'
|
112
|
-
connection = mock('mysql connection')
|
113
139
|
|
114
140
|
plan_fields = ["select_type", "key_len", "table", "id", "possible_keys", "type", "Extra", "rows", "ref", "key"]
|
115
141
|
plan_row = ["SIMPLE", nil, "blogs", "1", nil, "ALL", "", "2", nil, nil ]
|
142
|
+
explainer_result = mock('explain plan')
|
143
|
+
explainer_result.expects(:fields).returns(plan_fields)
|
144
|
+
explainer_result.expects(:each).yields(plan_row)
|
145
|
+
explainer = lambda { |statement| explainer_result}
|
116
146
|
|
117
|
-
|
118
|
-
result.
|
119
|
-
|
147
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
148
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
149
|
+
expected_result = [["select_type", "key_len", "table", "id", "possible_keys", "type",
|
150
|
+
"Extra", "rows", "ref","key"],
|
151
|
+
[["SIMPLE", nil, "blogs", "1", nil, "ALL", "", "2", nil, nil]]]
|
120
152
|
|
121
|
-
|
122
|
-
|
123
|
-
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
124
|
-
result = NewRelic::Agent::Database.explain_sql(sql, config, @explainer)
|
125
|
-
assert_equal(plan_fields.sort, result[0].sort)
|
126
|
-
assert_equal(plan_row.compact.sort, result[1][0].compact.sort)
|
153
|
+
assert_equal(expected_result[0].sort, result[0].sort, "Headers don't match")
|
154
|
+
assert_equal(expected_result[1][0].compact.sort, result[1][0].compact.sort, "Values don't match")
|
127
155
|
end
|
128
156
|
|
129
|
-
def
|
157
|
+
def test_explain_sql_one_select_with_pg_explain_result
|
130
158
|
config = {:adapter => 'postgresql'}
|
131
|
-
config.default('val')
|
132
159
|
sql = 'select count(id) from blogs limit 1'
|
133
|
-
|
160
|
+
|
134
161
|
plan = [{"QUERY PLAN"=>"Limit (cost=11.75..11.76 rows=1 width=4)"},
|
135
162
|
{"QUERY PLAN"=>" -> Aggregate (cost=11.75..11.76 rows=1 width=4)"},
|
136
163
|
{"QUERY PLAN"=>" -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"}]
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
164
|
+
explainer = lambda { |statement| plan}
|
165
|
+
|
166
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
167
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
168
|
+
expected_result = [['QUERY PLAN'],
|
169
|
+
[["Limit (cost=11.75..11.76 rows=1 width=4)"],
|
170
|
+
[" -> Aggregate (cost=11.75..11.76 rows=1 width=4)"],
|
171
|
+
[" -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"]
|
172
|
+
]]
|
173
|
+
|
174
|
+
assert_equal expected_result, result
|
144
175
|
end
|
145
176
|
|
146
|
-
def
|
177
|
+
def test_explain_sql_one_select_with_pg_explain_string_result
|
147
178
|
config = {:adapter => 'postgresql'}
|
148
|
-
config.default('val')
|
149
179
|
sql = 'select count(id) from blogs limit 1'
|
150
|
-
|
180
|
+
|
151
181
|
plan = "Limit (cost=11.75..11.76 rows=1 width=4)
|
152
182
|
-> Aggregate (cost=11.75..11.76 rows=1 width=4)
|
153
183
|
-> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"
|
184
|
+
explainer = lambda { |statement| plan}
|
185
|
+
|
186
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
187
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
188
|
+
expected_result = [['QUERY PLAN'],
|
189
|
+
[["Limit (cost=11.75..11.76 rows=1 width=4)"],
|
190
|
+
[" -> Aggregate (cost=11.75..11.76 rows=1 width=4)"],
|
191
|
+
[" -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"]
|
192
|
+
]]
|
154
193
|
|
155
|
-
|
156
|
-
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
157
|
-
assert_equal([['QUERY PLAN'],
|
158
|
-
[["Limit (cost=11.75..11.76 rows=1 width=4)"],
|
159
|
-
[" -> Aggregate (cost=11.75..11.76 rows=1 width=4)"],
|
160
|
-
[" -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"]]],
|
161
|
-
NewRelic::Agent::Database.explain_sql(sql, config, @explainer))
|
194
|
+
assert_equal expected_result, result
|
162
195
|
end
|
163
196
|
|
164
197
|
def test_explain_sql_obfuscates_for_postgres
|
165
198
|
config = {:adapter => 'postgresql'}
|
166
|
-
config.default('val')
|
167
199
|
sql = "SELECT * FROM blogs WHERE blogs.id=1234 AND blogs.title='sensitive text'"
|
168
|
-
|
200
|
+
|
169
201
|
plan = [{"QUERY PLAN"=>" Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540)"},
|
170
202
|
{"QUERY PLAN"=>" Index Cond: (id = 1234)"},
|
171
203
|
{"QUERY PLAN"=>" Filter: ((title)::text = 'sensitive text'::text)"}]
|
172
|
-
|
173
|
-
|
204
|
+
explainer = lambda { |statement| plan}
|
205
|
+
|
206
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
207
|
+
expected_result = [['QUERY PLAN'],
|
208
|
+
[[" Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540)"],
|
209
|
+
[" Index Cond: ?"],
|
210
|
+
[" Filter: ?"]
|
211
|
+
]]
|
212
|
+
|
174
213
|
with_config(:'transaction_tracer.record_sql' => 'obfuscated') do
|
175
|
-
|
176
|
-
|
177
|
-
[" Index Cond: ?"],
|
178
|
-
[" Filter: ?"]]],
|
179
|
-
NewRelic::Agent::Database.explain_sql(sql, config, @explainer))
|
214
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
215
|
+
assert_equal expected_result, result
|
180
216
|
end
|
181
217
|
end
|
182
218
|
|
183
219
|
def test_explain_sql_does_not_obfuscate_if_record_sql_raw
|
184
220
|
config = {:adapter => 'postgresql'}
|
185
|
-
config.default('val')
|
186
221
|
sql = "SELECT * FROM blogs WHERE blogs.id=1234 AND blogs.title='sensitive text'"
|
187
|
-
|
222
|
+
|
188
223
|
plan = [{"QUERY PLAN"=>" Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540)"},
|
189
224
|
{"QUERY PLAN"=>" Index Cond: (id = 1234)"},
|
190
225
|
{"QUERY PLAN"=>" Filter: ((title)::text = 'sensitive text'::text)"}]
|
191
|
-
|
192
|
-
|
226
|
+
explainer = lambda { |statement| plan}
|
227
|
+
|
228
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
229
|
+
expected_result = [['QUERY PLAN'],
|
230
|
+
[[" Index Scan using blogs_pkey on blogs (cost=0.00..8.27 rows=1 width=540)"],
|
231
|
+
[" Index Cond: (id = 1234)"],
|
232
|
+
[" Filter: ((title)::text = 'sensitive text'::text)"]
|
233
|
+
]]
|
234
|
+
|
193
235
|
with_config(:'transaction_tracer.record_sql' => 'raw') do
|
194
|
-
|
195
|
-
|
196
|
-
[" Index Cond: (id = 1234)"],
|
197
|
-
[" Filter: ((title)::text = 'sensitive text'::text)"]]],
|
198
|
-
NewRelic::Agent::Database.explain_sql(sql, config, @explainer))
|
236
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
237
|
+
assert_equal expected_result, result
|
199
238
|
end
|
200
239
|
end
|
201
240
|
|
202
|
-
def
|
241
|
+
def test_explain_sql_select_with_sqlite_explain_string_result
|
203
242
|
config = {:adapter => 'sqlite'}
|
204
|
-
config.default('val')
|
205
243
|
sql = 'SELECT foo'
|
206
|
-
|
244
|
+
|
207
245
|
plan = [
|
208
246
|
{"addr"=>0, "opcode"=>"Trace", "p1"=>0, "p2"=>0, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>0, 1=>"Trace", 2=>0, 3=>0, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
209
247
|
{"addr"=>1, "opcode"=>"Goto", "p1"=>0, "p2"=>5, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>1, 1=>"Goto", 2=>0, 3=>5, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
@@ -212,9 +250,10 @@ class NewRelic::Agent::DatabaseTest < Minitest::Test
|
|
212
250
|
{"addr"=>4, "opcode"=>"Halt", "p1"=>0, "p2"=>0, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>4, 1=>"Halt", 2=>0, 3=>0, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
213
251
|
{"addr"=>5, "opcode"=>"Goto", "p1"=>0, "p2"=>2, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>5, 1=>"Goto", 2=>0, 3=>2, 4=>0, 5=>"", 6=>"00", 7=>nil}
|
214
252
|
]
|
215
|
-
|
216
|
-
|
217
|
-
|
253
|
+
explainer = lambda { |statement| plan}
|
254
|
+
|
255
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
256
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
218
257
|
|
219
258
|
expected_headers = %w[addr opcode p1 p2 p3 p4 p5 comment]
|
220
259
|
expected_values = plan.map do |row|
|
@@ -225,68 +264,96 @@ class NewRelic::Agent::DatabaseTest < Minitest::Test
|
|
225
264
|
assert_equal(expected_values, result[1])
|
226
265
|
end
|
227
266
|
|
228
|
-
def
|
267
|
+
def test_explain_sql_select_with_sqlite3_explain_string_result
|
268
|
+
config = {:adapter => 'sqlite3'}
|
269
|
+
sql = 'SELECT foo'
|
270
|
+
|
271
|
+
plan = [
|
272
|
+
{"addr"=>0, "opcode"=>"Trace", "p1"=>0, "p2"=>0, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>0, 1=>"Trace", 2=>0, 3=>0, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
273
|
+
{"addr"=>1, "opcode"=>"Goto", "p1"=>0, "p2"=>5, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>1, 1=>"Goto", 2=>0, 3=>5, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
274
|
+
{"addr"=>2, "opcode"=>"String8", "p1"=>0, "p2"=>1, "p3"=>0, "p4"=>"foo", "p5"=>"00", "comment"=>nil, 0=>2, 1=>"String8", 2=>0, 3=>1, 4=>0, 5=>"foo", 6=>"00", 7=>nil},
|
275
|
+
{"addr"=>3, "opcode"=>"ResultRow", "p1"=>1, "p2"=>1, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>3, 1=>"ResultRow", 2=>1, 3=>1, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
276
|
+
{"addr"=>4, "opcode"=>"Halt", "p1"=>0, "p2"=>0, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>4, 1=>"Halt", 2=>0, 3=>0, 4=>0, 5=>"", 6=>"00", 7=>nil},
|
277
|
+
{"addr"=>5, "opcode"=>"Goto", "p1"=>0, "p2"=>2, "p3"=>0, "p4"=>"", "p5"=>"00", "comment"=>nil, 0=>5, 1=>"Goto", 2=>0, 3=>2, 4=>0, 5=>"", 6=>"00", 7=>nil}
|
278
|
+
]
|
279
|
+
explainer = lambda { |statement| plan}
|
280
|
+
|
281
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
282
|
+
result = NewRelic::Agent::Database.explain_sql(statement)
|
283
|
+
|
284
|
+
expected_headers = %w[addr opcode p1 p2 p3 p4 p5 comment]
|
285
|
+
expected_values = plan.map do |row|
|
286
|
+
expected_headers.map { |h| row[h] }
|
287
|
+
end
|
288
|
+
|
289
|
+
assert_equal(expected_headers.sort, result[0].sort)
|
290
|
+
assert_equal(expected_values, result[1])
|
291
|
+
end
|
292
|
+
|
293
|
+
def test_explain_sql_no_sql
|
294
|
+
statement = NewRelic::Agent::Database::Statement.new('', nil)
|
295
|
+
assert_equal(nil, NewRelic::Agent::Database.explain_sql(statement))
|
296
|
+
end
|
297
|
+
|
298
|
+
def test_explain_sql_non_select
|
299
|
+
statement = NewRelic::Agent::Database::Statement.new('foo', mock('config'), mock('explainer'))
|
300
|
+
assert_equal([], NewRelic::Agent::Database.explain_sql(statement))
|
301
|
+
end
|
302
|
+
|
303
|
+
def test_dont_collect_explain_for_truncated_query
|
229
304
|
config = {:adapter => 'postgresql'}
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
305
|
+
sql = 'SELECT * FROM table WHERE id IN (1,2,3,4,5...'
|
306
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, mock('explainer'))
|
307
|
+
|
308
|
+
expects_logging(:debug, 'Unable to collect explain plan for truncated query.')
|
309
|
+
assert_equal [], NewRelic::Agent::Database.explain_sql(statement)
|
310
|
+
end
|
235
311
|
|
312
|
+
def test_dont_collect_explain_for_parameterized_query
|
313
|
+
config = {:adapter => 'postgresql'}
|
236
314
|
sql = 'SELECT * FROM table WHERE id = $1'
|
237
|
-
|
315
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, mock('explainer'))
|
316
|
+
|
317
|
+
expects_logging(:debug, 'Unable to collect explain plan for parameter-less parameterized query.')
|
318
|
+
assert_equal [], NewRelic::Agent::Database.explain_sql(statement)
|
238
319
|
end
|
239
320
|
|
240
321
|
def test_do_collect_explain_for_parameter_looking_literal
|
241
322
|
config = {:adapter => 'postgresql'}
|
242
|
-
config.default('val')
|
243
|
-
connection = mock('literal connection')
|
244
|
-
plan = [{"QUERY PLAN"=>"Some Jazz"}]
|
245
323
|
sql = "SELECT * FROM table WHERE id = 'noise $11'"
|
246
|
-
|
247
|
-
|
248
|
-
NewRelic::Agent::Database.
|
324
|
+
plan = [{"QUERY PLAN"=>"Some Jazz"}]
|
325
|
+
explainer = lambda { |statement| plan}
|
326
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer)
|
249
327
|
|
250
328
|
assert_equal([['QUERY PLAN'], [["Some Jazz"]]],
|
251
|
-
NewRelic::Agent::Database.explain_sql(
|
329
|
+
NewRelic::Agent::Database.explain_sql(statement))
|
252
330
|
end
|
253
331
|
|
254
|
-
def
|
332
|
+
def test_do_collect_explain_for_parameterized_query_with_binds
|
255
333
|
config = {:adapter => 'postgresql'}
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
334
|
+
sql = 'SELECT * FROM table WHERE id = $1'
|
335
|
+
# binds objects don't actually look like this, just need non-blank for test
|
336
|
+
binds = "values for the parameters"
|
337
|
+
plan = [{"QUERY PLAN"=>"Some Jazz"}]
|
338
|
+
explainer = lambda { |statement| plan}
|
339
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, explainer, binds)
|
261
340
|
|
262
|
-
|
263
|
-
|
341
|
+
assert_equal([['QUERY PLAN'], [["Some Jazz"]]],
|
342
|
+
NewRelic::Agent::Database.explain_sql(statement))
|
264
343
|
end
|
265
344
|
|
266
345
|
def test_dont_collect_explain_if_adapter_not_recognized
|
267
346
|
config = {:adapter => 'dorkdb'}
|
268
|
-
config.default('val')
|
269
|
-
connection = mock('connection')
|
270
|
-
connection.expects(:execute).never
|
271
|
-
NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
|
272
|
-
expects_logging(:debug, "Not collecting explain plan because an unknown connection adapter ('dorkdb') was used.")
|
273
|
-
|
274
347
|
sql = 'SELECT * FROM table WHERE id IN (1,2,3,4,5)'
|
275
|
-
|
276
|
-
end
|
348
|
+
statement = NewRelic::Agent::Database::Statement.new(sql, config, mock('explainer'))
|
277
349
|
|
278
|
-
|
279
|
-
assert_equal
|
350
|
+
expects_logging(:debug, "Not collecting explain plan because an unknown connection adapter ('dorkdb') was used.")
|
351
|
+
assert_equal [], NewRelic::Agent::Database.explain_sql(statement)
|
280
352
|
end
|
281
353
|
|
282
354
|
def test_explain_sql_no_connection_config
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
def test_explain_sql_non_select
|
287
|
-
assert_equal([], NewRelic::Agent::Database.explain_sql('foo',
|
288
|
-
mock('config'),
|
289
|
-
@explainer))
|
355
|
+
statement = NewRelic::Agent::Database::Statement.new('select foo', nil)
|
356
|
+
assert_equal(nil, NewRelic::Agent::Database.explain_sql(statement))
|
290
357
|
end
|
291
358
|
|
292
359
|
def test_explain_sql_one_select_no_connection
|
@@ -296,7 +363,12 @@ class NewRelic::Agent::DatabaseTest < Minitest::Test
|
|
296
363
|
# errors to percolate up.
|
297
364
|
config = mock('config')
|
298
365
|
config.stubs(:[]).returns(nil)
|
299
|
-
|
366
|
+
|
367
|
+
# if you have an invalid config or no connection, the explainer returns nil
|
368
|
+
explainer = lambda { |statement| nil}
|
369
|
+
statement = NewRelic::Agent::Database::Statement.new('SELECT', config, explainer)
|
370
|
+
|
371
|
+
assert_equal([], NewRelic::Agent::Database.explain_sql(statement))
|
300
372
|
end
|
301
373
|
|
302
374
|
# See SqlObfuscationTest, which uses cross agent tests for the basic SQL
|