newrelic_rpm 3.6.4.122 → 3.6.5.130

Sign up to get free protection for your applications and to get access to all the features.
Files changed (82) hide show
  1. data.tar.gz.sig +0 -0
  2. data/CHANGELOG +14 -1
  3. data/lib/new_relic/agent.rb +8 -7
  4. data/lib/new_relic/agent/agent.rb +2 -0
  5. data/lib/new_relic/agent/configuration/defaults.rb +1 -1
  6. data/lib/new_relic/agent/configuration/server_source.rb +2 -1
  7. data/lib/new_relic/agent/cross_app_tracing.rb +52 -26
  8. data/lib/new_relic/agent/event_listener.rb +1 -1
  9. data/lib/new_relic/agent/http_clients/excon_wrappers.rb +61 -0
  10. data/lib/new_relic/agent/http_clients/net_http_wrappers.rb +46 -0
  11. data/lib/new_relic/agent/http_clients/typhoeus_wrappers.rb +72 -0
  12. data/lib/new_relic/agent/{uri_util.rb → http_clients/uri_util.rb} +11 -17
  13. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +4 -11
  14. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +1 -1
  15. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +2 -2
  16. data/lib/new_relic/agent/instrumentation/excon.rb +72 -0
  17. data/lib/new_relic/agent/instrumentation/excon/connection.rb +33 -0
  18. data/lib/new_relic/agent/instrumentation/excon/middleware.rb +45 -0
  19. data/lib/new_relic/agent/instrumentation/net.rb +3 -1
  20. data/lib/new_relic/agent/instrumentation/typhoeus.rb +73 -0
  21. data/lib/new_relic/agent/method_tracer.rb +3 -6
  22. data/lib/new_relic/agent/new_relic_service.rb +1 -1
  23. data/lib/new_relic/agent/samplers/object_sampler.rb +1 -1
  24. data/lib/new_relic/agent/stats.rb +12 -10
  25. data/lib/new_relic/agent/stats_engine/metric_stats.rb +24 -6
  26. data/lib/new_relic/agent/stats_engine/stats_hash.rb +9 -11
  27. data/lib/new_relic/agent/transaction.rb +34 -33
  28. data/lib/new_relic/agent/transaction_sampler.rb +15 -6
  29. data/lib/new_relic/language_support.rb +8 -0
  30. data/lib/new_relic/local_environment.rb +10 -14
  31. data/lib/new_relic/version.rb +1 -1
  32. data/test/agent_helper.rb +43 -0
  33. data/test/config/test.cert.crt +14 -0
  34. data/test/config/test.cert.csr +11 -0
  35. data/test/config/test.cert.key +15 -0
  36. data/test/config/testing-privkey.pem +18 -0
  37. data/test/multiverse/lib/multiverse/color.rb +9 -3
  38. data/test/multiverse/lib/multiverse/suite.rb +11 -1
  39. data/test/multiverse/suites/agent_only/audit_log_test.rb +1 -0
  40. data/test/multiverse/suites/agent_only/config/newrelic.yml +4 -0
  41. data/test/multiverse/suites/agent_only/http_response_code_test.rb +1 -1
  42. data/test/multiverse/suites/agent_only/rename_rule_test.rb +2 -1
  43. data/test/multiverse/suites/agent_only/start_up_test.rb +3 -2
  44. data/test/multiverse/suites/config_file_loading/Envfile +2 -0
  45. data/test/multiverse/suites/excon/Envfile +15 -0
  46. data/test/multiverse/suites/excon/config/newrelic.yml +21 -0
  47. data/test/multiverse/suites/excon/excon_test.rb +60 -0
  48. data/test/multiverse/suites/net_http/Envfile +6 -0
  49. data/test/multiverse/suites/net_http/config/newrelic.yml +21 -0
  50. data/test/multiverse/suites/net_http/net_http_test.rb +102 -0
  51. data/test/multiverse/suites/rails/Envfile +1 -1
  52. data/test/multiverse/suites/rails/view_instrumentation_test.rb +6 -0
  53. data/test/multiverse/suites/resque/Envfile +0 -9
  54. data/test/multiverse/suites/resque/instrumentation_test.rb +21 -6
  55. data/test/multiverse/suites/typhoeus/Envfile +46 -0
  56. data/test/multiverse/suites/typhoeus/config/newrelic.yml +21 -0
  57. data/test/multiverse/suites/typhoeus/typhoeus_test.rb +77 -0
  58. data/test/new_relic/agent/agent_test_controller_test.rb +11 -10
  59. data/test/new_relic/agent/configuration/server_source_test.rb +23 -9
  60. data/test/new_relic/agent/database_test.rb +6 -0
  61. data/test/new_relic/agent/http_clients/uri_util_test.rb +64 -0
  62. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +0 -406
  63. data/test/new_relic/agent/new_relic_service_test.rb +23 -19
  64. data/test/new_relic/agent/stats_engine/metric_stats_test.rb +29 -12
  65. data/test/new_relic/agent/stats_hash_test.rb +24 -3
  66. data/test/new_relic/agent/stats_test.rb +4 -4
  67. data/test/new_relic/agent/transaction/pop_test.rb +1 -1
  68. data/test/new_relic/agent/transaction_sampler_test.rb +18 -0
  69. data/test/new_relic/agent/transaction_test.rb +64 -69
  70. data/test/new_relic/agent_test.rb +20 -0
  71. data/test/new_relic/dependency_detection_test.rb +99 -0
  72. data/test/new_relic/evil_server.rb +56 -0
  73. data/test/new_relic/fake_collector.rb +12 -96
  74. data/test/new_relic/fake_external_server.rb +55 -0
  75. data/test/new_relic/fake_server.rb +97 -0
  76. data/test/new_relic/http_client_test_cases.rb +444 -0
  77. data/test/new_relic/language_support_test.rb +45 -0
  78. data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +25 -4
  79. metadata +37 -35
  80. metadata.gz.sig +0 -0
  81. data/test/multiverse/suites/resque/resque_setup.rb +0 -19
  82. data/test/new_relic/agent/uri_util_test.rb +0 -75
