newrelic_rpm 4.5.0.337 → 4.6.0.338

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/.yardopts +2 -0
  3. data/CHANGELOG.md +8 -0
  4. data/lib/new_relic/agent.rb +1 -0
  5. data/lib/new_relic/agent/agent.rb +19 -12
  6. data/lib/new_relic/agent/configuration/default_source.rb +7 -0
  7. data/lib/new_relic/agent/distributed_trace_monitor.rb +29 -0
  8. data/lib/new_relic/agent/distributed_trace_payload.rb +223 -0
  9. data/lib/new_relic/agent/distributed_trace_priority_sampled_buffer.rb +72 -0
  10. data/lib/new_relic/agent/external.rb +137 -0
  11. data/lib/new_relic/agent/http_clients/abstract_request.rb +31 -0
  12. data/lib/new_relic/agent/http_clients/curb_wrappers.rb +3 -1
  13. data/lib/new_relic/agent/http_clients/excon_wrappers.rb +3 -1
  14. data/lib/new_relic/agent/http_clients/http_rb_wrappers.rb +3 -1
  15. data/lib/new_relic/agent/http_clients/httpclient_wrappers.rb +3 -1
  16. data/lib/new_relic/agent/http_clients/net_http_wrappers.rb +3 -1
  17. data/lib/new_relic/agent/http_clients/typhoeus_wrappers.rb +3 -1
  18. data/lib/new_relic/agent/sql_sampler.rb +2 -2
  19. data/lib/new_relic/agent/throughput_monitor.rb +59 -0
  20. data/lib/new_relic/agent/transaction.rb +59 -15
  21. data/lib/new_relic/agent/transaction/abstract_segment.rb +17 -5
  22. data/lib/new_relic/agent/transaction/distributed_tracing.rb +121 -0
  23. data/lib/new_relic/agent/transaction/external_request_segment.rb +183 -19
  24. data/lib/new_relic/agent/transaction/segment.rb +2 -3
  25. data/lib/new_relic/agent/transaction/trace.rb +14 -5
  26. data/lib/new_relic/agent/transaction/trace_builder.rb +58 -0
  27. data/lib/new_relic/agent/transaction/trace_node.rb +34 -32
  28. data/lib/new_relic/agent/transaction/tracing.rb +4 -9
  29. data/lib/new_relic/agent/transaction_error_primitive.rb +18 -0
  30. data/lib/new_relic/agent/transaction_event_aggregator.rb +14 -0
  31. data/lib/new_relic/agent/transaction_event_primitive.rb +14 -0
  32. data/lib/new_relic/agent/transaction_event_recorder.rb +9 -1
  33. data/lib/new_relic/agent/transaction_sampler.rb +12 -66
  34. data/lib/new_relic/agent/transaction_state.rb +1 -3
  35. data/lib/new_relic/supportability_helper.rb +5 -0
  36. data/lib/new_relic/version.rb +1 -1
  37. data/newrelic.yml +1 -1
  38. data/newrelic_rpm.gemspec +1 -1
  39. data/test/agent_helper.rb +11 -3
  40. metadata +10 -4
  41. data/lib/new_relic/agent/transaction_sample_builder.rb +0 -138
@@ -37,12 +37,13 @@ module NewRelic
37
37
  transaction.segment_complete self
38
38
  end
39
39
  rescue => e
40
- # This rescue block was added for the benefit of this test:
41
- # test/multiverse/suites/bare/standalone_instrumentation_test.rb
42
- # See the top of the test for details.
43
40
  NewRelic::Agent.logger.error "Exception finishing segment: #{name}", e
44
41
  end
45
42
 
43
+ def finished?
44
+ !!@end_time
45
+ end
46
+
46
47
  def record_metrics?
47
48
  @record_metrics
48
49
  end
@@ -55,8 +56,15 @@ module NewRelic
55
56
  @record_on_finish
56
57
  end
57
58
 
58
- def record_metrics
59
- raise NotImplementedError, "Subclasses must implement record_metrics"
59
+ def finalize
60
+ unless finished?
61
+ finish
62
+ NewRelic::Agent.logger.warn "Segment: #{name} was unfinished at " \
63
+ "the end of transaction. Timing information for " \
64
+ "#{transaction.best_name} may be inaccurate."
65
+ # @todo: we should record a supportability metric here
66
+ end
67
+ record_metrics if record_metrics?
60
68
  end
