Class: NewRelic::Agent::CrossProcessMonitor

Inherits:
Object
  • Object
show all
Defined in:
lib/new_relic/agent/cross_process_monitoring.rb

Constant Summary collapse

THREAD_ID_KEY =

Because we aren’t in the right spot when our transaction actually starts, hold client_cross_process_id we get thread local until then.

:newrelic_client_cross_process_id
NEWRELIC_ID_HEADER_KEYS =
%w{X-NewRelic-ID HTTP_X_NEWRELIC_ID X_NEWRELIC_ID}
CONTENT_LENGTH_HEADER_KEYS =
%w{Content-Length HTTP_CONTENT_LENGTH CONTENT_LENGTH}

Instance Method Summary collapse

Constructor Details

#initialize(events = nil) ⇒ CrossProcessMonitor

Returns a new instance of CrossProcessMonitor.



8
9
10
11
12
13
14
15
16
17
18
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 8

def initialize(events = nil)
  # When we're starting up for real in the agent, we get passed the events
  # Other spots can pull from the agent, during startup the agent doesn't exist yet!
  events ||= Agent.instance.events
  @trusted_ids = []

  events.subscribe(:finished_configuring) do
    finish_setup(Agent.config)
    register_event_listeners
  end
end

Instance Method Details

#build_payload(timings, content_length) ⇒ Object



101
102
103
104
105
106
107
108
109
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 101

def build_payload(timings, content_length)

  # FIXME The transaction name might not be properly encoded.  use a json generator
  # For now we just handle quote characters by dropping them
  transaction_name = timings.transaction_name.gsub(/["']/, "")

  payload = %[["#{@cross_process_id}","#{transaction_name}",#{timings.queue_time_in_seconds},#{timings.app_time_in_seconds},#{content_length}] ]
  payload = obfuscate_with_key(payload)
end

#clear_client_cross_process_idObject



62
63
64
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 62

def clear_client_cross_process_id
  NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = nil
end

#client_cross_process_idObject



66
67
68
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 66

def client_cross_process_id
  NewRelic::Agent::AgentThread.current[THREAD_ID_KEY]
end

#content_length_from_request(request) ⇒ Object



144
145
146
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 144

def content_length_from_request(request)
  from_headers(request, CONTENT_LENGTH_HEADER_KEYS) || -1
end

#decode_with_key(text) ⇒ Object



130
131
132
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 130

def decode_with_key(text)
  encode_with_key(Base64.decode64(text))
end

#decoded_id(request) ⇒ Object



137
138
139
140
141
142
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 137

def decoded_id(request)
  encoded_id = from_headers(request, NEWRELIC_ID_HEADER_KEYS)
  return "" if encoded_id.nil?

  decode_with_key(encoded_id)
end

#finish_setup(config) ⇒ Object



20
21
22
23
24
25
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 20

def finish_setup(config)
  @cross_process_id = config[:cross_process_id]
  @encoding_key = config[:encoding_key]
  @encoding_bytes = get_bytes(@encoding_key) unless @encoding_key.nil?
  @trusted_ids = config[:trusted_account_ids] || []
end

#insert_response_header(request_headers, response_headers) ⇒ Object



70
71
72
73
74
75
76
77
78
79
80
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 70

def insert_response_header(request_headers, response_headers)
  unless client_cross_process_id.nil?
    timings = NewRelic::Agent::BrowserMonitoring.timings
    content_length = content_length_from_request(request_headers)

    set_response_headers(response_headers, timings, content_length)
    set_metrics(client_cross_process_id, timings)

    clear_client_cross_process_id
  end
end

#obfuscate_with_key(text) ⇒ Object



126
127
128
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 126

def obfuscate_with_key(text)
  Base64.encode64(encode_with_key(text)).chomp
end

#register_event_listenersObject

Expected sequence of events:

:before_call will save our cross process request id to the thread
:start_transaction will get called when a transaction starts up
:after_call will write our response headers/metrics and clean up the thread


31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 31

def register_event_listeners
  NewRelic::Agent.logger.debug("Wiring up Cross Process monitoring to events after finished configuring")

  events = Agent.instance.events
  events.subscribe(:before_call) do |env|
    save_client_cross_process_id(env)
  end

  events.subscribe(:start_transaction) do |name|
    set_transaction_custom_parameters
  end

  events.subscribe(:after_call) do |env, (status_code, headers, body)|
    insert_response_header(env, headers)
  end

  events.subscribe(:notice_error) do |_, options|
    set_error_custom_parameters(options)
  end
end

#save_client_cross_process_id(request_headers) ⇒ Object



56
57
58
59
60
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 56

def save_client_cross_process_id(request_headers)
  if should_process_request(request_headers)
    NewRelic::Agent::AgentThread.current[THREAD_ID_KEY] = decoded_id(request_headers)
  end
end

#set_error_custom_parameters(options) ⇒ Object



117
118
119
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 117

def set_error_custom_parameters(options)
  options[:client_cross_process_id] = client_cross_process_id unless client_cross_process_id.nil?
end

#set_metrics(id, timings) ⇒ Object



121
122
123
124
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 121

def set_metrics(id, timings)
  metric = NewRelic::Agent.instance.stats_engine.get_stats_no_scope("ClientApplication/#{id}/all")
  metric.record_data_point(timings.app_time_in_seconds)
end

#set_response_headers(response_headers, timings, content_length) ⇒ Object



97
98
99
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 97

def set_response_headers(response_headers, timings, content_length)
  response_headers['X-NewRelic-App-Data'] = build_payload(timings, content_length)
end

#set_transaction_custom_parametersObject



111
112
113
114
115
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 111

def set_transaction_custom_parameters
  # We expect to get the before call to set the id (if we have it) before
  # this, and then write our custom parameter when the transaction starts
  NewRelic::Agent.add_custom_parameters(:client_cross_process_id => client_cross_process_id) unless client_cross_process_id.nil?
end

#should_process_request(request_headers) ⇒ Object



82
83
84
85
86
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 82

def should_process_request(request_headers)
  return Agent.config[:'cross_process.enabled'] &&
      @cross_process_id &&
      trusts?(request_headers)
end

#trusts?(request) ⇒ Boolean

Expects an ID of format “12#345”, and will only accept that!

Returns:

  • (Boolean)


89
90
91
92
93
94
95
# File 'lib/new_relic/agent/cross_process_monitoring.rb', line 89

def trusts?(request)
  id = decoded_id(request)
  split_id = id.match(/(\d+)#\d+/)
  return false if split_id.nil?

  @trusted_ids.include?(split_id.captures.first.to_i)
end