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

Defined Under Namespace

Modules: Pop

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



158
159
160
# File 'lib/new_relic/agent/transaction.rb', line 158

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.



275
276
277
# File 'lib/new_relic/agent/transaction.rb', line 275

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



397
398
399
400
401
402
403
404
405
406
407
408
# File 'lib/new_relic/agent/transaction.rb', line 397

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



279
280
281
# File 'lib/new_relic/agent/transaction.rb', line 279

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



248
249
250
251
252
253
254
255
256
257
258
259
# File 'lib/new_relic/agent/transaction.rb', line 248

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



393
394
395
# File 'lib/new_relic/agent/transaction.rb', line 393

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)


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

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.



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

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



283
284
285
# File 'lib/new_relic/agent/transaction.rb', line 283

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

.uri_from_request(request) ⇒ Object

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



380
381
382
383
384
385
386
387
388
389
# File 'lib/new_relic/agent/transaction.rb', line 380

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



287
288
289
# File 'lib/new_relic/agent/transaction.rb', line 287

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



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

def abort_transaction!
  transaction_sampler.ignore_transaction
end

#add_custom_parameters(p) ⇒ Object



353
354
355
# File 'lib/new_relic/agent/transaction.rb', line 353

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

#apdex_tObject



309
310
311
# File 'lib/new_relic/agent/transaction.rb', line 309

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

#custom_parametersObject



341
342
343
# File 'lib/new_relic/agent/transaction.rb', line 341

def custom_parameters
  @custom_parameters ||= {}
end

#freeze_nameObject



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

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

#has_parent?Boolean

Returns:

  • (Boolean)


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

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

#merge_stats_hashObject



208
209
210
211
# File 'lib/new_relic/agent/transaction.rb', line 208

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

#name_frozen?Boolean

Returns:

  • (Boolean)


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

def name_frozen?
  @name_frozen
end

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

Do not call this. Invoke the class method instead.



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

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



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

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

#queue_timeObject



349
350
351
# File 'lib/new_relic/agent/transaction.rb', line 349

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

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



293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
# File 'lib/new_relic/agent/transaction.rb', line 293

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



213
214
215
216
217
218
# File 'lib/new_relic/agent/transaction.rb', line 213

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)


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

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

#refererObject

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



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

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

#root?Boolean

Returns:

  • (Boolean)


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

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

#set_user_attributes(attributes) ⇒ Object



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

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.



146
147
148
149
150
151
152
153
154
# File 'lib/new_relic/agent/transaction.rb', line 146

def start(transaction_type)
  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(metric = ::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.



180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/new_relic/agent/transaction.rb', line 180

def stop(metric=::NewRelic::Agent::UNKNOWN_METRIC, end_time=Time.now)
  @name ||= metric unless 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?
    agent.events.notify(:transaction_finished, @name, start_time.to_f, end_time.to_f - start_time.to_f, overview_metrics)
    agent.stats_engine.end_transaction
  end
end

#transaction_overview_metricsObject



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

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



313
314
315
316
# File 'lib/new_relic/agent/transaction.rb', line 313

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.



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

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

#user_attributesObject



345
346
347
# File 'lib/new_relic/agent/transaction.rb', line 345

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.



325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
# File 'lib/new_relic/agent/transaction.rb', line 325

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