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.
Files changed (30) hide show
  1. data/CHANGELOG +20 -0
  2. data/Gemfile +1 -0
  3. data/lib/new_relic/agent/autostart.rb +4 -3
  4. data/lib/new_relic/agent/database.rb +14 -17
  5. data/lib/new_relic/agent/instrumentation/active_record.rb +15 -2
  6. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +1 -1
  7. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +14 -2
  8. data/lib/new_relic/agent/instrumentation/net.rb +11 -5
  9. data/lib/new_relic/agent/instrumentation/resque.rb +5 -3
  10. data/lib/new_relic/agent/instrumentation/sequel.rb +40 -0
  11. data/lib/new_relic/agent/sql_sampler.rb +14 -4
  12. data/lib/new_relic/agent/transaction_sampler.rb +13 -10
  13. data/lib/new_relic/build.rb +2 -2
  14. data/lib/new_relic/transaction_sample/segment.rb +12 -5
  15. data/lib/new_relic/version.rb +1 -1
  16. data/lib/sequel/extensions/newrelic_instrumentation.rb +103 -0
  17. data/lib/sequel/plugins/newrelic_instrumentation.rb +83 -0
  18. data/test/multiverse/suites/agent_only/thread_profiling_test.rb +1 -1
  19. data/test/multiverse/suites/resque/instrumentation_test.rb +2 -0
  20. data/test/new_relic/agent/database_test.rb +15 -12
  21. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +13 -3
  22. data/test/new_relic/agent/instrumentation/sequel_test.rb +285 -0
  23. data/test/new_relic/agent/sql_sampler_test.rb +27 -4
  24. data/test/new_relic/agent/transaction_sampler_test.rb +14 -14
  25. data/test/new_relic/transaction_sample/segment_test.rb +24 -2
  26. data/test/new_relic/transaction_sample_test.rb +1 -1
  27. data/test/test_helper.rb +19 -5
  28. data.tar.gz.sig +0 -0
  29. metadata +17 -26
  30. 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
+
@@ -83,7 +83,7 @@ class ThreadProfilingTest < Test::Unit::TestCase
83
83
 
84
84
 
85
85
  def let_it_finish
86
- Timeout.timeout(2) do
86
+ Timeout.timeout(5) do
87
87
  until @thread_profiler.finished?
88
88
  sleep(0.1)
89
89
  end
@@ -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.expects(:get_connection).with(config).returns(connection)
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 = mock('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.expects(:get_connection).with(config).returns(connection)
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