Class: NewRelic::Agent::Transaction

Inherits:
Object
  • Object
show all
Includes:
Pop
Defined in:
lib/new_relic/agent/transaction.rb,
lib/new_relic/agent/transaction/pop.rb,
lib/new_relic/agent/transaction/xray_sample_buffer.rb,
lib/new_relic/agent/transaction/slowest_sample_buffer.rb,
lib/new_relic/agent/transaction/transaction_sample_buffer.rb,
lib/new_relic/agent/transaction/force_persist_sample_buffer.rb,
lib/new_relic/agent/transaction/developer_mode_sample_buffer.rb

Defined Under Namespace

Modules: Pop Classes: DeveloperModeSampleBuffer, ForcePersistSampleBuffer, SlowestSampleBuffer, TransactionSampleBuffer, XraySampleBuffer

Constant Summary collapse

OVERVIEW_SPECS =
[
  [:webDuration,      MetricSpec.new('HttpDispatcher')],
  [:queueDuration,    MetricSpec.new('WebFrontend/QueueTime')],
  [:externalDuration, MetricSpec.new('External/allWeb')],
  [:databaseDuration, MetricSpec.new('ActiveRecord/all')],
  [:gcCumulative,     MetricSpec.new("GC/cumulative")],
  [:memcacheDuration, MetricSpec.new('Memcache/allWeb')]
]
APDEX_METRIC_SPEC =
NewRelic::MetricSpec.new('Apdex').freeze
@@java_classes_loaded =
false

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Pop

#cpu_burn, #current_stack_metric, #jruby_cpu_burn, #log_underflow, #normal_cpu_burn, #record_jruby_cpu_burn, #record_transaction_cpu, #traced?

Constructor Details

#initialize(type = :controller, options = {}) ⇒ Transaction

Returns a new instance of Transaction.



96
97
98
99
100
101
102
103
104
105
106
107
108
# File 'lib/new_relic/agent/transaction.rb', line 96

def initialize(type=:controller, options={})
  @type = type
  @start_time = Time.now
  @apdex_start = @start_time
  @jruby_cpu_start = jruby_cpu_time
  @process_cpu_start = process_cpu
  @filtered_params = options[:filtered_params] || {}
  @force_flag = options[:force]
  @request = options[:request]
  @exceptions = {}
  @stats_hash = StatsHash.new
  TransactionState.get.transaction = self
end

Instance Attribute Details

#apdex_startObject

A Time instance used for calculating the apdex score, which



21
22
23
# File 'lib/new_relic/agent/transaction.rb', line 21

def apdex_start
  @apdex_start
end

#database_metric_nameObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def database_metric_name
  @database_metric_name
end

#depthObject (readonly)

Returns the value of attribute depth.



94
95
96
# File 'lib/new_relic/agent/transaction.rb', line 94

def depth
  @depth
end

#exceptionsObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def exceptions
  @exceptions
end

#filtered_paramsObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def filtered_params
  @filtered_params
end

#force_flagObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def force_flag
  @force_flag
end

#jruby_cpu_startObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def jruby_cpu_start
  @jruby_cpu_start
end

#nameObject

Returns the value of attribute name.



26
27
28
# File 'lib/new_relic/agent/transaction.rb', line 26

def name
  @name
end

#process_cpu_startObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def process_cpu_start
  @process_cpu_start
end

#requestObject

Give the current transaction a request context. Use this to get the URI and referer. The request is interpreted loosely as a Rack::Request or an ActionController::AbstractRequest.



32
33
34
# File 'lib/new_relic/agent/transaction.rb', line 32

def request
  @request
end

#start_timeObject

A Time instance for the start time, never nil



20
21
22
# File 'lib/new_relic/agent/transaction.rb', line 20

def start_time
  @start_time
end

#stats_hashObject (readonly)

Returns the value of attribute stats_hash.



27
28
29
# File 'lib/new_relic/agent/transaction.rb', line 27

