Class: NewRelic::Agent::NewRelicService

Inherits:
Object
  • Object
show all
Defined in:
lib/new_relic/agent/new_relic_service.rb,
lib/new_relic/agent/new_relic_service/encoders.rb,
lib/new_relic/agent/new_relic_service/marshaller.rb,
lib/new_relic/agent/new_relic_service/json_marshaller.rb,
lib/new_relic/agent/new_relic_service/security_policy_settings.rb

Defined Under Namespace

Modules: Encoders, SecurityPolicySettings Classes: JsonMarshaller, Marshaller

Constant Summary collapse

PROTOCOL_VERSION =

Specifies the version of the agent’s communication protocol with the NewRelic hosted site.

17
CONNECTION_ERRORS =

These include Errno connection errors, and all indicate that the underlying TCP connection may be in a bad state.

[Net::OpenTimeout, Net::ReadTimeout, EOFError, SystemCallError, SocketError]
MAX_ATTEMPTS =

The maximum number of times to attempt an HTTP request

2
MIN_BYTE_SIZE_TO_COMPRESS =

Don’t perform compression on the payload unless its uncompressed size is greater than or equal to this number of bytes. In testing with Ruby 2.2 - 3.1, we determined an absolute minimum value for ASCII to be 535 bytes to obtain at least a 10% savings in size. It is recommended that this value be kept above that 535 number. It is also important to consider the CPU cost involved with performing compression and to find a balance between CPU cycles spent and bandwidth saved. A good reasonable default here is 2048 bytes, which is a tried and true Apache Tomcat default (as of v8.5.78)

2048

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(license_key = nil, collector = control.server) ⇒ NewRelicService

Returns a new instance of NewRelicService.



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

def initialize(license_key = nil, collector = control.server)
  @license_key = license_key
  @collector = collector
  @configured_collector = collector
  @request_timeout = Agent.config[:timeout]
  @ssl_cert_store = nil
  @in_session = nil
  @agent_id = nil
  @shared_tcp_connection = nil
  @request_headers_map = nil
  reset_remote_method_uris

  prep_audit_logger
  prep_marshaller
end

Instance Attribute Details

#agent_idObject

Returns the value of attribute agent_id.



44
45
46
# File 'lib/new_relic/agent/new_relic_service.rb', line 44

def agent_id
  @agent_id
end

#collectorObject (readonly)

Returns the value of attribute collector.



44
45
46
# File 'lib/new_relic/agent/new_relic_service.rb', line 44

def collector
  @collector
end

#marshallerObject (readonly)

Returns the value of attribute marshaller.



44
45
46
# File 'lib/new_relic/agent/new_relic_service.rb', line 44

def marshaller
  @marshaller
end

#request_timeoutObject

Returns the value of attribute request_timeout.



43
44
45
# File 'lib/new_relic/agent/new_relic_service.rb', line 43

def request_timeout
  @request_timeout
end

Instance Method Details

#agent_command_results(results) ⇒ Object



185
186
187
# File 'lib/new_relic/agent/new_relic_service.rb', line 185

def agent_command_results(results)
  invoke_remote(:agent_command_results, [@agent_id, results])
end

#analytic_event_data(data) ⇒ Object



189
190
191
192
193
# File 'lib/new_relic/agent/new_relic_service.rb', line 189

def analytic_event_data(data)
  _, items = data
  invoke_remote(:analytic_event_data, [@agent_id, *data],
    :item_count => items.size)
end

#build_metric_data_array(stats_hash) ⇒ Object

The collector wants to receive metric data in a format that’s different from how we store it internally, so this method handles the translation.



134
135
136
137
138
139
140
141
142
143
# File 'lib/new_relic/agent/new_relic_service.rb', line 134

def build_metric_data_array(stats_hash)
  metric_data_array = []
  stats_hash.each do |metric_spec, stats|
    # Omit empty stats as an optimization
    unless stats.is_reset?
      metric_data_array << NewRelic::MetricData.new(metric_spec, stats)
    end
  end
  metric_data_array
end

#cert_file_pathObject

The path to the certificate file used to verify the SSL connection if verify_peer is enabled



385
386
387
388
389
390
# File 'lib/new_relic/agent/new_relic_service.rb', line 385

def cert_file_path
  if path_override = NewRelic::Agent.config[:ca_bundle_path]
    NewRelic::Agent.logger.warn("Couldn't find CA bundle from configured ca_bundle_path: #{path_override}") unless File.exist?(path_override)
    path_override
  end
end

#close_shared_connectionObject



277
278
279
280
281
282
283
# File 'lib/new_relic/agent/new_relic_service.rb', line 277

