Module: NewRelic::Agent::CrossAppTracing

Extended by:
NewRelic::Agent::CrossAppMonitor::EncodingFunctions
Defined in:
lib/new_relic/agent/cross_app_tracing.rb

Defined Under Namespace

Classes: Error

Constant Summary collapse

NR_APPDATA_HEADER =

The cross app response header for “outgoing” calls

'X-NewRelic-App-Data'
NR_ID_HEADER =

The cross app id header for “outgoing” calls

'X-NewRelic-ID'
NR_TXN_HEADER =

The cross app transaction header for “outgoing” calls

'X-NewRelic-Transaction'
APPDATA_TXN_GUID_INDEX =

The index of the transaction GUID in the appdata header of responses

5

Class Method Summary collapse

Methods included from NewRelic::Agent::CrossAppMonitor::EncodingFunctions

decode_with_key, encode_with_key, obfuscate_with_key

Class Method Details

.check_crossapp_id(id) ⇒ Object

Check the given id to ensure it conforms to the format of a cross-application ID. Raises an NewRelic::Agent::CrossAppTracing::Error if it doesn’t.



264
265
266
267
268
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 264

def check_crossapp_id( id )
  id =~ /\A\d+#\d+\z/ or
    raise NewRelic::Agent::CrossAppTracing::Error,
      "malformed cross application ID %p" % [ id ]
end

.check_transaction_name(name) ⇒ Object

Check the given name to ensure it conforms to the format of a valid transaction name.



273
274
275
276
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 273

def check_transaction_name( name )
  # No-op -- apparently absolutely anything is a valid transaction name?
  # This is here for when that inevitably comes back to haunt us.
end

.common_metrics(http) ⇒ Object

Return an Array of metrics used for every response.



162
163
164
165
166
167
168
169
170
171
172
173
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 162

def common_metrics( http )
  metrics = [ "External/all" ]
  metrics << "External/#{http.address}/all"

  if NewRelic::Agent::Instrumentation::MetricFrame.recording_web_transaction?
    metrics << "External/allWeb"
  else
    metrics << "External/allOther"
  end

  return metrics
end

.cross_app_enabled?Boolean

Return true if cross app tracing is enabled in the config.

Returns:

  • (Boolean)


96
97
98
99
100
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 96

def cross_app_enabled?
  NewRelic::Agent.config[:cross_process_id] &&
    (NewRelic::Agent.config[:"cross_application_tracer.enabled"] ||
     NewRelic::Agent.config[:cross_application_tracing])
end

.cross_app_encoding_keyObject

Memoized fetcher for the cross app encoding key. Raises a NewRelic::Agent::CrossAppTracing::Error if the key isn’t configured.



105
106
107
108
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 105

def cross_app_encoding_key
  NewRelic::Agent.config[:encoding_key] or
    raise NewRelic::Agent::CrossAppTracing::Error, "No encoding_key set."
end

.extract_appdata(response) ⇒ Object

Extract x-process application data from the specified response and return it as an array of the form:

[
  <cross app ID>,
  <transaction name>,
  <queue time in seconds>,
  <response time in seconds>,
  <request content length in bytes>,
  <transaction GUID>
]


219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 219

def extract_appdata( response )
  appdata = response[NR_APPDATA_HEADER] or
    raise NewRelic::Agent::CrossAppTracing::Error,
      "Can't derive metrics for response: no #{NR_APPDATA_HEADER} header!"

  key = cross_app_encoding_key()
  decoded_appdata = decode_with_key( key, appdata )
  decoded_appdata.set_encoding( ::Encoding::UTF_8 ) if
    decoded_appdata.respond_to?( :set_encoding )

  return NewRelic.json_load( decoded_appdata )
end

.extract_custom_parameters(response) ⇒ Object

Extract any custom parameters from response if it’s cross-application and add them to the current TT node.



129
130
131
132
133
134
135
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 129

def extract_custom_parameters( response )

  appdata = extract_appdata( response )
  sampler = NewRelic::Agent.instance.transaction_sampler
  sampler.add_segment_parameters( :transaction_guid => appdata[APPDATA_TXN_GUID_INDEX] )

end

.finish_trace(t0, segment, request, response, http) ⇒ Object

Finish tracing the HTTP request that started at t0 with the information in response and the given http connection.



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 64

def finish_trace( t0, segment, request, response, http )
  t1 = Time.now
  duration = t1.to_f - t0.to_f

  begin
    if request && response && http
      # Figure out which metrics we need to report based on the request and response
      # The last (most-specific) one is scoped.
      metrics = metrics_for( http, request, response )
      scoped_metric = metrics.pop

      # Report the metrics
      metrics.each { |metric| get_metric(metric).trace_call(duration) }
      get_scoped_metric( scoped_metric ).trace_call( duration )

      # Add TT custom parameters
      stats_engine.rename_scope_segment( scoped_metric )
      extract_custom_parameters( response ) if response_is_crossapp?( response )
    end
  ensure
    # We always need to pop the scope stack to avoid an inconsistent
    # state, which will prevent tracing of the whole transaction.
    stats_engine.pop_scope( segment, duration, t1 )
  end
