module Mmtrix::Agent::CrossAppTracing

Constants

APPDATA_TXN_GUID_INDEX

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

NR_APPDATA_HEADER

The cross app response header for “outgoing” calls

NR_ID_HEADER

The cross app id header for “outgoing” calls

NR_SYNTHETICS_HEADER

The cross app synthetics header

NR_TXN_HEADER

The cross app transaction header for “outgoing” calls

Public Instance Methods

add_cat_transaction_trace_parameters( response ) click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 203
def add_cat_transaction_trace_parameters( response )
  appdata = extract_appdata( response )
  transaction_sampler.add_node_parameters( \
    :transaction_guid => appdata[APPDATA_TXN_GUID_INDEX] )
end
add_transaction_trace_parameters(request, response) click to toggle source
# File lib/mmtrix/agent/cross_app_tracing.rb, line 192
def add_transaction_trace_parameters(request, response)
  filtered_uri = ::Mmtrix::Agent::HTTPClients::URIUtil.filter_uri(request.uri)
  transaction_sampler.add_node_parameters(:uri => filtered_uri)
  if response && response_is_crossapp?(response)
    add_cat_transaction_trace_parameters(response)
  end
end
check_crossapp_id( id ) click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 322
def check_crossapp_id( id )
  id =~ /\A\d+#\d+\z/ or
    raise Mmtrix::Agent::CrossAppTracing::Error,
      "malformed cross application ID %p" % [ id ]
end
check_transaction_name( name ) click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 331
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( request ) click to toggle source

Return an Array of metrics used for every response.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 235
def common_metrics( request )
  metrics = [ "External/all" ]
  metrics << "External/#{request.host}/all"

  if Mmtrix::Agent::Transaction.recording_web_transaction?
    metrics << "External/allWeb"
  else
    metrics << "External/allOther"
  end

  return metrics
end
cross_app_enabled?() click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 138
def cross_app_enabled?
  valid_cross_process_id? &&
    valid_encoding_key? &&
    cross_application_tracer_enabled?
end
cross_app_encoding_key() click to toggle source

Fetcher for the cross app encoding key. Raises a Mmtrix::Agent::CrossAppTracing::Error if the key isn’t configured.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 158
def cross_app_encoding_key
  Mmtrix::Agent.config[:encoding_key] or
    raise Mmtrix::Agent::CrossAppTracing::Error, "No encoding_key set."
end
cross_application_tracer_enabled?() click to toggle source
# File lib/mmtrix/agent/cross_app_tracing.rb, line 152
def cross_application_tracer_enabled?
  Mmtrix::Agent.config[:"cross_application_tracer.enabled"] || Mmtrix::Agent.config[:cross_application_tracing]
end
extract_appdata( response ) click to toggle source

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>
]
# File lib/mmtrix/agent/cross_app_tracing.rb, line 288
def extract_appdata( response )
  appdata = response[NR_APPDATA_HEADER] or
    raise Mmtrix::Agent::CrossAppTracing::Error,
      "Can't derive metrics for response: no #{NR_APPDATA_HEADER} header!"

  decoded_appdata = obfuscator.deobfuscate( appdata )
  decoded_appdata.set_encoding( ::Encoding::UTF_8 ) if
    decoded_appdata.respond_to?( :set_encoding )

  return Mmtrix::JSONWrapper.load( decoded_appdata )
end
finish_trace(state, t0, node, request, response) click to toggle source

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

The request must conform to the same interface described in the documentation for start_trace.

The response must respond to the following methods:

  • [](key) - Reads response headers.

  • to_hash - Converts response headers to a Hash

# File lib/mmtrix/agent/cross_app_tracing.rb, line 97
def finish_trace(state, t0, node, request, response)
  unless t0
    Mmtrix::Agent.logger.error("HTTP request trace finished without start time. This is probably an agent bug.")
    return
  end

  t1 = Time.now
  duration = t1.to_f - t0.to_f

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

      stats_engine.record_scoped_and_unscoped_metrics(
        state, scoped_metric, metrics, duration)

      # If we don't have node, something failed during start_trace so
      # the current node isn't the HTTP call it should have been.
      if node
        node.name = scoped_metric
        add_transaction_trace_parameters(request, response)
      end
    end
  ensure
    # If we have a node, always pop the traced method stack to avoid
    # an inconsistent state, which prevents tracing of whole transaction.
    if node
      stack = state.traced_method_stack
      stack.pop_frame(state, node, scoped_metric, t1)
    end
  end
rescue Mmtrix::Agent::CrossAppTracing::Error => err
  Mmtrix::Agent.logger.debug "while cross app tracing", err
rescue => err
  Mmtrix::Agent.logger.error "Uncaught exception while finishing an HTTP request trace", err
end
inject_request_headers(state, request) click to toggle source