def close_shared_connection
  if @shared_tcp_connection
    ::NewRelic::Agent.logger.debug("Closing shared TCP connection to #{@shared_tcp_connection.address}:#{@shared_tcp_connection.port}")
    @shared_tcp_connection.finish if @shared_tcp_connection.started?
    @shared_tcp_connection = nil
  end
end

#compress_request_if_needed(data, endpoint) ⇒ Object



222
223
224
225
226
227
228
229
230
231
232
233
234
# File 'lib/new_relic/agent/new_relic_service.rb', line 222

def compress_request_if_needed(data, endpoint)
  encoding = 'identity'
  if data.size >= MIN_BYTE_SIZE_TO_COMPRESS
    encoding = Agent.config[:compressed_content_encoding]
    data = if encoding == 'deflate'
      Encoders::Compressed::Deflate.encode(data)
    else
      Encoders::Compressed::Gzip.encode(data)
    end
  end
  check_post_size(data, endpoint)
  [data, encoding]
end

#connect(settings = {}) ⇒ Object



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/new_relic/agent/new_relic_service.rb', line 88

def connect(settings = {})
  @request_headers_map = nil
  security_policies = nil
  if response = preconnect
    if host = response['redirect_host']
      @collector = NewRelic::Control.instance.server_from_host(host)
    end
    if policies = response['security_policies']
      security_policies = SecurityPolicySettings.preliminary_settings(policies)
      settings.merge!(security_policies)
    end
  end
  response = invoke_remote(:connect, [settings])
  @request_headers_map = response['request_headers_map']
  self.agent_id = response['agent_run_id']
  response.merge!(security_policies) if security_policies
  response
end

#create_and_start_http_connectionObject



374
375
376
377
378
379
380
381
# File 'lib/new_relic/agent/new_relic_service.rb', line 374

def create_and_start_http_connection
  conn = create_http_connection
  start_connection(conn)
  conn
rescue Net::OpenTimeout
  ::NewRelic::Agent.logger.info('Timed out while attempting to connect. For SSL issues, you may need to install system-level CA Certificates to be used by Net::HTTP.')
  raise
end

#create_http_connectionObject



348
349
350
351
352
353
354
355
# File 'lib/new_relic/agent/new_relic_service.rb', line 348

def create_http_connection
  conn = prep_connection
  setup_connection_for_ssl(conn)
  setup_connection_timeouts(conn)

  ::NewRelic::Agent.logger.debug("Created net/http handle to #{conn.address}:#{conn.port}")
  conn
end

#custom_event_data(data) ⇒ Object



195
196
197
198
199
# File 'lib/new_relic/agent/new_relic_service.rb', line 195

def custom_event_data(data)
  _, items = data
  invoke_remote(:custom_event_data, [@agent_id, *data],
    :item_count => items.size)
end

#error_data(unsent_errors) ⇒ Object



159
160
161
162
163
164
165
# File 'lib/new_relic/agent/new_relic_service.rb', line 159

def error_data(unsent_errors)
  # let the serverless handler handle serialization
  return NewRelic::Agent.agent.serverless_handler.error_data(unsent_errors) if NewRelic::Agent.agent.serverless?

  invoke_remote(:error_data, [@agent_id, unsent_errors],
    :item_count => unsent_errors.size)
end

#error_event_data(data) ⇒ Object



206
207
208
209
210
211
212
# File 'lib/new_relic/agent/new_relic_service.rb', line 206

def error_event_data(data)
  , items = data
  response = invoke_remote(:error_event_data, [@agent_id, *data], :item_count => items.size)
  NewRelic::Agent.record_metric('Supportability/Events/TransactionError/Sent', :count => items.size)
  NewRelic::Agent.record_metric('Supportability/Events/TransactionError/Seen', :count => [:events_seen])
  response
end

#establish_shared_connectionObject



272
273
274
275
# File 'lib/new_relic/agent/new_relic_service.rb', line 272

def establish_shared_connection
  @shared_tcp_connection ||= create_and_start_http_connection
  @shared_tcp_connection
end

#force_restartObject



128
129
130
# File 'lib/new_relic/agent/new_relic_service.rb', line 128

def force_restart
  close_shared_connection
end

#get_agent_commandsObject



181
182
183
# File 'lib/new_relic/agent/new_relic_service.rb', line 181

def get_agent_commands
  invoke_remote(:get_agent_commands, [@agent_id])
end

#has_shared_connection?Boolean

Returns:

  • (Boolean)


285
286
287
# File 'lib/new_relic/agent/new_relic_service.rb', line 285

def has_shared_connection?
  !@shared_tcp_connection.nil?
end

#http_connectionObject

Return a Net::HTTP connection object to make a call to the collector. We’ll reuse the same handle for cases where we’re using keep-alive, or otherwise create a new one.