61
69
 
62
70
  def params
@@ -78,6 +86,10 @@ module NewRelic
78
86
 
79
87
  protected
80
88
 
89
+ def record_metrics
90
+ raise NotImplementedError, "Subclasses must implement record_metrics"
91
+ end
92
+
81
93
  def child_complete segment
82
94
  if segment.record_metrics?
83
95
  self.children_time += segment.duration
@@ -0,0 +1,121 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ require 'new_relic/agent/distributed_trace_payload'
6
+
7
+ module NewRelic
8
+ module Agent
9
+ class Transaction
10
+ module DistributedTracing
11
+ attr_accessor :distributed_trace_payload
12
+
13
+ def distributed_trace?
14
+ !!distributed_trace_payload
15
+ end
16
+
17
+ def create_distributed_trace_payload url = nil
18
+ return unless Agent.config[:'distributed_tracing.enabled']
19
+ self.order += 1
20
+ DistributedTracePayload.for_transaction self, url
21
+ end
22
+
23
+ LBRACE = "{".freeze
24
+
25
+ def accept_distributed_trace_payload transport_type, payload
26
+ return unless Agent.config[:'distributed_tracing.enabled']
27
+ if distributed_trace_payload
28
+ NewRelic::Agent.increment_metric "Supportability/DistributedTracing/AcceptFailure/PayloadAlreadyAccepted"
29
+ return false
30
+ elsif self.order > 0
31
+ NewRelic::Agent.increment_metric "Supportability/DistributedTracing/AcceptFailure/CreateDistributedTracePayload-before-AcceptDistributedTracePayload"
32
+ return false
33
+ elsif name_frozen?
34
+ NewRelic::Agent.increment_metric "Supportability/DistributedTracing/AcceptFailure/BrowserAgentInjected"
35
+ return false
36
+ end
37
+
38
+ payload = if payload.start_with? LBRACE
39
+ DistributedTracePayload.from_json payload
40
+ else
41
+ DistributedTracePayload.from_http_safe payload
42
+ end
43
+
44
+ payload.caller_transport_type = transport_type
45
+ self.distributed_trace_payload = payload
46
+
47
+ self.sampled = payload.sampled unless payload.sampled.nil?
48
+
49
+ true
50
+ rescue => e
51
+ NewRelic::Agent.increment_metric "Supportability/DistributedTracing/AcceptFailure/Unknown"
52
+ NewRelic::Agent.logger.warn "Failed to accept distributed trace payload", e
53
+ false
54
+ end
55
+
56
+ def distributed_tracing_trip_id
57
+ if distributed_trace_payload
58
+ distributed_trace_payload.trip_id
59
+ else
60
+ guid
61
+ end
62
+ end
63
+
64
+ def parent_ids
65
+ if distributed_trace_payload &&
66
+ distributed_trace_payload.parent_ids &&
67
+ distributed_trace_payload.parent_ids.last != guid
68
+ inbound_ids = distributed_trace_payload.parent_ids.dup
69
+ inbound_ids.push guid
70
+ if inbound_ids.size > 3
71
+ inbound_ids.shift
72
+ end
73
+ inbound_ids
74
+ else
75
+ [guid]
76
+ end
77
+ end
78
+
79
+ def depth
80
+ if distributed_trace_payload
81
+ distributed_trace_payload.depth
82
+ else
83
+ 1
84
+ end
85
+ end
86
+
87
+ def order
88
+ @order ||= 0
89
+ end
90
+
91
+ attr_writer :order
92
+
93
+ def append_distributed_tracing_info payload
94
+ return unless Agent.config[:'distributed_tracing.enabled']
95
+ if distributed_trace_payload
96
+ distributed_trace_payload.assign_intrinsics self, payload
97
+ elsif order > 0
98
+ DistributedTracePayload.assign_initial_intrinsics self, payload
99
+ end
100
+ end
101
+
102
+ def assign_distributed_tracing_intrinsics
103
+ return unless Agent.config[:'distributed_tracing.enabled']
104
+ DistributedTracePayload::INTRINSIC_KEYS.each do |key|
105
+ next unless value = @payload[key]
106
+ attributes.add_intrinsic_attribute key, value
107
+ end
108
+ nil
109
+ end
110
+
111
+ # This method returns transport_duration in seconds. Transport duration
112
+ # is stored in milliseconds on the payload, but it's needed in seconds
113
+ # for metrics and intrinsics.
114
+ def transport_duration
115
+ return unless distributed_trace_payload
116
+ (start_time.to_f * 1000 - distributed_trace_payload.timestamp) / 1000
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -8,12 +8,17 @@ require 'new_relic/agent/http_clients/uri_util'
8
8
  module NewRelic