def stats_hash
  @stats_hash
end

#typeObject

might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



24
25
26
# File 'lib/new_relic/agent/transaction.rb', line 24

def type
  @type
end

Class Method Details

.abort_transaction!Object

Indicate that you don’t want to keep the currently saved transaction information



164
165
166
# File 'lib/new_relic/agent/transaction.rb', line 164

def self.abort_transaction!
  current.abort_transaction! if current
end

.add_custom_parameters(p) ⇒ Object

Add context parameters to the transaction. This information will be passed in to errors and transaction traces. Keys and Values should be strings, numbers or date/times.



292
293
294
# File 'lib/new_relic/agent/transaction.rb', line 292

def self.add_custom_parameters(p)
  current.add_custom_parameters(p) if current
end

.agentObject



74
75
76
# File 'lib/new_relic/agent/transaction.rb', line 74

def self.agent
  NewRelic::Agent.instance
end

.apdex_bucket(duration, failed, apdex_t) ⇒ Object



418
419
420
421
422
423
424
425
426
427
428
429
# File 'lib/new_relic/agent/transaction.rb', line 418

def self.apdex_bucket(duration, failed, apdex_t)
  case
  when failed
    :apdex_f
  when duration <= apdex_t
    :apdex_s
  when duration <= 4 * apdex_t
    :apdex_t
  else
    :apdex_f
  end
end

.currentObject

Return the currently active transaction, or nil.



35
36
37
# File 'lib/new_relic/agent/transaction.rb', line 35

def self.current
  self.stack.last
end

.custom_parametersObject



296
297
298
# File 'lib/new_relic/agent/transaction.rb', line 296

def self.custom_parameters
  (current && current.custom_parameters) ? current.custom_parameters : {}
end

.database_metric_nameObject

This is the name of the model currently assigned to database measurements, overriding the default.



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

def self.database_metric_name
  current && current.database_metric_name
end

.freeze_nameObject



78
79
80
# File 'lib/new_relic/agent/transaction.rb', line 78

def self.freeze_name
  self.current && self.current.freeze_name
end

.in_transaction?Boolean

Returns:

  • (Boolean)


60
61
62
# File 'lib/new_relic/agent/transaction.rb', line 60

def self.in_transaction?
  !self.stack.empty?
end

.notice_error(e, options = {}) ⇒ Object

If we have an active transaction, notice the error and increment the error metric. Options:

  • :request => Request object to get the uri and referer

  • :uri => The request path, minus any request params or query string.

  • :referer => The URI of the referer

  • :metric => The metric name associated with the transaction

  • :request_params => Request parameters, already filtered if necessary

  • :custom_params => Custom parameters

Anything left over is treated as custom params



265
266
267
268
269
270
271
272
273
274
275
276
# File 'lib/new_relic/agent/transaction.rb', line 265

def self.notice_error(e, options={})
  request = options.delete(:request)
  if request
    options[:referer] = referer_from_request(request)
    options[:uri] = uri_from_request(request)
  end
  if current
    current.notice_error(e, options)
  else
    agent.error_collector.notice_error(e, options)
  end
end

.parentObject



39
40
41
# File 'lib/new_relic/agent/transaction.rb', line 39

def self.parent
  self.stack[-2]
end

.record_apdex(end_time, is_error) ⇒ Object



414
415
416
# File 'lib/new_relic/agent/transaction.rb', line 414

def self.record_apdex(end_time, is_error)
  current && current.record_apdex(end_time, is_error)
end

.recording_web_transaction?Boolean

Returns truthy if the current in-progress transaction is considered a a web transaction (as opposed to, e.g., a background transaction).

Returns:

  • (Boolean)


380
381
382
# File 'lib/new_relic/agent/transaction.rb', line 380

def self.recording_web_transaction?
  self.current && self.current.recording_web_transaction?
end

.refererObject



70
71
72
# File 'lib/new_relic/agent/transaction.rb', line 70