Inject the X-Process header into the outgoing request.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 168
def inject_request_headers(state, request)
  cross_app_id = Mmtrix::Agent.config[:cross_process_id] or
    raise Mmtrix::Agent::CrossAppTracing::Error, "no cross app ID configured"

  state.is_cross_app_caller = true
  txn_guid = state.request_guid
  txn = state.current_transaction
  if txn
    trip_id   = txn.cat_trip_id(state)
    path_hash = txn.cat_path_hash(state)

    if txn.raw_synthetics_header
      request[NR_SYNTHETICS_HEADER] = txn.raw_synthetics_header
    end
  end
  txn_data  = Mmtrix::JSONWrapper.dump([txn_guid, false, trip_id, path_hash])

  request[NR_ID_HEADER]  = obfuscator.obfuscate(cross_app_id)
  request[NR_TXN_HEADER] = obfuscator.obfuscate(txn_data)

rescue Mmtrix::Agent::CrossAppTracing::Error => err
  Mmtrix::Agent.logger.debug "Not injecting x-process header", err
end
metrics_for( request, response ) click to toggle source

Return the set of metric names that correspond to the given request and response. response may be nil in the case that the request produced an error without ever receiving an HTTP response.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 214
def metrics_for( request, response )
  metrics = common_metrics( request )

  if response && response_is_crossapp?( response )
    begin
      metrics.concat metrics_for_crossapp_response( request, response )
    rescue => err
      # Fall back to regular metrics if there's a problem with x-process metrics
      Mmtrix::Agent.logger.debug "%p while fetching x-process metrics: %s" %
        [ err.class, err.message ]
      metrics.concat metrics_for_regular_request( request )
    end
  else
    metrics.concat metrics_for_regular_request( request )
  end

  return metrics
end
metrics_for_crossapp_response( request, response ) click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 263
def metrics_for_crossapp_response( request, response )
  xp_id, txn_name, _q_time, _r_time, _req_len, _ = extract_appdata( response )

  check_crossapp_id( xp_id )
  check_transaction_name( txn_name )

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

  return metrics
end
metrics_for_regular_request( request ) click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 303
def metrics_for_regular_request( request )
  metrics = []
  metrics << "External/#{request.host}/#{request.type}/#{request.method}"

  return metrics
end
obfuscator() click to toggle source
# File lib/mmtrix/agent/cross_app_tracing.rb, line 163
def obfuscator
  @obfuscator ||= Mmtrix::Agent::Obfuscator.new(cross_app_encoding_key)
end
response_is_crossapp?( response ) click to toggle source

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

# File lib/mmtrix/agent/cross_app_tracing.rb, line 251
def response_is_crossapp?( response )
  return false unless cross_app_enabled?
  unless response[NR_APPDATA_HEADER]
    return false
  end

  return true
end
start_trace(state, t0, request) click to toggle source

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

The request object passed in must respond to the following methods:

  • type - Return a String describing the underlying library being used

    to make the request (e.g. 'Net::HTTP' or 'Typhoeus')
  • host - Return a String with the hostname or IP of the host being

    communicated with.
  • method - Return a String with the HTTP method name for this request

  • [](key) - Lookup an HTTP request header by name

  • []=(key, val) - Set an HTTP request header by name

  • uri - Full URI of the request

This method returns the transaction node if it was sucessfully pushed.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 70
def start_trace(state, t0, request)
  inject_request_headers(state, request) if cross_app_enabled?
  stack = state.traced_method_stack
  node = stack.push_frame(state, :http_request, t0)

  return node
rescue => err
  Mmtrix::Agent.logger.error "Uncaught exception while tracing HTTP request", err
  return nil
rescue Exception => e
  Mmtrix::Agent.logger.debug "Unexpected exception raised while tracing HTTP request", e

  raise e
end
stats_engine() click to toggle source

Fetch a reference to the stats engine.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 312
def stats_engine
  Mmtrix::Agent.instance.stats_engine
end
tl_trace_http_request(request) { || ... } click to toggle source

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

See the documentation for start_trace for an explanation of what request should look like.

# File lib/mmtrix/agent/cross_app_tracing.rb, line 38
def tl_trace_http_request(request)
  state = Mmtrix::Agent::TransactionState.tl_get
  return yield unless state.is_execution_traced?

  # It's important to set t0 outside the ensured block, otherwise there's
  # a race condition if we raise after begin but before t0's set.
  t0 = Time.now
  begin
    node = start_trace(state, t0, request)
    response = yield
  ensure
    finish_trace(state, t0, node, request, response)
  end

  return response
end
transaction_sampler() click to toggle source
# File lib/mmtrix/agent/cross_app_tracing.rb, line 316
def transaction_sampler
  Mmtrix::Agent.instance.transaction_sampler
end
valid_cross_process_id?() click to toggle source
# File lib/mmtrix/agent/cross_app_tracing.rb, line 144
def valid_cross_process_id?
  Mmtrix::Agent.config[:cross_process_id] && Mmtrix::Agent.config[:cross_process_id].length > 0
end
valid_encoding_key?() click to toggle source
# File lib/mmtrix/agent/cross_app_tracing.rb, line 148
def valid_encoding_key?
  Mmtrix::Agent.config[:encoding_key] && Mmtrix::Agent.config[:encoding_key].length > 0
end