@@ -0,0 +1,56 @@
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
+ # This is a simple TCP server that binds to an ephemeral port, accepts
6
+ # incoming connections on that port, and then closes those connections
7
+ # immediately thereafter.
8
+ #
9
+ # Its purpose is to emulate a misbehaving HTTP server (or flaky network
10
+ # connection) by closing the TCP connection without sending an HTTP response.
11
+
12
+ require 'socket'
13
+
14
+ module NewRelic
15
+ class EvilServer
16
+ attr_reader :port, :requests
17
+
18
+ def initialize
19
+ @requests = []
20
+ end
21
+
22
+ def should_run?
23
+ @state == :running
24
+ end
25
+
26
+ def stop
27
+ @state = :stopped
28
+
29
+ # just a lazy way of just forcing the server to wakeup from accept
30
+ TCPSocket.open("localhost", @port).close rescue nil
31
+
32
+ @thread.join
33
+ @thread = nil
34
+ end
35
+
36
+ def start
37
+ return if @thread && @thread.alive?
38
+ @requests = []
39
+ @server = TCPServer.new(0)
40
+ @port = @server.addr[1]
41
+ @state = :running
42
+ @thread = Thread.new { run }
43
+ @thread.abort_on_exception = true
44
+ end
45
+
46
+ def run
47
+ loop do
48
+ client = @server.accept
49
+ @requests << client.readpartial(1024) if should_run?
50
+ client.close
51
+ break unless should_run?
52
+ end
53
+ end
54
+ end
55
+ end
56
+
@@ -8,14 +8,16 @@ require 'uri'
8
8
  require 'socket'
9
9
  require 'timeout'
10
10
  require 'ostruct'
11
+ require 'fake_server'
11
12
 
12
13
  require 'json' if RUBY_VERSION >= '1.9'
13
14
 
14
15
  module NewRelic
15
- class FakeCollector
16
+ class FakeCollector < FakeServer
16
17
  attr_accessor :agent_data, :mock
17
18
 
18
19
  def initialize
20
+ super
19
21
  @id_counter = 0