303
304
305
306
307
308
309
# File 'lib/new_relic/agent/new_relic_service.rb', line 303

def http_connection
  if @in_session
    establish_shared_connection
  else
    create_http_connection
  end
end

#log_event_data(data) ⇒ Object



201
202
203
204
# File 'lib/new_relic/agent/new_relic_service.rb', line 201

def log_event_data(data)
  payload, size = LogEventAggregator.payload_to_melt_format(data)
  invoke_remote(:log_event_data, payload, :item_count => size)
end

#metric_data(stats_hash) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/new_relic/agent/new_relic_service.rb', line 145

def metric_data(stats_hash)
  # let the serverless handler handle serialization
  return NewRelic::Agent.agent.serverless_handler.metric_data(stats_hash) if NewRelic::Agent.agent.serverless?

  timeslice_start = stats_hash.started_at
  timeslice_end = stats_hash.harvested_at || Process.clock_gettime(Process::CLOCK_REALTIME)
  metric_data_array = build_metric_data_array(stats_hash)
  invoke_remote(
    :metric_data,
    [@agent_id, timeslice_start, timeslice_end, metric_data_array],
    :item_count => metric_data_array.size
  )
end

#preconnectObject



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/new_relic/agent/new_relic_service.rb', line 107

def preconnect
  token = Agent.config[:security_policies_token]

  if token && !token.empty?
    response = invoke_remote(:preconnect, [{'security_policies_token' => token, 'high_security' => false}])

    validator = SecurityPolicySettings::Validator.new(response)
    validator.validate_matching_agent_config!

    response
  elsif Agent.config[:high_security]
    invoke_remote(:preconnect, [{'high_security' => true}])
  else
    invoke_remote(:preconnect, [{'high_security' => false}])
  end
end

#prep_audit_loggerObject



62
63
64
65
66
67
# File 'lib/new_relic/agent/new_relic_service.rb', line 62

def prep_audit_logger
  @audit_logger = ::NewRelic::Agent::AuditLogger.new
  Agent.config.register_callback(:'audit_log.enabled') do |enabled|
    @audit_logger.enabled = enabled
  end
end

#prep_connectionObject



357
358
359
360
361
362
# File 'lib/new_relic/agent/new_relic_service.rb', line 357

def prep_connection
  return Net::HTTP.new(@collector.name, @collector.port) unless Agent.config[:proxy_host]

  ::NewRelic::Agent.logger.debug("Using proxy server #{Agent.config[:proxy_host]}:#{Agent.config[:proxy_port]}")
  prep_proxy_connection
end

#prep_marshallerObject



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

def prep_marshaller
  Agent.config.register_callback(:marshaller) do |marshaller|
    if marshaller != 'json'
      ::NewRelic::Agent.logger.warn("Non-JSON marshaller '#{marshaller}' requested but not supported, using " \
        'JSON marshaller instead. pruby marshalling has been removed as of version 3.14.0.')
    end

    @marshaller = JsonMarshaller.new
  end
end

#prep_proxy_connectionObject



364
365
366
367
368
369
370
371
372
# File 'lib/new_relic/agent/new_relic_service.rb', line 364

def prep_proxy_connection
  proxy = Net::HTTP::Proxy(
    Agent.config[:proxy_host],
    Agent.config[:proxy_port],
    Agent.config[:proxy_user],
    Agent.config[:proxy_pass]
  )
  proxy.new(@collector.name, @collector.port)
end

#profile_data(profile) ⇒ Object



177
178
179
# File 'lib/new_relic/agent/new_relic_service.rb', line 177

def profile_data(profile)
  invoke_remote(:profile_data, [@agent_id, profile], :skip_normalization => true) || ''
end

#session(&block) ⇒ Object

One session with the service’s endpoint. In this case the session represents 1 tcp connection which may transmit multiple HTTP requests via keep-alive.

Raises:

  • (ArgumentError)


239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/new_relic/agent/new_relic_service.rb', line 239

def session(&block)
  raise ArgumentError, "#{self.class}#shared_connection must be passed a block" unless block

  begin
    t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
    @in_session = true
    if NewRelic::Agent.config[:aggressive_keepalive]
      session_with_keepalive(&block)
    else
      session_without_keepalive(&block)
    end
  rescue *CONNECTION_ERRORS => e
    elapsed = Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0
    raise NewRelic::Agent::ServerConnectionException, "Recoverable error connecting to #{@collector} after #{elapsed} seconds: #{e}"
  ensure
    @in_session = false
  end
end

#session_with_keepalive(&block) ⇒ Object



258
259
260
261
# File 'lib/new_relic/agent/new_relic_service.rb', line 258

def session_with_keepalive(&block)
  establish_shared_connection
  yield
end

#session_without_keepalive(&block) ⇒ Object