def self.referer
  current && current.referer
end

.referer_from_request(request) ⇒ Object

Make a safe attempt to get the referer from a request object, generally successful when it’s a Rack request.



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

def self.referer_from_request(request)
  if request && request.respond_to?(:referer)
    request.referer.to_s.split('?').first
  end
end

.set_user_attributes(attributes) ⇒ Object



300
301
302
# File 'lib/new_relic/agent/transaction.rb', line 300

def self.set_user_attributes(attributes)
  current.set_user_attributes(attributes) if current
end

.stackObject



56
57
58
# File 'lib/new_relic/agent/transaction.rb', line 56

def self.stack
  TransactionState.get.current_transaction_stack
end

.start(transaction_type, options = {}) ⇒ Object



43
44
45
46
47
48
# File 'lib/new_relic/agent/transaction.rb', line 43

def self.start(transaction_type, options={})
  txn = Transaction.new(transaction_type, options)
  txn.start(transaction_type)
  self.stack.push(txn)
  return txn
end

.stop(metric_name = nil, end_time = Time.now) ⇒ Object



50
51
52
53
54
# File 'lib/new_relic/agent/transaction.rb', line 50

def self.stop(metric_name=nil, end_time=Time.now)
  txn = self.stack.last
  txn.stop(metric_name, end_time) if txn
  return self.stack.pop
end

.transaction_type_is_web?(type) ⇒ Boolean

Returns:

  • (Boolean)


384
385
386
# File 'lib/new_relic/agent/transaction.rb', line 384

def self.transaction_type_is_web?(type)
  [:controller, :uri, :rack, :sinatra].include?(type)
end

.uri_from_request(request) ⇒ Object

Make a safe attempt to get the URI, without the host and query string.



401
402
403
404
405
406
407
408
409
410
# File 'lib/new_relic/agent/transaction.rb', line 401