20
22
  @base_expectations = {
21
23
  'get_redirect_host' => [200, {'return_value' => 'localhost'}],
@@ -35,10 +37,17 @@ module NewRelic
35
37
  @id_counter += 1
36
38
  end
37
39
 
40
+ def reset
41
+ @mock = @base_expectations.dup
42
+ @id_counter = 0
43
+ @agent_data = []
44
+ end
45
+
38
46
  def call(env)
39
47
  req = ::Rack::Request.new(env)
40
48
  res = ::Rack::Response.new
41
49
  uri = URI.parse(req.url)
50
+
42
51
  if uri.path =~ /agent_listener\/\d+\/.+\/(\w+)/
43
52
  method = $1
44
53
  format = json_format?(uri) && RUBY_VERSION >= '1.9' ? :json : :pruby
@@ -81,85 +90,8 @@ module NewRelic
81
90
  uri.query && uri.query.include?('marshal_format=json')
82
91
  end
83
92
 
84
- # We generate a "unique" port for ourselves based off our pid
85
- # If this logic changes, look for multiverse newrelic.yml files to update
86
- # with it duplicated (since we can't easily pull this ruby into a yml)
87
- def self.determine_port
88
- 30_000 + ($$ % 10_000)
89
- end
90
-
91
- def determine_port
92
- FakeCollector.determine_port
93
- end
94
-
95
- @seen_port_failure = false
96
-
97
- def run(port=nil)
98
- port ||= determine_port
99
- return if @thread && @thread.alive?
100
- serve_on_port(port) do
101
- @thread = Thread.new do
102
- begin
103
- ::Rack::Handler::WEBrick.run(self,
104
- :Port => port,
105
- :Logger => ::WEBrick::Log.new("/dev/null"),
106
- :AccessLog => [ ['/dev/null', ::WEBrick::AccessLog::COMMON_LOG_FORMAT] ]
107
- )
108
- rescue Errno::EADDRINUSE => ex
109
- msg = "Port #{port} for FakeCollector was in use"
110
- if !@seen_port_failure
111
- # This is slow, so only do it the first collision we detect
112
- lsof = `lsof | grep #{port}`
113
- msg = msg + "\n#{lsof}"
114
- @seen_port_failure = true
115
- end
116
-
117
- raise Errno::EADDRINUSE.new(msg)
118
- end
119
- end
120
- @thread.abort_on_exception = true
121
- end
122
- end
123
-
124
- def serve_on_port(port)
125
- port ||= determine_port
126
- if is_port_available?('127.0.0.1', port)
127
- yield
128
- loop do
129
- break if !is_port_available?('127.0.0.1', port)
130
- sleep 0.01
131
- end
132
- end
133
- end
134
-
135
- def stop
136
- return unless @thread.alive?
137
- ::Rack::Handler::WEBrick.shutdown
138
- @thread.join
139
- reset
140
- end
141
-
142
- def reset
143
- @mock = @base_expectations.dup
144
- @id_counter = 0
145
- @agent_data = []
146
- end
147
-
148
- def is_port_available?(ip, port)
149
- begin
150
- Timeout::timeout(1) do
151
- begin
152
- s = TCPSocket.new(ip, port)
153
- s.close
154
- return false
155
- rescue Errno::ECONNREFUSED, Errno::EHOSTUNREACH
156
- return true
157
- end
158
- end
159
- rescue Timeout::Error
160
- end
161
-
162
- return true
93
+ def app
94
+ self
163
95
  end
164
96
 
165
97
  def calls_for(method)
@@ -258,22 +190,6 @@ module NewRelic
258
190
  end
259
191
  end
260
192
 
261
- # might we need this? I'll just leave it here for now
262
- class FakeCollectorProcess < FakeCollector
263
- def run(port)
264
- serve_on_port(port) do
265
- @pid = Process.fork do
266
- ::Rack::Handler::WEBrick.run(self, :Port => port)
267
- end
268
- end
269
- end
270
-
271
- def stop
272
- return unless @pid
273
- Process.kill('QUIT', @pid)
274
- Process.wait(@pid)
275
- end
276
- end
277
193
  end
278
194
 
279
195
  if $0 == __FILE__
@@ -0,0 +1,55 @@
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 'rack'
6
+ require 'fake_server'
7
+
8
+ require 'json' if RUBY_VERSION >= '1.9'
9
+
10
+ module NewRelic
11
+ class FakeExternalServer < FakeServer
12
+
13
+ STATUS_MESSAGE = "<html><head><title>FakeExternalServer status</title></head>" +
14
+ "<body>The FakeExternalServer is rockin'</body></html>"
15
+
16
+ def initialize( * )
17
+ super
18
+ @requests = []
19
+ end
20
+
21
+ attr_reader :requests
22
+
23
+ def call(env)
24
+ @requests << env.dup
25
+
26
+ req = ::Rack::Request.new(env)
27
+ res = ::Rack::Response.new
28
+ res.status = req.params["status"].to_i if req.params["status"]
29
+
30
+ in_transaction('test') do
31
+ res.write STATUS_MESSAGE
32
+ end
33
+ res.finish
34
+ end
35
+
36
+ def reset
37
+ @requests.clear
38
+ end
39
+
40
+ def app
41
+ NewRelic::Rack::AgentHooks.new(self)
42
+ end
43
+
44
+ def fallback_port
45
+ # Only use fallback port on the FakeCollector....
46
+ nil
47
+ end
48
+ end
49
+
50
+ class FakeSecureExternalServer < FakeExternalServer
51
+ def initialize
52
+ super(0, true)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,97 @@
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 'webrick'
6
+ require 'webrick/https'
7
+ require 'rack'
8
+ require 'rack/handler'
9
+ require 'timeout'
10
+
11
+ require 'json' if RUBY_VERSION >= '1.9'
12
+
13
+ module NewRelic
14
+ class FakeServer
15
+
16
+ # Use ephemeral ports by default
17
+ DEFAULT_PORT = 0
18
+
19
+ # Default server options
20
+ DEFAULT_OPTIONS = {
21
+ :Logger => ::WEBrick::Log.new('/dev/null'),
22
+ :AccessLog => [ ['/dev/null', ''] ]
23
+ }
24
+
25
+ CONFIG_PATH = File.join(File.dirname(__FILE__), "..", "config")
26
+
27
+ SSL_OPTIONS = {
28
+ :SSLEnable => true,
29
+ :SSLVerifyClient => OpenSSL::SSL::VERIFY_NONE,
30
+ :SSLPrivateKey => OpenSSL::PKey::RSA.new(File.open(File.join(CONFIG_PATH, "test.cert.key")).read),
31
+ :SSLCertificate => OpenSSL::X509::Certificate.new(File.open(File.join(CONFIG_PATH, "test.cert.crt")).read),
32
+ :SSLCertName => [["CN", "newrelic.com"]]
33
+ }
34
+
35
+ def initialize( port=DEFAULT_PORT, ssl=false )
36
+ @thread = nil
37
+
38
+ defaults = $DEBUG ? {} : DEFAULT_OPTIONS
39
+ @options = defaults.merge( :Port => port )
40
+
41
+ @options.merge!(SSL_OPTIONS) if ssl
42
+
43
+ @server = WEBrick::HTTPServer.new( @options )
44
+ @server.mount "/", ::Rack::Handler.get( :webrick ), app
45
+ end
46
+
47
+
48
+ attr_reader :server
49
+
50
+ # Run the server, returning the Thread it is running in.
51
+ def run( port=nil )
52
+ return if @thread && @thread.alive?
53
+ @server.listen( @options[:BindAddress], port ) if port
54
+ @server.listen( @options[:BindAddress], fallback_port ) if fallback_port
55
+ @thread = Thread.new( &self.method(:run_server) )
56
+ return @thread
57
+ end
58
+
59
+
60
+ # Thread routine for running the server.
61
+ def run_server
62
+ Thread.current.abort_on_exception = true
63
+ @server.start
64
+ end
65
+
66
+
67
+ def stop
68
+ return unless @thread.alive?
69
+ @server.shutdown
70
+ @server = nil
71
+ @thread.join
72
+ reset
73
+ end
74
+
75
+
76
+ def ports
77
+ @server.listeners.map {|sock| sock.addr[1] }
78
+ end
79
+
80
+ def port
81
+ self.ports.first
82
+ end
83
+ alias_method :determine_port, :port
84
+
85
+
86
+ #######
87
+ private
88
+ #######
89
+
90
+ # Return the port that will be the default if the collector hasn't been
91
+ # created.
92
+ def fallback_port
93
+ 30_000 + ($$ % 10_000)
94
+ end
95
+
96
+ end
97
+ end
@@ -0,0 +1,444 @@
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 "newrelic_rpm"
6
+ require "fake_external_server"
7
+ require "evil_server"
8
+ require 'mocha'
9
+
10
+ module HttpClientTestCases
11
+ include NewRelic::Agent::Instrumentation::ControllerInstrumentation,
12
+ NewRelic::Agent::CrossAppMonitor::EncodingFunctions,
13
+ NewRelic::Agent::CrossAppTracing
14
+
15
+ TRANSACTION_GUID = 'BEC1BC64675138B9'
16
+
17
+ $fake_server = NewRelic::FakeExternalServer.new
18
+ $fake_secure_server = NewRelic::FakeSecureExternalServer.new
19
+
20
+ def setup
21
+ $fake_server.reset
22
+ $fake_server.run
23
+
24
+ $fake_secure_server.reset
25
+ $fake_secure_server.run
26
+
27
+ NewRelic::Agent.manual_start(
28
+ :"cross_application_tracer.enabled" => false,
29
+ :cross_process_id => "269975#22824",
30
+ :encoding_key => "gringletoes",
31
+ :trusted_account_ids => [269975]
32
+ )
33
+
34
+ NewRelic::Agent.instance.reset_stats
35
+
36
+ NewRelic::Agent.instance.events.clear
37
+ NewRelic::Agent.instance.cross_app_monitor.register_event_listeners
38
+ NewRelic::Agent.instance.events.notify(:finished_configuring)
39
+
40
+ @nr_header = nil
41
+ NewRelic::Agent.instance.events.subscribe(:after_call) do |_, (_, headers, _)|
42
+ headers[ NR_APPDATA_HEADER ] = @nr_header unless @nr_header.nil?
43
+ end
44
+
45
+ @engine = NewRelic::Agent.instance.stats_engine
46
+ NewRelic::Agent::TransactionInfo.get.guid = TRANSACTION_GUID
47
+ end
48
+
49
+ def teardown
50
+ NewRelic::Agent.instance.transaction_sampler.reset!
51
+ Thread::current[:newrelic_scope_stack] = nil
52
+ NewRelic::Agent.instance.stats_engine.end_transaction
53
+ end
54
+
55
+ # Helpers to support shared tests
56
+
57
+ def use_ssl
58
+ @ssl = true
59
+ end
60
+
61
+ def use_ssl?
62
+ @ssl
63
+ end
64
+
65
+ def server
66
+ @ssl ? $fake_secure_server : $fake_server
67
+ end
68
+
69
+ def protocol
70
+ @ssl ? "https" : "http"
71
+ end
72
+
73
+ def default_url
74
+ "#{protocol}://localhost:#{server.port}/status"
75
+ end
76
+
77
+ def default_uri
78
+ URI.parse(default_url)
79
+ end
80
+
81
+ def body(res)
82
+ res.body
83
+ end
84
+
85
+ # Tests
86
+
87
+ def test_validate_request_wrapper
88
+ req = request_instance
89
+ req.respond_to?(:type)
90
+ req.respond_to?(:host)
91
+ req.respond_to?(:method)
92
+ req.respond_to?(:[])
93
+ req.respond_to?(:[]=)
94
+ req.respond_to?(:uri)
95
+ end
96
+
97
+ def test_validate_response_wrapper
98
+ res = response_instance
99
+ res.respond_to?(:[])
100
+ res.respond_to?(:to_hash)
101
+ end
102
+
103
+ # Some libraries (older Typhoeus), have had odd behavior around [] for
104
+ # missing keys. This generates log messages, although it behaves right in
105
+ # terms of metrics, so double-check we get what we expect
106
+ def test_request_headers_for_missing_key
107
+ assert_nil request_instance["boo"]
108
+ end
109
+
110
+ def test_response_headers_for_missing_key
111
+ assert_nil response_instance["boo"]
112
+ end
113
+
114
+ def test_get
115
+ res = get_response
116
+
117
+ assert_match %r/<head>/i, body(res)
118
+ assert_metrics_recorded([
119
+ "External/all",
120
+ "External/localhost/#{client_name}/GET",
121
+ "External/allOther",
122
+ "External/localhost/all"
123
+ ])
124
+ end
125
+
126
+ # Only some HTTP clients support explicit connection reuse, so this test
127
+ # checks whether the host responds to get_response_multi before executing.
128
+ def test_get_with_reused_connection
129
+ if self.respond_to?(:get_response_multi)
130
+ n = 2
131
+ responses = get_response_multi(default_url, n)
132
+
133
+ responses.each do |res|
134
+ assert_match %r/<head>/i, body(res)
135
+ end
136
+
137
+ expected = { :call_count => n }
138
+ assert_metrics_recorded(
139
+ "External/all" => expected,
140
+ "External/localhost/#{client_name}/GET" => expected,
141
+ "External/allOther" => expected,
142
+ "External/localhost/all" => expected
143
+ )
144
+ end
145
+ end
146
+
147
+ def test_background
148
+ res = nil
149
+
150
+ perform_action_with_newrelic_trace("task", :category => :task) do
151
+ res = get_response
152
+ end
153
+
154
+ assert_match %r/<head>/i, body(res)
155
+ assert_metrics_recorded([
156
+ "External/all",
157
+ "External/allOther",
158
+ "External/localhost/all",
159
+ "External/localhost/#{client_name}/GET",
160
+ ["External/localhost/#{client_name}/GET", "OtherTransaction/Background/#{self.class.name}/task"],
161
+ "OtherTransaction/Background/#{self.class.name}/task",
162
+ "OtherTransaction/Background/all",
163
+ "OtherTransaction/all"
164
+ ])
165
+ end
166
+
167
+ def test_transactional_metrics
168
+ res = nil
169
+
170
+ perform_action_with_newrelic_trace("task") do
171
+ res = get_response
172
+ end
173
+
174
+ assert_match %r/<head>/i, body(res)
175
+ assert_metrics_recorded([
176
+ "External/all",
177
+ "External/localhost/#{client_name}/GET",
178
+ "External/allWeb",
179
+ "External/localhost/all",
180
+ "Controller/#{self.class.name}/task"
181
+ ])
182
+
183
+ assert_metrics_not_recorded([
184
+ "External/allOther"
185
+ ])
186
+ end
187
+
188
+
189
+ def test_transactional_traces_nodes
190
+ perform_action_with_newrelic_trace("task") do
191
+ res = get_response
192
+
193
+ last_segment = find_last_transaction_segment()
194
+ assert_equal "External/localhost/#{client_name}/GET", last_segment.metric_name
195
+ end
196
+ end
197
+
198
+ def test_ignore
199
+ in_transaction do
200
+ NewRelic::Agent.disable_all_tracing do
201
+ res = post_response
202
+ end
203
+ end
204
+
205
+ assert_metrics_recorded([])
206
+ end
207
+
208
+ def test_head
209
+ res = head_response
210
+
211
+ assert_metrics_recorded([
212
+ "External/all",
213
+ "External/localhost/#{client_name}/HEAD",
214
+ "External/allOther",
215
+ "External/localhost/all"
216
+ ])
217
+ end
218
+
219
+ def test_post
220
+ post_response
221
+
222
+ assert_metrics_recorded([
223
+ "External/all",
224
+ "External/localhost/#{client_name}/POST",
225
+ "External/allOther",
226
+ "External/localhost/all"
227
+ ])
228
+ end
229
+
230
+ # When an http call is made, the agent should add a request header named
231
+ # X-NewRelic-ID with a value equal to the encoded cross_app_id.
232
+
233
+ def test_adds_a_request_header_to_outgoing_requests_if_xp_enabled
234
+ with_config(:"cross_application_tracer.enabled" => true) do
235
+ get_response
236
+ assert_equal "VURQV1BZRkZdXUFT", server.requests.last["HTTP_X_NEWRELIC_ID"]
237
+ end
238
+ end
239
+
240
+ def test_adds_a_request_header_to_outgoing_requests_if_old_xp_config_is_present
241
+ with_config(:cross_application_tracing => true) do
242
+ get_response
243
+ assert_equal "VURQV1BZRkZdXUFT", server.requests.last["HTTP_X_NEWRELIC_ID"]
244
+ end
245
+ end
246
+
247
+ def test_agent_doesnt_add_a_request_header_to_outgoing_requests_if_xp_disabled
248
+ get_response
249
+ assert_equal false, server.requests.last.keys.any? {|k| k =~ /NEWRELIC_ID/}
250
+ end
251
+
252
+
253
+ def test_instrumentation_with_crossapp_enabled_records_normal_metrics_if_no_header_present
254
+ @nr_header = ""
255
+
256
+ with_config(:"cross_application_tracer.enabled" => true) do
257
+ in_transaction("test") do
258
+ get_response
259
+ end
260
+ end
261
+
262
+ assert_metrics_recorded([
263
+ "External/all",
264
+ "External/allOther",
265
+ "External/localhost/all",
266
+ "External/localhost/#{client_name}/GET",
267
+ ["External/localhost/#{client_name}/GET", "test"]
268
+ ])
269
+ end
270
+
271
+ def test_instrumentation_with_crossapp_disabled_records_normal_metrics_even_if_header_is_present
272
+ @nr_header =
273
+ make_app_data_payload( "18#1884", "txn-name", 2, 8, 0, TRANSACTION_GUID )
274
+
275
+ in_transaction("test") do
276
+ get_response
277
+ end
278
+
279
+ assert_metrics_recorded([
280
+ "External/all",
281
+ "External/allOther",
282
+ "External/localhost/all",
283
+ "External/localhost/#{client_name}/GET",
284
+ ["External/localhost/#{client_name}/GET", "test"]
285
+ ])
286
+ end
287
+
288
+ def test_instrumentation_with_crossapp_enabled_records_crossapp_metrics_if_header_present
289
+ @nr_header =
290
+ make_app_data_payload( "18#1884", "txn-name", 2, 8, 0, TRANSACTION_GUID )
291
+
292
+ with_config(:"cross_application_tracer.enabled" => true) do
293
+ in_transaction("test") do
294
+ get_response
295
+
296
+ last_segment = find_last_transaction_segment()
297
+ assert_includes last_segment.params.keys, :transaction_guid
298
+ assert_equal TRANSACTION_GUID, last_segment.params[:transaction_guid]
299
+ end
300
+ end
301
+
302
+ assert_metrics_recorded([
303
+ "External/all",
304
+ "External/allOther",
305
+ "ExternalApp/localhost/18#1884/all",
306
+ "ExternalTransaction/localhost/18#1884/txn-name",
307
+ "External/localhost/all",
308
+ ["ExternalTransaction/localhost/18#1884/txn-name", "test"]
309
+ ])
310
+ end
311
+
312
+ def test_crossapp_metrics_allow_valid_utf8_characters
313
+ @nr_header =
314
+ make_app_data_payload( "12#1114", "世界線航跡蔵", 18.0, 88.1, 4096, TRANSACTION_GUID )
315
+
316
+ with_config(:"cross_application_tracer.enabled" => true) do
317
+ in_transaction("test") do
318
+ get_response
319
+
320
+ last_segment = find_last_transaction_segment()
321
+ assert_includes last_segment.params.keys, :transaction_guid
322
+ assert_equal TRANSACTION_GUID, last_segment.params[:transaction_guid]
323
+ end
324
+ end
325
+
326
+ assert_metrics_recorded([
327
+ "External/all",
328
+ "External/allOther",
329
+ "ExternalApp/localhost/12#1114/all",
330
+ "External/localhost/all",
331
+ "ExternalTransaction/localhost/12#1114/世界線航跡蔵",
332
+ ["ExternalTransaction/localhost/12#1114/世界線航跡蔵", "test"]
333
+ ])
334
+ end
335
+
336
+ def test_crossapp_metrics_ignores_crossapp_header_with_malformed_crossprocess_id
337
+ @nr_header =
338
+ make_app_data_payload( "88#88#88", "invalid", 1, 2, 4096, TRANSACTION_GUID )
339
+
340
+ with_config(:"cross_application_tracer.enabled" => true) do
341
+ in_transaction("test") do
342
+ get_response
343
+ end
344
+ end
345
+
346
+ assert_metrics_recorded([
347
+ "External/all",
348
+ "External/allOther",
349
+ "External/localhost/#{client_name}/GET",
350
+ "External/localhost/all",
351
+ ["External/localhost/#{client_name}/GET", "test"]
352
+ ])
353
+ end
354
+
355
+ def test_doesnt_affect_the_request_if_an_exception_is_raised_while_setting_up_tracing
356
+ res = nil
357
+ NewRelic::Agent.instance.stats_engine.stubs( :push_scope ).
358
+ raises( NoMethodError, "undefined method `push_scope'" )
359
+
360
+ with_config(:"cross_application_tracer.enabled" => true) do
361
+ assert_nothing_raised do
362
+ res = get_response
363
+ end
364
+ end
365
+
366
+ assert_equal NewRelic::FakeExternalServer::STATUS_MESSAGE, body(res)
367
+ end
368
+
369
+ def test_doesnt_affect_the_request_if_an_exception_is_raised_while_finishing_tracing
370
+ res = nil
371
+ NewRelic::Agent.instance.stats_engine.stubs( :pop_scope ).
372
+ raises( NoMethodError, "undefined method `pop_scope'" )
373
+
374
+ with_config(:"cross_application_tracer.enabled" => true) do
375
+ assert_nothing_raised do
376
+ res = get_response
377
+ end
378
+ end
379
+
380
+ assert_equal NewRelic::FakeExternalServer::STATUS_MESSAGE, body(res)
381
+ end
382
+
383
+ def test_doesnt_misbehave_when_transaction_tracing_is_disabled
384
+ @engine.transaction_sampler = nil
385
+
386
+ # The error should have any other consequence other than logging the error, so
387
+ # this will capture logs
388
+ logger = NewRelic::Agent::MemoryLogger.new
389
+ NewRelic::Agent.logger = logger
390
+
391
+ with_config(:"cross_application_tracer.enabled" => true) do
392
+ get_response
393
+ end
394
+
395
+ assert_no_match( /undefined method `rename_scope_segment" for nil:NilClass/i,
396
+ logger.messages.flatten.map {|log| log.to_s }.join(" ") )
397
+
398
+ ensure
399
+ @engine.transaction_sampler = NewRelic::Agent.agent.transaction_sampler
400
+ end
401
+
402
+ def test_includes_full_url_in_transaction_trace
403
+ full_url = "#{default_url}?foo=bar#fragment"
404
+ in_transaction do
405
+ get_response(full_url)
406
+ last_segment = find_last_transaction_segment()
407
+ filtered_uri = default_url
408
+ assert_equal filtered_uri, last_segment.params[:uri]
409
+ end
410
+ end
411
+
412
+ def test_still_records_tt_node_when_request_fails
413
+ # This test does not work on older versions of Typhoeus, because the
414
+ # on_complete callback is not reliably invoked. That said, it's a corner
415
+ # case, and the failure mode is just that you lose tracing for the one
416
+ # transaction in which the error occurs. That, coupled with the fact that
417
+ # fixing it for old versions of Typhoeus would require large changes to
418
+ # the instrumentation, makes us say 'meh'.
419
+ if client_name == 'Typhoeus' && Typhoeus::VERSION >= "0.5.4"
420
+ evil_server = NewRelic::EvilServer.new
421
+ evil_server.start
422
+
423
+ in_transaction do
424
+ begin
425
+ get_response("http://localhost:#{evil_server.port}")
426
+ rescue => e
427
+ # it's expected that this will raise for some HTTP libraries (e.g.
428
+ # Net::HTTP). we unfortunately don't know the exact exception class
429
+ # across all libraries
430
+ end
431
+
432
+ last_segment = find_last_transaction_segment()
433
+ assert_equal("External/localhost/#{client_name}/GET", last_segment.metric_name)
434
+ end
435
+
436
+ evil_server.stop
437
+ end
438
+ end
439
+
440
+ def make_app_data_payload( *args )
441
+ return obfuscate_with_key( 'gringletoes', args.to_json ).gsub( /\n/, '' ) + "\n"
442
+ end
443
+
444
+ end