rescue NewRelic::Agent::CrossAppTracing::Error => err
  NewRelic::Agent.logger.debug "while cross app tracing", err
rescue => err
  NewRelic::Agent.logger.error "Uncaught exception while finishing an HTTP request trace", err
end

.get_metric(metric_name) ⇒ Object

Convenience function for fetching the metric associated with metric_name.



244
245
246
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 244

def get_metric( metric_name )
  stats_engine.get_stats_no_scope( metric_name )
end

.get_scoped_metric(metric_name) ⇒ Object

Convenience function for fetching the scoped metric associated with metric_name.



250
251
252
253
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 250

def get_scoped_metric( metric_name )
  # Default is to use the metric_name itself as the scope, which is what we want
  stats_engine.get_stats( metric_name )
end

.inject_request_headers(request) ⇒ Object

Inject the X-Process header into the outgoing request.



112
113
114
115
116
117
118
119
120
121
122
123
124
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 112

def inject_request_headers( request )
  key = cross_app_encoding_key()
  cross_app_id = NewRelic::Agent.config[:cross_process_id] or
    raise NewRelic::Agent::CrossAppTracing::Error, "no cross app ID configured"
  txn_guid = NewRelic::Agent::TransactionInfo.get.guid
  txn_data = NewRelic.json_dump([ txn_guid, false ])

  request[ NR_ID_HEADER ] = obfuscate_with_key( key, cross_app_id )
  request[ NR_TXN_HEADER ] = obfuscate_with_key( key, txn_data )

rescue NewRelic::Agent::CrossAppTracing::Error => err
  NewRelic::Agent.logger.debug "Not injecting x-process header", err
end

.metrics_for(http, request, response) ⇒ Object

Return the set of metrics (NewRelic::MethodTraceStats objects) that correspond to the given request and response.



140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 140

def metrics_for( http, request, response )
  metrics = common_metrics( http )

  if response_is_crossapp?( response )
    begin
      metrics.concat metrics_for_crossapp_response( http, response )
    rescue => err
      # Fall back to regular metrics if there's a problem with x-process metrics
      NewRelic::Agent.logger.debug "%p while fetching x-process metrics: %s" %
        [ err.class, err.message ]
      metrics.concat metrics_for_regular_response( http, request, response )
    end
  else
    NewRelic::Agent.logger.debug "Response doesn't have CAT headers."
    metrics.concat metrics_for_regular_response( http, request, response )
  end

  return metrics
end

.metrics_for_crossapp_response(http, response) ⇒ Object

Return the set of metric objects appropriate for the given cross app response.



192
193
194
195
196
197
198
199
200
201
202
203
204
205
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 192

def metrics_for_crossapp_response( http, response )
  xp_id, txn_name, q_time, r_time, req_len, _ = extract_appdata( response )

  check_crossapp_id( xp_id )
  check_transaction_name( txn_name )

  NewRelic::Agent.logger.debug "CAT xp_id: %p, txn_name: %p." % [ xp_id, txn_name ]

  metrics = []
  metrics << "ExternalApp/#{http.address}/#{xp_id}/all"
  metrics << "ExternalTransaction/#{http.address}/#{xp_id}/#{txn_name}"

  return metrics
end

.metrics_for_regular_response(http, request, response) ⇒ Object

Return the set of metric objects appropriate for the given (non-cross app) response.



235
236
237
238
239
240
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 235

def metrics_for_regular_response( http, request, response )
  metrics = []
  metrics << "External/#{http.address}/Net::HTTP/#{request.method}"

  return metrics
end

.response_is_crossapp?(response) ⇒ Boolean

Returns true if Cross Application Tracing is enabled, and the given response has the appropriate headers.

Returns:

  • (Boolean)


178
179
180
181
182
183
184
185
186
187
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 178

def response_is_crossapp?( response )
  return false unless cross_app_enabled?
  unless response[NR_APPDATA_HEADER]
    NewRelic::Agent.logger.debug "Response doesn't have the %p header: %p" %
      [ NR_APPDATA_HEADER, response.to_hash ]
    return false
  end

  return true
end

.start_trace(http, request) ⇒ Object

Set up the necessary state for cross-application tracing before the given request goes out on the specified http connection.



48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 48

def start_trace( http, request )
  inject_request_headers( request ) if cross_app_enabled?

  # Create a segment and time the call
  t0 = Time.now
  segment = stats_engine.push_scope( "External/#{http.address}/all", t0 )

  return t0, segment
rescue => err
  NewRelic::Agent.logger.error "Uncaught exception while tracing HTTP request", err
  return nil
end

.stats_engineObject

Fetch a reference to the stats engine.



257
258
259
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 257

def stats_engine
  NewRelic::Agent.instance.stats_engine
end

.trace_http_request(http, request) ⇒ Object

Send the given request, adding metrics appropriate to the response when it comes back.



32
33
34
35
36
37
38
39
40
41
42
43
# File 'lib/new_relic/agent/cross_app_tracing.rb', line 32

def trace_http_request( http, request )
  return yield unless NewRelic::Agent.is_execution_traced?

  t0, segment = start_trace( http, request )
  begin
    response = yield
  ensure
    finish_trace( t0, segment, request, response, http ) if t0
  end

  return response
end