9
9
  module Agent
10
10
  class Transaction
11
+
12
+ #
13
+ # This class represents an external segment in a transaction trace.
14
+ #
15
+ # @api public
11
16
  class ExternalRequestSegment < Segment
12
17
  attr_reader :library, :uri, :procedure
13
18
 
14
19
  NR_SYNTHETICS_HEADER = 'X-NewRelic-Synthetics'.freeze
15
20
 
16
- def initialize library, uri, procedure
21
+ def initialize library, uri, procedure # :nodoc:
17
22
  @library = library
18
23
  @uri = HTTPClients::URIUtil.parse_and_normalize_url(uri)
19
24
  @procedure = procedure
@@ -22,20 +27,25 @@ module NewRelic
22
27
  super()
23
28
  end
24
29
 
25
- def name
30
+ def name # :nodoc:
26
31
  @name ||= "External/#{host}/#{library}/#{procedure}"
27
32
  end
28
33
 
29
- def host
34
+ def host # :nodoc:
30
35
  @host_header || uri.host
31
36
  end
32
37
 
33
- # This method will add NewRelic headers for cross application tracing and
34
- # will check to see if a host header is used for the request. If a host
35
- # header is used it will update the segment name to reflect the host header.
38
+ # This method adds New Relic request headers to a given request made to an
39
+ # external API and checks to see if a host header is used for the request.
40
+ # If a host header is used, it updates the segment name to match the host
41
+ # header.
42
+ #
43
+ # @param [NewRelic::Agent::HTTPClients::AbstractRequest] request the request
44
+ # object (must belong to a subclass of NewRelic::Agent::HTTPClients::AbstractRequest)
45
+ #
46
+ # @api public
36
47
  def add_request_headers request
37
48
  process_host_header request
38
-
39
49
  synthetics_header = transaction && transaction.raw_synthetics_header
40
50
  insert_synthetics_header request, synthetics_header if synthetics_header
41
51
 
@@ -43,14 +53,22 @@ module NewRelic
43
53
 
44
54
  transaction_state.is_cross_app_caller = true
45
55
  txn_guid = transaction_state.request_guid
46
- trip_id = transaction && transaction.cat_trip_id(transaction_state)
47
- path_hash = transaction && transaction.cat_path_hash(transaction_state)
56
+ trip_id = transaction && transaction.cat_trip_id
57
+ path_hash = transaction && transaction.cat_path_hash
48
58
 
49
59
  CrossAppTracing.insert_request_headers request, txn_guid, trip_id, path_hash
60
+ insert_distributed_trace_header request
50
61
  rescue => e
51
62
  NewRelic::Agent.logger.error "Error in add_request_headers", e
52
63
  end
53
-
64
+
65
+ # This method extracts app data from an external response if present. If
66
+ # a valid cross-app ID is found, the name of the segment is updated to
67
+ # reflect the cross-process ID and transaction name.
68
+ #
69
+ # @param [Hash] response a hash of response headers
70
+ #
71
+ # @api public
54
72
  def read_response_headers response
55
73
  return unless record_metrics? && CrossAppTracing.cross_app_enabled?
56
74
  return unless CrossAppTracing.response_has_crossapp_header?(response)
@@ -69,22 +87,103 @@ module NewRelic
69
87
  NewRelic::Agent.logger.error "Error in read_response_headers", e
70
88
  end
71
89
 
72
- def cross_app_request?
90
+ def cross_app_request? # :nodoc:
73
91
  !!@app_data
74
92
  end
75
93
 
76
- def cross_process_id
94
+ def cross_process_id # :nodoc:
77
95
  @app_data && @app_data[0]