def self.uri_from_request(request)
  approximate_uri = case
                    when request.respond_to?(:fullpath) then request.fullpath
                    when request.respond_to?(:path) then request.path
                    when request.respond_to?(:request_uri) then request.request_uri
                    when request.respond_to?(:uri) then request.uri
                    when request.respond_to?(:url) then request.url
                    end
  return approximate_uri[%r{^(https?://.*?)?(/[^?]*)}, 2] || '/' if approximate_uri # '
end

.user_attributesObject



304
305
306
# File 'lib/new_relic/agent/transaction.rb', line 304

def self.user_attributes
  (current) ? current.user_attributes : {}
end

Instance Method Details

#abort_transaction!Object

Call this to ensure that the current transaction is not saved



179
180
181
# File 'lib/new_relic/agent/transaction.rb', line 179

def abort_transaction!
  transaction_sampler.ignore_transaction
end

#add_custom_parameters(p) ⇒ Object



370
371
372
# File 'lib/new_relic/agent/transaction.rb', line 370

def add_custom_parameters(p)
  custom_parameters.merge!(p)
end

#apdex_tObject



326
327
328
# File 'lib/new_relic/agent/transaction.rb', line 326

def apdex_t
  transaction_specific_apdex_t || Agent.config[:apdex_t]
end

#custom_parametersObject



358
359
360
# File 'lib/new_relic/agent/transaction.rb', line 358

def custom_parameters
  @custom_parameters ||= {}
end

#freeze_nameObject



126
127
128
129
130
# File 'lib/new_relic/agent/transaction.rb', line 126

def freeze_name
  return if name_frozen?
  @name = NewRelic::Agent.instance.transaction_rules.rename(@name)
  @name_frozen = true
end

#has_parent?Boolean

Returns:

  • (Boolean)


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

def has_parent?
  self.class.stack.size > 1
end

#merge_stats_hashObject



225
226
227
228
# File 'lib/new_relic/agent/transaction.rb', line 225

def merge_stats_hash
  stats_hash.resolve_scopes!(@name)
  NewRelic::Agent.instance.stats_engine.merge!(stats_hash)
end

#name_frozen?Boolean

Returns:

  • (Boolean)


132
133
134
# File 'lib/new_relic/agent/transaction.rb', line 132

def name_frozen?
  @name_frozen
end

#name_set?Boolean

Returns:

  • (Boolean)


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

def name_set?
  @name && @name != NewRelic::Agent::UNKNOWN_METRIC
end

#notice_error(e, options = {}) ⇒ Object

Do not call this. Invoke the class method instead.



279
280
281
282
283
284
285
286
287
288
# File 'lib/new_relic/agent/transaction.rb', line 279

def notice_error(e, options={}) # :nodoc:
  params = custom_parameters
  options[:referer] = referer if referer
  options[:request_params] = filtered_params if filtered_params
  options[:uri] = uri if uri
  options.merge!(custom_parameters)
  if !@exceptions.keys.include?(e)
    @exceptions[e] = options
  end
end

#noticed_error_idsObject



110
111
112
# File 'lib/new_relic/agent/transaction.rb', line 110

def noticed_error_ids
  @noticed_error_ids ||= []
end

#parentObject



136
137
138
# File 'lib/new_relic/agent/transaction.rb', line 136

def parent
  has_parent? && self.class.stack[-2]
end

#queue_timeObject



366
367
368
# File 'lib/new_relic/agent/transaction.rb', line 366

def queue_time
  @apdex_start ? @start_time - @apdex_start : 0
end

#record_apdex(end_time = Time.now, is_error = nil) ⇒ Object



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
# File 'lib/new_relic/agent/transaction.rb', line 310

def record_apdex(end_time=Time.now, is_error=nil)
  return unless recording_web_transaction? && NewRelic::Agent.is_execution_traced?

  freeze_name
  action_duration = end_time - start_time
  total_duration  = end_time - apdex_start
  is_error = is_error.nil? ? !exceptions.empty? : is_error

  apdex_bucket_global = self.class.apdex_bucket(total_duration,  is_error, apdex_t)
  apdex_bucket_txn    = self.class.apdex_bucket(action_duration, is_error, apdex_t)

  @stats_hash.record(APDEX_METRIC_SPEC, apdex_bucket_global, apdex_t)
  txn_apdex_metric = NewRelic::MetricSpec.new(@name.gsub(/^[^\/]+\//, 'Apdex/'))
  @stats_hash.record(txn_apdex_metric, apdex_bucket_txn, apdex_t)
end

#record_exceptionsObject



230
231
232
233
234
235
# File 'lib/new_relic/agent/transaction.rb', line 230

def record_exceptions
  @exceptions.each do |exception, options|
    options[:metric] = @name
    agent.error_collector.notice_error(exception, options)
  end
end

#recording_web_transaction?Boolean

Returns:

  • (Boolean)


388
389
390
# File 'lib/new_relic/agent/transaction.rb', line 388

def recording_web_transaction?
  self.class.transaction_type_is_web?(@type)
end

#refererObject

For the current web transaction, return the full referer, minus the host string, or nil.



174
175
176
# File 'lib/new_relic/agent/transaction.rb', line 174

def referer
  @referer ||= self.class.referer_from_request(@request)
end

#root?Boolean

Returns:

  • (Boolean)


140
141
142
# File 'lib/new_relic/agent/transaction.rb', line 140

def root?
  self.class.stack.size == 1
end

#send_transaction_finished_event(start_time, end_time, overview_metrics) ⇒ Object



214
215
216
217
218
219
220
221
222
223
# File 'lib/new_relic/agent/transaction.rb', line 214

def send_transaction_finished_event(start_time, end_time, overview_metrics)
  payload = {
    :name             => @name,
    :type             => @type,
    :start_timestamp  => start_time.to_f,
    :duration         => end_time.to_f - start_time.to_f,
    :overview_metrics => overview_metrics
  }
  agent.events.notify(:transaction_finished, payload)
end

#set_user_attributes(attributes) ⇒ Object



374
375
376
# File 'lib/new_relic/agent/transaction.rb', line 374

def set_user_attributes(attributes)
  user_attributes.merge!(attributes)
end

#start(transaction_type) ⇒ Object

Indicate that we are entering a measured controller action or task. Make sure you unwind every push with a pop call.



150
151
152
153
154
155
156
157
158
159
160
# File 'lib/new_relic/agent/transaction.rb', line 150

def start(transaction_type)
  @name = NewRelic::Agent::UNKNOWN_METRIC

  transaction_sampler.notice_first_scope_push(start_time)
  sql_sampler.notice_first_scope_push(start_time)

  NewRelic::Agent::StatsEngine::GCProfiler.init
  agent.stats_engine.start_transaction
  transaction_sampler.notice_transaction(uri, filtered_params)
  sql_sampler.notice_transaction(uri, filtered_params)
end

#stop(fallback_name = ::NewRelic::Agent::UNKNOWN_METRIC, end_time = Time.now) ⇒ Object

Unwind one stack level. It knows if it’s back at the outermost caller and does the appropriate wrapup of the context.



186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
# File 'lib/new_relic/agent/transaction.rb', line 186

def stop(fallback_name=::NewRelic::Agent::UNKNOWN_METRIC, end_time=Time.now)
  @name = fallback_name unless name_set? || name_frozen?
  freeze_name
  log_underflow if @type.nil?

  # these record metrics so need to be done before merging stats
  if self.root?
    # this one records metrics and wants to happen
    # before the transaction sampler is finished
    if traced?
      record_transaction_cpu
      gc_time = NewRelic::Agent::StatsEngine::GCProfiler.capture
    end
    @transaction_trace = transaction_sampler.notice_scope_empty(self, Time.now, gc_time)
    sql_sampler.notice_scope_empty(@name)
    overview_metrics = transaction_overview_metrics
  end

  record_exceptions
  merge_stats_hash

  # these tear everything down so need to be done after merging stats
  if self.root?
    send_transaction_finished_event(start_time, end_time, overview_metrics)
    agent.stats_engine.end_transaction
  end
end

#transaction_overview_metricsObject



246
247
248
249
250
251
252
253
# File 'lib/new_relic/agent/transaction.rb', line 246

def transaction_overview_metrics
  metrics = {}
  stats = @stats_hash
  OVERVIEW_SPECS.each do |(dest_key, spec)|
    metrics[dest_key] = stats[spec].total_call_time if stats.key?(spec)
  end
  metrics
end

#transaction_specific_apdex_tObject



330
331
332
333
# File 'lib/new_relic/agent/transaction.rb', line 330

def transaction_specific_apdex_t
  key = :web_transactions_apdex
  Agent.config[key] && Agent.config[key][self.name]
end

#uriObject

For the current web transaction, return the path of the URI minus the host part and query string, or nil.



169
170
171
# File 'lib/new_relic/agent/transaction.rb', line 169

def uri
  @uri ||= self.class.uri_from_request(@request) unless @request.nil?
end

#user_attributesObject



362
363
364
# File 'lib/new_relic/agent/transaction.rb', line 362

def user_attributes
  @user_atrributes ||= {}
end

#with_database_metric_name(model, method) ⇒ Object

Yield to a block that is run with a database metric name context. This means the Database instrumentation will use this for the metric name if it does not otherwise know about a model. This is re-entrant.

  • model is the DB model class

  • method is the name of the finder method or other method to identify the operation with.



342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
# File 'lib/new_relic/agent/transaction.rb', line 342

def with_database_metric_name(model, method)
  previous = @database_metric_name
  model_name = case model
               when Class
                 model.name
               when String
                 model
               else
                 model.to_s
               end
  @database_metric_name = "ActiveRecord/#{model_name}/#{method}"
  yield
ensure
  @database_metric_name=previous
end