newrelic_rpm 3.6.1.88 → 3.6.2.90.beta
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +20 -0
- data/Gemfile +1 -0
- data/lib/new_relic/agent/autostart.rb +4 -3
- data/lib/new_relic/agent/database.rb +14 -17
- data/lib/new_relic/agent/instrumentation/active_record.rb +15 -2
- data/lib/new_relic/agent/instrumentation/active_record_helper.rb +1 -1
- data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +14 -2
- data/lib/new_relic/agent/instrumentation/net.rb +11 -5
- data/lib/new_relic/agent/instrumentation/resque.rb +5 -3
- data/lib/new_relic/agent/instrumentation/sequel.rb +40 -0
- data/lib/new_relic/agent/sql_sampler.rb +14 -4
- data/lib/new_relic/agent/transaction_sampler.rb +13 -10
- data/lib/new_relic/build.rb +2 -2
- data/lib/new_relic/transaction_sample/segment.rb +12 -5
- data/lib/new_relic/version.rb +1 -1
- data/lib/sequel/extensions/newrelic_instrumentation.rb +103 -0
- data/lib/sequel/plugins/newrelic_instrumentation.rb +83 -0
- data/test/multiverse/suites/agent_only/thread_profiling_test.rb +1 -1
- data/test/multiverse/suites/resque/instrumentation_test.rb +2 -0
- data/test/new_relic/agent/database_test.rb +15 -12
- data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +13 -3
- data/test/new_relic/agent/instrumentation/sequel_test.rb +285 -0
- data/test/new_relic/agent/sql_sampler_test.rb +27 -4
- data/test/new_relic/agent/transaction_sampler_test.rb +14 -14
- data/test/new_relic/transaction_sample/segment_test.rb +24 -2
- data/test/new_relic/transaction_sample_test.rb +1 -1
- data/test/test_helper.rb +19 -5
- data.tar.gz.sig +0 -0
- metadata +17 -26
- metadata.gz.sig +0 -0
@@ -0,0 +1,103 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# encoding: utf-8
|
3
|
+
# This file is distributed under New Relic's license terms.
|
4
|
+
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
|
5
|
+
|
6
|
+
require 'sequel' unless defined?( Sequel )
|
7
|
+
require 'newrelic_rpm' unless defined?( NewRelic )
|
8
|
+
require 'new_relic/agent/instrumentation/active_record_helper'
|
9
|
+
|
10
|
+
module Sequel
|
11
|
+
|
12
|
+
# New Relic's Sequel instrumentation is implemented via a plugin for
|
13
|
+
# Sequel::Models, and an extension for Sequel::Databases. Every database
|
14
|
+
# handle that Sequel knows about when New Relic is loaded will automatically
|
15
|
+
# be instrumented, but if you're using a version of Sequel before 3.47.0,
|
16
|
+
# you'll need to add the extension yourself if you create any after the
|
17
|
+
# instrumentation is loaded:
|
18
|
+
#
|
19
|
+
# db = Sequel.connect( ... )
|
20
|
+
# db.extension :newrelic_instrumentation
|
21
|
+
#
|
22
|
+
# Versions 3.47.0 and later use `Database.extension` to automatically
|
23
|
+
# install the extension for new connections.
|
24
|
+
#
|
25
|
+
# == Disabling
|
26
|
+
#
|
27
|
+
# If you don't want your models or database connections to be instrumented,
|
28
|
+
# you can disable them by setting `disable_database_instrumentation` in
|
29
|
+
# your `newrelic.yml` to `true`. It will also honor the
|
30
|
+
# `disable_activerecord_instrumentation` setting.
|
31
|
+
#
|
32
|
+
module NewRelicInstrumentation
|
33
|
+
include NewRelic::Agent::MethodTracer,
|
34
|
+
NewRelic::Agent::Instrumentation::ActiveRecordHelper
|
35
|
+
|
36
|
+
|
37
|
+
# Instrument all queries that go through #execute_query.
|
38
|
+
def log_yield( sql, args=nil )
|
39
|
+
return super unless NewRelic::Agent.is_execution_traced?
|
40
|
+
|
41
|
+
t0 = Time.now
|
42
|
+
rval = super
|
43
|
+
t1 = Time.now
|
44
|
+
|
45
|
+
begin
|
46
|
+
duration = t1 - t0
|
47
|
+
record_metrics( sql, args, duration )
|
48
|
+
notice_sql( sql, args, t0, t1 )
|
49
|
+
rescue => err
|
50
|
+
NewRelic::Agent.logger.debug "while recording metrics for Sequel", err
|
51
|
+
end
|
52
|
+
|
53
|
+
return rval
|
54
|
+
end
|
55
|
+
|
56
|
+
# Record metrics for the specified +sql+ and +args+ using the specified
|
57
|
+
# +duration+.
|
58
|
+
def record_metrics( sql, args, duration)
|
59
|
+
primary_metric = primary_metric_for( sql, args )
|
60
|
+
engine = NewRelic::Agent.instance.stats_engine
|
61
|
+
|
62
|
+
engine.record_metrics( primary_metric, duration, :scoped => true )
|
63
|
+
|
64
|
+
metrics = rollup_metrics_for( primary_metric )
|
65
|
+
metrics << remote_service_metric( *self.opts.values_at(:adapter, :host) ) if self.opts.key?(:adapter)
|
66
|
+
|
67
|
+
engine.record_metrics( metrics, duration, :scoped => false )
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
# Record the given +sql+ within a new scope, using the given +start+ and
|
72
|
+
# +finish+ times.
|
73
|
+
def notice_sql( sql, args, start, finish )
|
74
|
+
metric = primary_metric_for( sql, args )
|
75
|
+
agent = NewRelic::Agent.instance
|
76
|
+
duration = finish - start
|
77
|
+
|
78
|
+
begin
|
79
|
+
scope = agent.stats_engine.push_scope( :sequel, start )
|
80
|
+
agent.transaction_sampler.notice_sql( sql, self.opts, duration ) do |*|
|
81
|
+
self[ sql ].explain
|
82
|
+
end
|
83
|
+
agent.sql_sampler.notice_sql( sql, metric, self.opts, duration ) do |*|
|
84
|
+
self[ sql ].explain
|
85
|
+
end
|
86
|
+
ensure
|
87
|
+
agent.stats_engine.pop_scope( scope, metric, finish )
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# Derive a primary database metric for the specified +sql+.
|
93
|
+
def primary_metric_for( sql, _ )
|
94
|
+
return metric_for_sql(NewRelic::Helper.correctly_encoded(sql))
|
95
|
+
end
|
96
|
+
|
97
|
+
end # module NewRelicInstrumentation
|
98
|
+
|
99
|
+
NewRelic::Agent.logger.debug "Registering the :newrelic_instrumentation extension."
|
100
|
+
Database.register_extension( :newrelic_instrumentation, NewRelicInstrumentation )
|
101
|
+
|
102
|
+
end # module Sequel
|
103
|
+
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# -*- ruby -*-
|
2
|
+
# encoding: utf-8
|
3
|
+
# This file is distributed under New Relic's license terms.
|
4
|
+
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
|
5
|
+
|
6
|
+
require 'sequel' unless defined?( Sequel )
|
7
|
+
require 'newrelic_rpm' unless defined?( NewRelic )
|
8
|
+
|
9
|
+
module Sequel
|
10
|
+
module Plugins
|
11
|
+
|
12
|
+
# Sequel::Model instrumentation for the New Relic agent.
|
13
|
+
module NewrelicInstrumentation
|
14
|
+
|
15
|
+
# Meta-programming for creating method tracers for the Sequel::Model plugin.
|
16
|
+
module MethodTracer
|
17
|
+
|
18
|
+
# Make a lambda for the method body of the traced method
|
19
|
+
def make_tracer_method( opname, options )
|
20
|
+
body = Proc.new do |*args, &block|
|
21
|
+
classname = self.is_a?( Class ) ? self.name : self.class.name
|
22
|
+
metric = "ActiveRecord/%s/%s" % [ classname, opname ]
|
23
|
+
trace_execution_scoped( metric, options ) do
|
24
|
+
super( *args, &block )
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
return body
|
29
|
+
end
|
30
|
+
|
31
|
+
# Install a method named +method_name+ that will trace execution
|
32
|
+
# with a metric name derived from +metric+ (or +method_name+ if +metric+
|
33
|
+
# isn't specified). The +options+ hash is passed as-is though to
|
34
|
+
# NewRelic::Agent::MethodTracer#trace_execution_scoped; see the
|
35
|
+
# docs for that method for valid settings.
|
36
|
+
def add_method_tracer( method_name, metric=nil, options={} )
|
37
|
+
# Shift options hash if metric is omitted
|
38
|
+
if metric.is_a?( Hash )
|
39
|
+
options = metric
|
40
|
+
metric = nil
|
41
|
+
end
|
42
|
+
|
43
|
+
metric ||= method_name.to_s
|
44
|
+
|
45
|
+
body = make_tracer_method( metric, options )
|
46
|
+
define_method( method_name, &body )
|
47
|
+
end
|
48
|
+
|
49
|
+
end # module MethodTracer
|
50
|
+
|
51
|
+
|
52
|
+
# Methods to be added to Sequel::Model instances.
|
53
|
+
module InstanceMethods
|
54
|
+
include NewRelic::Agent::MethodTracer
|
55
|
+
extend Sequel::Plugins::NewrelicInstrumentation::MethodTracer
|
56
|
+
|
57
|
+
add_method_tracer :delete
|
58
|
+
add_method_tracer :destroy
|
59
|
+
add_method_tracer :update
|
60
|
+
add_method_tracer :update_all
|
61
|
+
add_method_tracer :update_except
|
62
|
+
add_method_tracer :update_fields
|
63
|
+
add_method_tracer :update_only
|
64
|
+
add_method_tracer :save
|
65
|
+
|
66
|
+
end # module InstanceMethods
|
67
|
+
|
68
|
+
|
69
|
+
# Methods to be added to Sequel::Model classes.
|
70
|
+
module ClassMethods
|
71
|
+
include NewRelic::Agent::MethodTracer
|
72
|
+
extend Sequel::Plugins::NewrelicInstrumentation::MethodTracer
|
73
|
+
|
74
|
+
add_method_tracer :[], :get
|
75
|
+
add_method_tracer :all
|
76
|
+
add_method_tracer :first
|
77
|
+
add_method_tracer :create
|
78
|
+
end # module ClassMethods
|
79
|
+
|
80
|
+
end # module NewRelicInstrumentation
|
81
|
+
end # module Plugins
|
82
|
+
end # module Sequel
|
83
|
+
|
@@ -135,6 +135,7 @@ class ResqueTest < Test::Unit::TestCase
|
|
135
135
|
|
136
136
|
def test_agent_posts_correct_metric_data
|
137
137
|
run_worker
|
138
|
+
assert_metric_and_call_count('Instance/Busy', 1)
|
138
139
|
assert_metric_and_call_count('OtherTransaction/ResqueJob/all', JOB_COUNT)
|
139
140
|
end
|
140
141
|
|
@@ -146,6 +147,7 @@ class ResqueTest < Test::Unit::TestCase
|
|
146
147
|
|
147
148
|
def test_agent_posts_correct_metric_data_background
|
148
149
|
run_worker(:background => true)
|
150
|
+
assert_metric_and_call_count('Instance/Busy', 1)
|
149
151
|
assert_metric_and_call_count('OtherTransaction/ResqueJob/all', JOB_COUNT)
|
150
152
|
end
|
151
153
|
end
|
@@ -6,6 +6,10 @@ 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 < Test::Unit::TestCase
|
9
|
+
def setup
|
10
|
+
@explainer = NewRelic::Agent::Instrumentation::ActiveRecord::EXPLAINER
|
11
|
+
end
|
12
|
+
|
9
13
|
def teardown
|
10
14
|
NewRelic::Agent::Database::Obfuscator.instance.reset
|
11
15
|
end
|
@@ -20,7 +24,7 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
|
|
20
24
|
config = {:adapter => 'mysql'}
|
21
25
|
config.default('val')
|
22
26
|
sql = 'SELECT foo'
|
23
|
-
connection = mock('connection')
|
27
|
+
connection = mock('mysql connection')
|
24
28
|
plan = {
|
25
29
|
"select_type"=>"SIMPLE", "key_len"=>nil, "table"=>"blogs", "id"=>"1",
|
26
30
|
"possible_keys"=>nil, "type"=>"ALL", "Extra"=>"", "rows"=>"2",
|
@@ -30,9 +34,8 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
|
|
30
34
|
result.expects(:each_hash).yields(plan)
|
31
35
|
# two rows, two columns
|
32
36
|
connection.expects(:execute).with('EXPLAIN SELECT foo').returns(result)
|
33
|
-
NewRelic::Agent::Database.
|
34
|
-
|
35
|
-
result = NewRelic::Agent::Database.explain_sql(sql, config)
|
37
|
+
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
38
|
+
result = NewRelic::Agent::Database.explain_sql(sql, config, &@explainer)
|
36
39
|
assert_equal(plan.keys.sort, result[0].sort)
|
37
40
|
assert_equal(plan.values.compact.sort, result[1][0].compact.sort)
|
38
41
|
end
|
@@ -41,23 +44,23 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
|
|
41
44
|
config = {:adapter => 'postgresql'}
|
42
45
|
config.default('val')
|
43
46
|
sql = 'select count(id) from blogs limit 1'
|
44
|
-
connection =
|
47
|
+
connection = stub('pg connection', :disconnect! => true)
|
45
48
|
plan = [{"QUERY PLAN"=>"Limit (cost=11.75..11.76 rows=1 width=4)"},
|
46
49
|
{"QUERY PLAN"=>" -> Aggregate (cost=11.75..11.76 rows=1 width=4)"},
|
47
50
|
{"QUERY PLAN"=>" -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"}]
|
48
51
|
connection.expects(:execute).returns(plan)
|
49
|
-
NewRelic::Agent::Database.
|
52
|
+
NewRelic::Agent::Database.stubs(:get_connection).returns(connection)
|
50
53
|
assert_equal([['QUERY PLAN'],
|
51
54
|
[["Limit (cost=11.75..11.76 rows=1 width=4)"],
|
52
55
|
[" -> Aggregate (cost=11.75..11.76 rows=1 width=4)"],
|
53
56
|
[" -> Seq Scan on blogs (cost=0.00..11.40 rows=140 width=4)"]]],
|
54
|
-
NewRelic::Agent::Database.explain_sql(sql, config))
|
57
|
+
NewRelic::Agent::Database.explain_sql(sql, config, &@explainer))
|
55
58
|
end
|
56
59
|
|
57
60
|
def test_dont_collect_explain_for_parameterized_query
|
58
61
|
config = {:adapter => 'postgresql'}
|
59
62
|
config.default('val')
|
60
|
-
connection = mock('connection')
|
63
|
+
connection = mock('param connection')
|
61
64
|
connection.expects(:execute).never
|
62
65
|
NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
|
63
66
|
expects_logging(:debug, 'Unable to collect explain plan for parameterized query.')
|
@@ -69,20 +72,20 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
|
|
69
72
|
def test_do_collect_explain_for_parameter_looking_literal
|
70
73
|
config = {:adapter => 'postgresql'}
|
71
74
|
config.default('val')
|
72
|
-
connection = mock('connection')
|
75
|
+
connection = mock('literal connection')
|
73
76
|
plan = [{"QUERY PLAN"=>"Some Jazz"}]
|
74
77
|
connection.stubs(:execute).returns(plan)
|
75
78
|
NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
|
76
79
|
|
77
80
|
sql = "SELECT * FROM table WHERE id = 'noise $11'"
|
78
81
|
assert_equal([['QUERY PLAN'], [["Some Jazz"]]],
|
79
|
-
NewRelic::Agent::Database.explain_sql(sql, config))
|
82
|
+
NewRelic::Agent::Database.explain_sql(sql, config, &@explainer))
|
80
83
|
end
|
81
84
|
|
82
85
|
def test_dont_collect_explain_for_truncated_query
|
83
86
|
config = {:adapter => 'postgresql'}
|
84
87
|
config.default('val')
|
85
|
-
connection = mock('connection')
|
88
|
+
connection = mock('truncated connection')
|
86
89
|
connection.expects(:execute).never
|
87
90
|
NewRelic::Agent::Database.stubs(:get_connection).with(config).returns(connection)
|
88
91
|
expects_logging(:debug, 'Unable to collect explain plan for truncated query.')
|
@@ -112,7 +115,7 @@ class NewRelic::Agent::DatabaseTest < Test::Unit::TestCase
|
|
112
115
|
# errors to percolate up.
|
113
116
|
config = mock('config')
|
114
117
|
config.stubs(:[]).returns(nil)
|
115
|
-
assert_equal([], NewRelic::Agent::Database.explain_sql('SELECT', config))
|
118
|
+
assert_equal([], NewRelic::Agent::Database.explain_sql('SELECT', config, &@explainer))
|
116
119
|
end
|
117
120
|
|
118
121
|
def test_obfuscation_mysql_basic
|
@@ -62,9 +62,6 @@ class NewRelic::Agent::Instrumentation::NetInstrumentationTest < Test::Unit::Tes
|
|
62
62
|
@response = CANNED_RESPONSE.clone
|
63
63
|
@socket = fixture_tcp_socket( @response )
|
64
64
|
|
65
|
-
# $stderr.puts '', '---', ''
|
66
|
-
# NewRelic::Agent.logger = NewRelic::Agent::AgentLogger.new( {:log_level => 'debug'}, '', Logger.new($stderr) )
|
67
|
-
|
68
65
|
@engine = NewRelic::Agent.instance.stats_engine
|
69
66
|
@engine.clear_stats
|
70
67
|
|
@@ -175,6 +172,18 @@ class NewRelic::Agent::Instrumentation::NetInstrumentationTest < Test::Unit::Tes
|
|
175
172
|
])
|
176
173
|
end
|
177
174
|
|
175
|
+
|
176
|
+
# https://newrelic.atlassian.net/browse/RUBY-835
|
177
|
+
def test_direct_get_request_doesnt_double_count
|
178
|
+
uri = URI.parse("http://www.google.com/index.html")
|
179
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
180
|
+
http.request(Net::HTTP::Get.new(uri.request_uri))
|
181
|
+
|
182
|
+
assert_metrics_recorded([
|
183
|
+
'External/www.google.com/Net::HTTP/GET'
|
184
|
+
])
|
185
|
+
end
|
186
|
+
|
178
187
|
def test_ignore
|
179
188
|
in_transaction do
|
180
189
|
NewRelic::Agent.disable_all_tracing do
|
@@ -429,4 +438,5 @@ class NewRelic::Agent::Instrumentation::NetInstrumentationTest < Test::Unit::Tes
|
|
429
438
|
assert_equal filtered_uri, last_segment.params[:uri]
|
430
439
|
end
|
431
440
|
end
|
441
|
+
|
432
442
|
end
|
@@ -0,0 +1,285 @@
|
|
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
|
+
if RUBY_VERSION >= "1.8.7"
|
6
|
+
|
7
|
+
require 'sequel'
|
8
|
+
|
9
|
+
require File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','test_helper'))
|
10
|
+
|
11
|
+
class NewRelic::Agent::Instrumentation::SequelInstrumentationTest < Test::Unit::TestCase
|
12
|
+
require 'active_record_fixtures'
|
13
|
+
include NewRelic::Agent::Instrumentation::ControllerInstrumentation,
|
14
|
+
TransactionSampleTestHelper
|
15
|
+
|
16
|
+
def self.jruby?
|
17
|
+
NewRelic::LanguageSupport.using_engine?('jruby')
|
18
|
+
end
|
19
|
+
|
20
|
+
def jruby?
|
21
|
+
self.class.jruby?
|
22
|
+
end
|
23
|
+
|
24
|
+
# Use an in-memory SQLite database
|
25
|
+
if (jruby?)
|
26
|
+
DB = Sequel.connect('jdbc:sqlite::memory:')
|
27
|
+
else
|
28
|
+
DB = Sequel.sqlite
|
29
|
+
end
|
30
|
+
DB.extension :newrelic_instrumentation
|
31
|
+
|
32
|
+
# Create tables and model classes for testing
|
33
|
+
DB.create_table( :authors ) do
|
34
|
+
primary_key :id
|
35
|
+
string :name
|
36
|
+
string :login
|
37
|
+
end
|
38
|
+
class Author < Sequel::Model; end
|
39
|
+
|
40
|
+
DB.create_table( :posts ) do
|
41
|
+
primary_key :id
|
42
|
+
string :title
|
43
|
+
string :content
|
44
|
+
time :created_at
|
45
|
+
end
|
46
|
+
class Post < Sequel::Model; end
|
47
|
+
|
48
|
+
|
49
|
+
|
50
|
+
#
|
51
|
+
# Setup/teardown
|
52
|
+
#
|
53
|
+
|
54
|
+
def setup
|
55
|
+
super
|
56
|
+
|
57
|
+
NewRelic::Agent.manual_start
|
58
|
+
|
59
|
+
@agent = NewRelic::Agent.instance
|
60
|
+
@agent.transaction_sampler.reset!
|
61
|
+
|
62
|
+
@engine = @agent.stats_engine
|
63
|
+
@engine.clear_stats
|
64
|
+
@engine.start_transaction
|
65
|
+
|
66
|
+
@sampler = NewRelic::Agent.instance.transaction_sampler
|
67
|
+
end
|
68
|
+
|
69
|
+
def teardown
|
70
|
+
super
|
71
|
+
@engine.end_transaction
|
72
|
+
NewRelic::Agent::TransactionInfo.reset
|
73
|
+
Thread::current[:newrelic_scope_name] = nil
|
74
|
+
NewRelic::Agent.shutdown
|
75
|
+
end
|
76
|
+
|
77
|
+
|
78
|
+
#
|
79
|
+
# Tests
|
80
|
+
#
|
81
|
+
|
82
|
+
def test_sequel_database_instrumentation_is_loaded
|
83
|
+
assert DB.respond_to?( :primary_metric_for )
|
84
|
+
end
|
85
|
+
|
86
|
+
def test_sequel_model_instrumentation_is_loaded
|
87
|
+
assert Post.respond_to?( :trace_execution_scoped )
|
88
|
+
end
|
89
|
+
|
90
|
+
def test_model_enumerator_generates_metrics
|
91
|
+
Post.all
|
92
|
+
|
93
|
+
assert_remote_service_metrics
|
94
|
+
assert_includes @engine.metrics, "Database/SQL/select"
|
95
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
96
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/all"
|
97
|
+
end
|
98
|
+
|
99
|
+
def test_model_index_operator_generates_metrics
|
100
|
+
Post[11]
|
101
|
+
|
102
|
+
assert_remote_service_metrics
|
103
|
+
assert_includes @engine.metrics, "Database/SQL/select"
|
104
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
105
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/get"
|
106
|
+
end
|
107
|
+
|
108
|
+
def test_model_create_method_generates_metrics
|
109
|
+
post = Post.create( :title => 'The Thing', :content => 'A wicked short story.' )
|
110
|
+
|
111
|
+
assert_remote_service_metrics
|
112
|
+
assert_includes @engine.metrics, "Database/SQL/insert"
|
113
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
114
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/create"
|
115
|
+
end
|
116
|
+
|
117
|
+
def test_model_update_method_generates_metrics
|
118
|
+
post = Post.create( :title => 'All The Things', :content => 'A story about beans.' )
|
119
|
+
post.update( :title => 'A Lot of the Things' )
|
120
|
+
|
121
|
+
assert_remote_service_metrics
|
122
|
+
assert_includes @engine.metrics, "Database/SQL/update"
|
123
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
124
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/update"
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_model_update_all_method_generates_metrics
|
128
|
+
post = Post.create( :title => 'All The Things', :content => 'A nicer story than yours.' )
|
129
|
+
post.update_all( :title => 'A Whole Hell of a Lot of the Things' )
|
130
|
+
|
131
|
+
assert_remote_service_metrics
|
132
|
+
assert_includes @engine.metrics, "Database/SQL/update"
|
133
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
134
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/update_all"
|
135
|
+
end
|
136
|
+
|
137
|
+
def test_model_update_except_method_generates_metrics
|
138
|
+
post = Post.create( :title => 'All The Things', :content => 'A story.' )
|
139
|
+
post.update_except( {:title => 'A Bit More of the Things'}, :created_at )
|
140
|
+
|
141
|
+
assert_remote_service_metrics
|
142
|
+
assert_includes @engine.metrics, "Database/SQL/update"
|
143
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
144
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/update_except"
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_model_update_fields_method_generates_metrics
|
148
|
+
post = Post.create( :title => 'All The Things', :content => 'A venal short story.' )
|
149
|
+
post.update_fields( {:title => 'A Plethora of Things'}, [:title] )
|
150
|
+
|
151
|
+
assert_remote_service_metrics
|
152
|
+
assert_includes @engine.metrics, "Database/SQL/update"
|
153
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
154
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/update_fields"
|
155
|
+
end
|
156
|
+
|
157
|
+
def test_model_update_only_method_generates_metrics
|
158
|
+
post = Post.create( :title => 'All The Things', :content => 'A meandering short story.' )
|
159
|
+
post.update_only( {:title => 'A Lot of the Things'}, :title )
|
160
|
+
|
161
|
+
assert_remote_service_metrics
|
162
|
+
assert_includes @engine.metrics, "Database/SQL/update"
|
163
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
164
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/update_only"
|
165
|
+
end
|
166
|
+
|
167
|
+
def test_model_save_method_generates_metrics
|
168
|
+
post = Post.new( :title => 'An Endless Lot Full of Things',
|
169
|
+
:content => 'A lingering long story.' )
|
170
|
+
post.save
|
171
|
+
|
172
|
+
assert_remote_service_metrics
|
173
|
+
assert_includes @engine.metrics, "Database/SQL/insert"
|
174
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
175
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/save"
|
176
|
+
end
|
177
|
+
|
178
|
+
def test_model_delete_method_generates_metrics
|
179
|
+
post = Post.create( :title => 'All The Things', :content => 'A nice short story.' )
|
180
|
+
post.delete
|
181
|
+
|
182
|
+
assert_remote_service_metrics
|
183
|
+
assert_includes @engine.metrics, "Database/SQL/delete"
|
184
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
185
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/delete"
|
186
|
+
end
|
187
|
+
|
188
|
+
def test_model_destroy_method_generates_metrics
|
189
|
+
post = Post.create( :title => 'Most of the Things', :content => 'Another short story.' )
|
190
|
+
post.destroy
|
191
|
+
|
192
|
+
assert_remote_service_metrics
|
193
|
+
assert_includes @engine.metrics, "Database/SQL/delete"
|
194
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
195
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Post.name}/destroy"
|
196
|
+
end
|
197
|
+
|
198
|
+
def test_model_destroy_uses_the_class_name_for_the_metric
|
199
|
+
author = Author.create( :name => 'Marlon Forswytthe', :login => 'mfors' )
|
200
|
+
author.destroy
|
201
|
+
|
202
|
+
assert_remote_service_metrics
|
203
|
+
assert_includes @engine.metrics, "Database/SQL/delete"
|
204
|
+
assert_includes @engine.metrics, "ActiveRecord/all"
|
205
|
+
assert_includes @engine.metrics, "ActiveRecord/#{Author.name}/destroy"
|
206
|
+
end
|
207
|
+
|
208
|
+
def test_slow_queries_get_an_explain_plan
|
209
|
+
with_config( :'transaction_tracer.explain_threshold' => 0.0 ) do
|
210
|
+
segment = last_segment_for(:record_sql => :raw) do
|
211
|
+
Post[11]
|
212
|
+
end
|
213
|
+
assert_match %r{select \* from `posts` where `id` = 11}i, segment.params[:sql]
|
214
|
+
assert_segment_has_explain_plan( segment )
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def test_queries_can_get_explain_plan_with_obfuscated_sql
|
219
|
+
with_config( :'transaction_tracer.explain_threshold' => 0.0 ) do
|
220
|
+
segment = last_segment_for(:record_sql => :obfuscated) do
|
221
|
+
Post[11]
|
222
|
+
end
|
223
|
+
assert_match %r{select \* from `posts` where `id` = \?}i, segment.params[:sql]
|
224
|
+
assert_segment_has_explain_plan( segment )
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
|
229
|
+
#
|
230
|
+
# Helpers
|
231
|
+
#
|
232
|
+
|
233
|
+
# Pattern to match the column headers of a Sqlite explain plan
|
234
|
+
SQLITE_EXPLAIN_PLAN_COLUMNS_RE =
|
235
|
+
%r{\|addr\s*\|opcode\s*\|p1\s*\|p2\s*\|p3\s*\|p4\s*\|p5\s*\|comment\s*\|}
|
236
|
+
|
237
|
+
# This is particular to sqlite plans currently. To abstract it up, we'd need to
|
238
|
+
# be able to specify a flavor (e.g., :sqlite, :postgres, :mysql, etc.)
|
239
|
+
def assert_segment_has_explain_plan( segment, msg=nil )
|
240
|
+
msg = build_message( msg, "Expected ? to have an explain plan", segment )
|
241
|
+
assert_block( msg ) { segment.params[:explain_plan].join =~ SQLITE_EXPLAIN_PLAN_COLUMNS_RE }
|
242
|
+
end
|
243
|
+
|
244
|
+
def assert_remote_service_metrics
|
245
|
+
if (jruby?)
|
246
|
+
assert @engine.metrics.none? {|s| s.start_with?("RemoteService/")}, "Sqlite on JRuby doesn't report adapter right for this metric. Why's it here?"
|
247
|
+
else
|
248
|
+
assert_includes @engine.metrics, "RemoteService/sql/sqlite/localhost"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
def with_controller_scope
|
253
|
+
@sampler.notice_first_scope_push Time.now.to_f
|
254
|
+
@sampler.notice_transaction('/', {})
|
255
|
+
@sampler.notice_push_scope "Controller/sandwiches/index"
|
256
|
+
|
257
|
+
yield if block_given?
|
258
|
+
|
259
|
+
@sampler.notice_pop_scope "Controller/sandwiches/index"
|
260
|
+
@sampler.notice_scope_empty(stub('txn', :name => '/', :custom_parameters => {}))
|
261
|
+
@sampler.samples
|
262
|
+
end
|
263
|
+
|
264
|
+
def last_segment_for(options={})
|
265
|
+
transaction_samples = with_controller_scope do
|
266
|
+
yield
|
267
|
+
end
|
268
|
+
|
269
|
+
sample = transaction_samples.first.prepare_to_send(
|
270
|
+
:explain_sql=>options[:explain_sql] || -0.01, # Force to take explain, even if duration's reported as 0.0
|
271
|
+
:record_sql=>options[:record_sql])
|
272
|
+
segment = last_segment( sample )
|
273
|
+
end
|
274
|
+
|
275
|
+
def last_segment(txn_sample)
|
276
|
+
l_segment = nil
|
277
|
+
txn_sample.root_segment.each_segment do |segment|
|
278
|
+
l_segment = segment
|
279
|
+
end
|
280
|
+
l_segment
|
281
|
+
end
|
282
|
+
|
283
|
+
end
|
284
|
+
|
285
|
+
end
|