78
96
  end
79
97
 
80
- def transaction_guid
98
+ def transaction_guid # :nodoc:
81
99
  @app_data && @app_data[5]
82
100
  end
83
101
 
84
- def cross_process_transaction_name
102
+ def cross_process_transaction_name # :nodoc:
85
103
  @app_data && @app_data[1]
86
104
  end
87
105
 
106
+ EXTERNAL_TRANSACTION_PREFIX = 'ExternalTransaction/'.freeze
107
+ SLASH = '/'.freeze
108
+ APP_DATA_KEY = 'NewRelicAppData'.freeze
109
+
110
+ # Obtain an obfuscated +String+ suitable for delivery across public networks that identifies this application
111
+ # and transaction to another application which is also running a New Relic agent. This +String+ can be processed
112
+ # by +process_request_metadata+ on the receiving application.
113
+ #
114
+ # @return [String] obfuscated request metadata to send
115
+ #
116
+ # @api public
117
+ #
118
+ def get_request_metadata
119
+ NewRelic::Agent.record_api_supportability_metric(:get_request_metadata)
120
+ return unless CrossAppTracing.cross_app_enabled?
121
+
122
+ if transaction
123
+
124
+ # build hash of CAT metadata
125
+ #
126
+ rmd = {
127
+ NewRelicID: NewRelic::Agent.config[:cross_process_id],
128
+ NewRelicTransaction: [
129
+ transaction.guid,
130
+ false,
131
+ transaction.cat_trip_id,
132
+ transaction.cat_path_hash
133
+ ]
134
+ }
135
+
136
+ # flag cross app in the state so transaction knows to add bits to paylaod
137
+ #
138
+ transaction.state.is_cross_app_caller = true
139
+
140
+ # add Synthetics header if we have it
141
+ #
142
+ rmd[:NewRelicSynthetics] = transaction.raw_synthetics_header if transaction.raw_synthetics_header
143
+
144
+ # obfuscate the generated request metadata JSON
145
+ #
146
+ obfuscator.obfuscate ::JSON.dump(rmd)
147
+
148
+ end
149
+ rescue => e
150
+ NewRelic::Agent.logger.error "error during get_request_metadata", e
151
+ end
152
+
153
+ # Process obfuscated +String+ sent from a called application that is also running a New Relic agent and
154
+ # save information in current transaction for inclusion in a trace. This +String+ is generated by
155
+ # +get_response_metadata+ on the receiving application.
156
+ #
157
+ # @param response_metadata [String] received obfuscated response metadata
158
+ #
159
+ # @api public
160
+ #
161
+ def process_response_metadata response_metadata
162
+ NewRelic::Agent.record_api_supportability_metric(:process_response_metadata)
163
+ if transaction
164
+ app_data = ::JSON.parse(obfuscator.deobfuscate(response_metadata))[APP_DATA_KEY]
165
+
166
+ # validate cross app id
167
+ #
168
+ if Array === app_data and CrossAppTracing.trusted_valid_cross_app_id? app_data[0]
169
+ @app_data = app_data
170
+ update_segment_name
171
+ else
172
+ NewRelic::Agent.logger.error "error processing response metadata: invalid/non-trusted ID"
173
+ end
174
+ end
175
+
176
+ nil
177
+ rescue => e
178
+ NewRelic::Agent.logger.error "error during process_response_metadata", e
179
+ end
180
+
181
+ def record_metrics
182
+ add_unscoped_metrics
183
+ record_distributed_tracing_metrics if Agent.config[:'distributed_tracing.enabled']
184
+ super
185
+ end
186
+
88
187
  private
89
188
 
90
189
  def insert_synthetics_header request, header
@@ -106,19 +205,25 @@ module NewRelic
106
205
  end
107
206
  end
108
207
 
208
+ X_NEWRELIC_TRACE_HEADER = "X-NewRelic-Trace".freeze
209
+
210
+ def insert_distributed_trace_header request
211
+ return unless Agent.config[:'distributed_tracing.enabled']
212
+ payload = transaction.create_distributed_trace_payload uri
213
+ request[X_NEWRELIC_TRACE_HEADER] = payload.http_safe
214
+ end
215
+
109
216
  EXTERNAL_ALL = "External/all".freeze