263
264
265
266
267
268
269
270
# File 'lib/new_relic/agent/new_relic_service.rb', line 263

def session_without_keepalive(&block)
  begin
    establish_shared_connection
    yield
  ensure
    close_shared_connection
  end
end

#set_cert_store(conn) ⇒ Object



323
324
325
326
327
328
329
# File 'lib/new_relic/agent/new_relic_service.rb', line 323

def set_cert_store(conn)
  if NewRelic::Agent.config[:ca_bundle_path]
    conn.cert_store = ssl_cert_store
  else
    ::NewRelic::Agent.logger.debug('Using default security certificates')
  end
end

#setup_connection_for_ssl(conn) ⇒ Object



311
312
313
314
315
316
317
318
319
320
321
# File 'lib/new_relic/agent/new_relic_service.rb', line 311

def setup_connection_for_ssl(conn)
  # Jruby 1.6.8 requires a gem for full ssl support and will throw
  # an error when use_ssl=(true) is called and jruby-openssl isn't
  # installed
  conn.use_ssl = true
  conn.verify_mode = OpenSSL::SSL::VERIFY_PEER
  set_cert_store(conn)
rescue StandardError, LoadError
  msg = 'SSL is not available in the environment; please install SSL support.'
  raise UnrecoverableAgentException.new(msg)
end

#setup_connection_timeouts(conn) ⇒ Object



336
337
338
339
340
341
342
343
344
345
346
# File 'lib/new_relic/agent/new_relic_service.rb', line 336

def setup_connection_timeouts(conn)
  conn.open_timeout = @request_timeout
  conn.read_timeout = @request_timeout
  # TODO: MAJOR VERSION - #write_timeout= requires Ruby 2.6+, so remove
  #       the conditional check once support for Ruby 2.5 is dropped
  conn.write_timeout = @request_timeout if conn.respond_to?(:write_timeout=)

  if conn.respond_to?(:keep_alive_timeout) && NewRelic::Agent.config[:aggressive_keepalive]
    conn.keep_alive_timeout = NewRelic::Agent.config[:keep_alive_timeout]
  end
end

#shutdown(time) ⇒ Object



124
125
126
# File 'lib/new_relic/agent/new_relic_service.rb', line 124

def shutdown(time)
  invoke_remote(:shutdown, [@agent_id, time.to_i]) if @agent_id
end

#span_event_data(data) ⇒ Object



214
215
216
217
218
219
220
# File 'lib/new_relic/agent/new_relic_service.rb', line 214

def span_event_data(data)
  , items = data
  response = invoke_remote(:span_event_data, [@agent_id, *data], :item_count => items.size)
  NewRelic::Agent.record_metric('Supportability/Events/SpanEvents/Sent', :count => items.size)
  NewRelic::Agent.record_metric('Supportability/Events/SpanEvents/Seen', :count => [:events_seen])
  response
end

#sql_trace_data(sql_traces) ⇒ Object



172
173
174
175
# File 'lib/new_relic/agent/new_relic_service.rb', line 172

def sql_trace_data(sql_traces)
  invoke_remote(:sql_trace_data, [sql_traces],
    :item_count => sql_traces.size)
end

#ssl_cert_storeObject



289
290
291
292
293
294
295
296
297
298
# File 'lib/new_relic/agent/new_relic_service.rb', line 289

def ssl_cert_store
  path = cert_file_path
  if !@ssl_cert_store || path != @cached_cert_store_path
    ::NewRelic::Agent.logger.debug("Creating SSL certificate store from file at #{path}")
    @ssl_cert_store = OpenSSL::X509::Store.new
    @ssl_cert_store.add_file(path)
    @cached_cert_store_path = path
  end
  @ssl_cert_store
end

#start_connection(conn) ⇒ Object



331
332
333
334
# File 'lib/new_relic/agent/new_relic_service.rb', line 331

def start_connection(conn)
  NewRelic::Agent.logger.debug("Opening TCP connection to #{conn.address}:#{conn.port}")
  conn.start
end

#transaction_sample_data(traces) ⇒ Object



167
168
169
170
# File 'lib/new_relic/agent/new_relic_service.rb', line 167

def transaction_sample_data(traces)
  invoke_remote(:transaction_sample_data, [@agent_id, traces],
    :item_count => traces.size)
end

#valid_to_marshal?(data) ⇒ Boolean

Returns:

  • (Boolean)


392
393
394
395
396
397
398
# File 'lib/new_relic/agent/new_relic_service.rb', line 392

def valid_to_marshal?(data)
  @marshaller.dump(data)
  true
rescue StandardError, SystemStackError => e
  NewRelic::Agent.logger.warn('Unable to marshal environment report on connect.', e)
  false
end