110
217
 
111
- def unscoped_metrics
112
- metrics = [ EXTERNAL_ALL,
218
+ def add_unscoped_metrics
219
+ @unscoped_metrics = [ EXTERNAL_ALL,
113
220
  "External/#{host}/all",
114
221
  suffixed_rollup_metric
115
222
  ]
116
223
 
117
224
  if cross_app_request?
118
- metrics << "ExternalApp/#{host}/#{cross_process_id}/all"
225
+ @unscoped_metrics << "ExternalApp/#{host}/#{cross_process_id}/all"
119
226
  end
120
-
121
- metrics
122
227
  end
123
228
 
124
229
  EXTERNAL_ALL_WEB = "External/allWeb".freeze
@@ -132,6 +237,61 @@ module NewRelic
132
237
  end
133
238
  end
134
239
 
240
+ ALL_SUFFIX = "all".freeze
241
+ ALL_WEB_SUFFIX = "allWeb".freeze
242
+ ALL_OTHER_SUFFIX = "allOther".freeze
243
+
244
+ def transaction_type_suffix
245
+ if Transaction.recording_web_transaction?
246
+ ALL_WEB_SUFFIX
247
+ else
248
+ ALL_OTHER_SUFFIX
249
+ end
250
+ end
251
+
252
+ def record_distributed_tracing_metrics
253
+ add_caller_by_duration_metrics
254
+ record_transport_duration_metrics
255
+ record_errors_by_caller_metrics
256
+ end
257
+
258
+ DURATION_BY_CALLER_UNKOWN_PREFIX = "DurationByCaller/Unknown/Unknown/Unknown/Unknown".freeze
259
+
260
+ def add_caller_by_duration_metrics
261
+ prefix = if transaction.distributed_trace?
262
+ payload = transaction.distributed_trace_payload
263
+ "DurationByCaller/#{payload.caller_type}/#{payload.caller_account_id}/#{payload.caller_app_id}/transport"
264
+ else
265
+ DURATION_BY_CALLER_UNKOWN_PREFIX
266
+ end
267
+
268
+ @unscoped_metrics << "#{prefix}/#{ALL_SUFFIX}"
269
+ @unscoped_metrics << "#{prefix}/#{transaction_type_suffix}"
270
+ end
271
+
272
+ def record_transport_duration_metrics
273
+ return unless transaction.distributed_trace?
274
+ payload = transaction.distributed_trace_payload
275
+ prefix = "TransportDuration/#{payload.caller_type}/#{payload.caller_account_id}/#{payload.caller_app_id}/transport"
276
+ metric_cache.record_unscoped "#{prefix}/#{ALL_SUFFIX}", transaction.transport_duration
277
+ metric_cache.record_unscoped "#{prefix}/#{transaction_type_suffix}", transaction.transport_duration
278
+ end
279
+
280
+ ERRORS_BY_CALLER_UNKOWN_PREFIX = "ErrorsByCaller/Unknown/Unknown/Unknown/Unknown".freeze
281
+
282
+ def record_errors_by_caller_metrics
283
+ return unless transaction.exceptions.size > 0
284
+ prefix = if transaction.distributed_trace?
285
+ payload = transaction.distributed_trace_payload
286
+ "ErrorsByCaller/#{payload.caller_type}/#{payload.caller_account_id}/#{payload.caller_app_id}/transport"
287
+ else
288
+ ERRORS_BY_CALLER_UNKOWN_PREFIX
289
+ end
290
+
291
+ NewRelic::Agent.increment_metric "#{prefix}/#{ALL_SUFFIX}"
292
+ NewRelic::Agent.increment_metric "#{prefix}/#{transaction_type_suffix}"
293
+ end
294
+
135
295
  def update_segment_name
136
296
  if @app_data
137
297
  @name = "ExternalTransaction/#{host}/#{cross_process_id}/#{cross_process_transaction_name}"
@@ -139,6 +299,10 @@ module NewRelic
139
299
  @name = "External/#{host}/#{library}/#{procedure}"
140
300
  end
141
301
  end
302
+
303
+ def obfuscator
304
+ CrossAppTracing.obfuscator
305
+ end
142
306
  end
143
307
  end
144
308
  end