Class: TingYun::Agent::Transaction

Inherits:
Object
  • Object
show all
Defined in:
lib/ting_yun/agent/transaction.rb,
lib/ting_yun/agent/transaction/trace.rb,
lib/ting_yun/agent/transaction/attributes.rb,
lib/ting_yun/agent/transaction/trace_node.rb,
lib/ting_yun/agent/transaction/request_attributes.rb

Defined Under Namespace

Classes: Attributes, RequestAttributes, Trace, TraceNode

Constant Summary collapse

APDEX_TXN_METRIC_PREFIX =
'Apdex/'.freeze
SUBTRANSACTION_PREFIX =
'Nested/'.freeze
CONTROLLER_PREFIX =
'WebAction/'.freeze
RAKE_TRANSACTION_PREFIX =
'BackgroundAction/Rake'.freeze
TASK_PREFIX =
'OtherTransaction/Background/'.freeze
RACK_PREFIX =
'Rack/'.freeze
SINATRA_PREFIX =
'Sinatra/'.freeze
MIDDLEWARE_PREFIX =
'Middleware/Rack/'.freeze
GRAPE_PREFIX =
'Grape/'.freeze
RAKE_PREFIX =
'Rake'.freeze
WEB_TRANSACTION_CATEGORIES =
[:controller, :uri, :rack, :sinatra, :grape, :middleware, :thrift].freeze
EMPTY_SUMMARY_METRICS =
[].freeze
MIDDLEWARE_SUMMARY_METRICS =
['Middleware/all'.freeze].freeze
TRACE_OPTIONS_SCOPED =
{:metric => true, :scoped_metric => true}.freeze
TRACE_OPTIONS_UNSCOPED =
{:metric => true, :scoped_metric => false}.freeze
NESTED_TRACE_STOP_OPTIONS =
{:metric => true}.freeze
HEX_DIGITS =
(0..15).map{|i| i.to_s(16)}
GUID_LENGTH =
16

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(category, options) ⇒ Transaction

Returns a new instance of Transaction.



56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/ting_yun/agent/transaction.rb', line 56

def initialize(category, options)
  @guid = options[:client_transaction_id] || generate_guid
  @has_children = false
  @category = category
  @exceptions = {}
  @start_time = Time.now
  @apdex_start = options[:apdex_start_time] || @start_time
  @frame_stack = []
  @frozen_name = nil
  @default_name = TingYun::Helper.correctly_encoded(options[:transaction_name])
  @metrics = TingYun::Agent::TransactionMetrics.new

  @error_recorded = false

  @attributes = TingYun::Agent::Transaction::Attributes.new


  if request = options[:request]
    @request_attributes = TingYun::Agent::Transaction::RequestAttributes.new request
  else
    @request_attributes = nil
  end
end

Instance Attribute Details

#apdex_startObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def apdex_start
  @apdex_start
end

#attributesObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def attributes
  @attributes
end

#categoryObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def category
  @category
end

#default_nameObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def default_name
  @default_name
end

#error_recordedObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def error_recorded
  @error_recorded
end

#exceptionsObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def exceptions
  @exceptions
end

#frame_stackObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def frame_stack
  @frame_stack
end

#guidObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def guid
  @guid
end

#http_response_codeObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def http_response_code
  @http_response_code
end

#metricsObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def metrics
  @metrics
end

#request_attributesObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def request_attributes
  @request_attributes
end

#response_content_typeObject

A Time instance used for calculating the apdex score, which might end up being @start, or it might be further upstream if we can find a request header for the queue entry time



42
43
44
# File 'lib/ting_yun/agent/transaction.rb', line 42

def response_content_type
  @response_content_type
end

#start_timeObject

A Time instance for the start time, never nil



36
37
38
# File 'lib/ting_yun/agent/transaction.rb', line 36

def start_time
  @start_time
end

Class Method Details

.apdex_bucket(duration, failed, apdex_t) ⇒ Object



330
331
332
333
334
335
336
337
338
339
340
341
# File 'lib/ting_yun/agent/transaction.rb', line 330

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

.nested_transaction_name(name) ⇒ Object



232
233
234
235
236
237
238
# File 'lib/ting_yun/agent/transaction.rb', line 232

def self.nested_transaction_name(name)
  if name.start_with?(CONTROLLER_PREFIX) || name.start_with?(RAKE_TRANSACTION_PREFIX)
    "#{SUBTRANSACTION_PREFIX}#{name}"
  else
    name
  end
end

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

See TingYun::Agent.notice_error for options and commentary



349
350
351
352
353
354
355
356
357
# File 'lib/ting_yun/agent/transaction.rb', line 349

def self.notice_error(e, options={})
  state = TingYun::Agent::TransactionState.tl_get
  txn = state.current_transaction
  if txn
    txn.notice_error(e, options)
  elsif TingYun::Agent.instance
    TingYun::Agent.instance.error_collector.notice_error(e, options)
  end
end

.recording_web_transaction?Boolean

THREAD_LOCAL_ACCESS

Returns:

  • (Boolean)


453
454
455
456
# File 'lib/ting_yun/agent/transaction.rb', line 453

def self.recording_web_transaction? #THREAD_LOCAL_ACCESS
  txn = tl_current
  txn && txn.recording_web_transaction?
end

.start(state, category, options) ⇒ Object



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/ting_yun/agent/transaction.rb', line 105

def self.start(state, category, options)
  category ||= :controller
  txn = state.current_transaction
  options[:client_transaction_id] = state.client_transaction_id
  if txn
    txn.create_nested_frame(state, category, options)
  else
    txn = start_new_transaction(state, category, options)
  end

  # merge params every step into here
  txn.attributes.merge_request_parameters(options[:filtered_params])

  txn
rescue => e
  TingYun::Agent.logger.error("Exception during Transaction.start", e)
end

.start_new_transaction(state, category, options) ⇒ Object



123
124
125
126
127
128
# File 'lib/ting_yun/agent/transaction.rb', line 123

def self.start_new_transaction(state, category, options)
  txn = Transaction.new(category, options)
  state.reset(txn)
  txn.start(state)
  txn
end

.stop(state, end_time = Time.now) ⇒ Object



159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
# File 'lib/ting_yun/agent/transaction.rb', line 159

def self.stop(state, end_time = Time.now)

  txn = state.current_transaction

  if txn.nil?
    TingYun::Agent.logger.error("Failed during Transaction.stop because there is no current transaction")
    return
  end

  nested_frame = txn.frame_stack.pop

  if txn.frame_stack.empty?
    txn.stop(state, end_time, nested_frame)
    state.reset
  else
    nested_name = nested_transaction_name(nested_frame.name)

    if nested_name.start_with?(MIDDLEWARE_PREFIX)
      summary_metrics = MIDDLEWARE_SUMMARY_METRICS
    else
      summary_metrics = EMPTY_SUMMARY_METRICS
    end

    TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_footer(
        state,
        nested_frame.start_time.to_f,
        nested_name,
        summary_metrics,
        nested_frame,
        NESTED_TRACE_STOP_OPTIONS,
        end_time.to_f)

  end

  :transaction_stopped
rescue => e
  state.reset
  TingYun::Agent.logger.error("Exception during Transaction.stop", e)
  nil
end

.tl_currentObject



458
459
460
# File 'lib/ting_yun/agent/transaction.rb', line 458

def self.tl_current
  TingYun::Agent::TransactionState.tl_get.current_transaction
end

.wrap(state, name, category, options = {}) ⇒ Object



89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/ting_yun/agent/transaction.rb', line 89

def self.wrap(state, name, category, options = {})
  Transaction.start(state, category, options.merge(:transaction_name => name))

  begin
    # We shouldn't raise from Transaction.start, but only wrap the yield
    # to be absolutely sure we don't report agent problems as app errors
    yield
  rescue => e
    Transaction.notice_error(e)
    raise e
  ensure
    Transaction.stop(state)
  end
end

Instance Method Details

#add_agent_attribute(key, value) ⇒ Object



281
282
283
# File 'lib/ting_yun/agent/transaction.rb', line 281

def add_agent_attribute(key, value)
  @attributes.add_agent_attribute(key, value)
end

#agentObject



477
478
479
# File 'lib/ting_yun/agent/transaction.rb', line 477

def agent
  TingYun::Agent.instance
end

#apdex_bucket(duration, current_apdex_t) ⇒ Object



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

def apdex_bucket(duration, current_apdex_t)
  self.class.apdex_bucket(duration, had_error?, current_apdex_t)
end

#apdex_tObject



343
344
345
# File 'lib/ting_yun/agent/transaction.rb', line 343

def apdex_t
  TingYun::Agent.config[:apdex_t]
end

#assign_agent_attributesObject



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
# File 'lib/ting_yun/agent/transaction.rb', line 262

def assign_agent_attributes

  add_agent_attribute(:threadName,  "pid-#{$$}");

  if http_response_code
    add_agent_attribute(:httpStatus, http_response_code.to_s)
  end

  if response_content_type
    add_agent_attribute(:contentType, response_content_type)
  end


  if @request_attributes
    @request_attributes.assign_agent_attributes self
  end

end

#best_nameObject



468
469
470
# File 'lib/ting_yun/agent/transaction.rb', line 468

def best_name
  @frozen_name || @default_name || ::TingYun::Agent::UNKNOWN_METRIC
end

#commit!(state, end_time, outermost_node_name) ⇒ Object



240
241
242
243
244
245
246
247
248
249
250
251
252
253
# File 'lib/ting_yun/agent/transaction.rb', line 240

def commit!(state, end_time, outermost_node_name)

  assign_agent_attributes


  transaction_sampler.on_finishing_transaction(state, self, end_time)

  sql_sampler.on_finishing_transaction(state, @frozen_name)

  record_summary_metrics(outermost_node_name, end_time)
  record_apdex(state, end_time)
  record_exceptions
  merge_metrics
end

#create_nested_frame(state, category, options) ⇒ Object



141
142
143
144
145
146
147
# File 'lib/ting_yun/agent/transaction.rb', line 141

def create_nested_frame(state, category, options)
  @has_children = true
  frame_stack.push TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_header(state, Time.now.to_f)
  name_last_frame(options[:transaction_name])

  set_default_transaction_name(options[:transaction_name], category)
end

#freeze_name_and_executeObject



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

def freeze_name_and_execute
  if !name_frozen?
    @name_frozen = true
    @frozen_name = best_name
  end

  yield if block_given?
end

#had_error?Boolean

collector error

Returns:

  • (Boolean)


286
287
288
289
290
291
292
# File 'lib/ting_yun/agent/transaction.rb', line 286

def had_error?
  if @exceptions.empty?
    return false
  else
    return true
  end
end

#influences_transaction_name?(category) ⇒ Boolean

Returns:

  • (Boolean)


437
438
439
# File 'lib/ting_yun/agent/transaction.rb', line 437

def influences_transaction_name?(category)
  !category || frame_stack.size == 1 || similar_category?(category)
end

#instrumentation_stateObject

This transaction-local hash may be used as temprory storage by instrumentation that needs to pass data from one instrumentation point to another.

For example, if both A and B are instrumented, and A calls B but some piece of state needed by the instrumentation at B is only available at A, the instrumentation at A may write into the hash, call through, and then remove the key afterwards, allowing the instrumentation at B to read the value in between.

Keys should be symbols, and care should be taken to not generate key names dynamically, and to ensure that keys are removed upon return from the method that creates them.



383
384
385
# File 'lib/ting_yun/agent/transaction.rb', line 383

def instrumentation_state
  @instrumentation_state ||= {}
end

#log_frozen_name(name) ⇒ Object



432
433
434
435
# File 'lib/ting_yun/agent/transaction.rb', line 432

def log_frozen_name(name)
  TingYun::Agent.logger.warn("Attempted to rename transaction to '#{name}' after transaction name was already frozen as '#{@frozen_name}'.")
  nil
end

#merge_metricsObject



420
421
422
# File 'lib/ting_yun/agent/transaction.rb', line 420

def merge_metrics
  TingYun::Agent.instance.stats_engine.merge_transaction_metrics!(@metrics, best_name)
end

#name_frozen?Boolean

Returns:

  • (Boolean)


428
429
430
# File 'lib/ting_yun/agent/transaction.rb', line 428

def name_frozen?
  @frozen_name ? true : false
end

#name_last_frame(name) ⇒ Object



424
425
426
# File 'lib/ting_yun/agent/transaction.rb', line 424

def name_last_frame(name)
  frame_stack.last.name = name
end

#needs_middleware_summary_metrics?(name) ⇒ Boolean Also known as: ignore

Returns:

  • (Boolean)


462
463
464
# File 'lib/ting_yun/agent/transaction.rb', line 462

def needs_middleware_summary_metrics?(name)
  name.start_with?(MIDDLEWARE_PREFIX)
end

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

Do not call this. Invoke the class method instead.



360
361
362
363
364
365
366
# File 'lib/ting_yun/agent/transaction.rb', line 360

def notice_error(error, options={}) # :nodoc:
  if @exceptions[error]
    @exceptions[error].merge! options
  else
    @exceptions[error] = options
  end
end


412
413
414
415
416
417
418
# File 'lib/ting_yun/agent/transaction.rb', line 412

def promoted_transaction_name(name)
  if name.start_with?(MIDDLEWARE_PREFIX)
    "#{CONTROLLER_PREFIX}#{name}"
  else
    name
  end
end

#queue_timeObject



472
473
474
# File 'lib/ting_yun/agent/transaction.rb', line 472

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

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



308
309
310
311
312
313
# File 'lib/ting_yun/agent/transaction.rb', line 308

def record_apdex(state, end_time=Time.now)
  total_duration = (end_time - apdex_start)*1000
  if recording_web_transaction?
    record_apdex_metrics(APDEX_TXN_METRIC_PREFIX, total_duration, apdex_t)
  end
end

#record_apdex_metrics(transaction_prefix, total_duration, current_apdex_t) ⇒ Object



316
317
318
319
320
321
322
323
# File 'lib/ting_yun/agent/transaction.rb', line 316

def record_apdex_metrics(transaction_prefix, total_duration, current_apdex_t)
  return unless current_apdex_t
  return unless @frozen_name.start_with?(CONTROLLER_PREFIX)

  apdex_bucket_global = apdex_bucket(total_duration, current_apdex_t)
  txn_apdex_metric = @frozen_name.sub(/^[^\/]+\//, transaction_prefix)
  @metrics.record_unscoped(txn_apdex_metric, apdex_bucket_global, current_apdex_t)
end

#record_exceptionsObject



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

def record_exceptions
  unless @exceptions.empty?
    @exceptions.each do |exception, options|

      options[:uri]      ||= request_path if request_path
      options[:port]       = request_port if request_port
      options[:metric_name]     = best_name
      options[:attributes] = @attributes

      @error_recorded = !!::TingYun::Agent.instance.error_collector.notice_error(exception, options) || @error_recorded
    end
  end
end

#record_summary_metrics(outermost_node_name, end_time) ⇒ Object



256
257
258
259
260
# File 'lib/ting_yun/agent/transaction.rb', line 256

def record_summary_metrics(outermost_node_name,end_time)
  unless @frozen_name == outermost_node_name
    @metrics.record_unscoped(@frozen_name, TingYun::Helper.time_to_millis(end_time.to_f - start_time.to_f))
  end
end

#recording_web_transaction?Boolean

Returns:

  • (Boolean)


449
450
451
# File 'lib/ting_yun/agent/transaction.rb', line 449

def recording_web_transaction?
  web_category?(@category)
end

#request_pathObject



81
82
83
# File 'lib/ting_yun/agent/transaction.rb', line 81

def request_path
  @request_attributes && @request_attributes.request_path
end

#request_portObject



85
86
87
# File 'lib/ting_yun/agent/transaction.rb', line 85

def request_port
  @request_attributes && @request_attributes.port
end

#set_default_transaction_name(name, category) ⇒ Object



150
151
152
153
154
155
156
# File 'lib/ting_yun/agent/transaction.rb', line 150

def set_default_transaction_name(name, category)
  return log_frozen_name(name) if name_frozen?
  if influences_transaction_name?(category)
    self.default_name = name
    @category = category if category
  end
end

#similar_category?(category) ⇒ Boolean

Returns:

  • (Boolean)


445
446
447
# File 'lib/ting_yun/agent/transaction.rb', line 445

def similar_category?(category)
  web_category?(@category) == web_category?(category)
end

#sql_samplerObject



481
482
483
# File 'lib/ting_yun/agent/transaction.rb', line 481

def sql_sampler
  agent.sql_sampler
end

#start(state) ⇒ Object



130
131
132
133
134
135
136
137
138
139
# File 'lib/ting_yun/agent/transaction.rb', line 130

def start(state)
  return if !state.execution_traced?

  transaction_sampler.on_start_transaction(state, start_time)
  sql_sampler.on_start_transaction(state, request_path)
  TingYun::Agent.instance.events.notify(:start_transaction)
  frame_stack.push TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_header(state, Time.now.to_f)
  name_last_frame @default_name
  freeze_name_and_execute if @default_name.start_with?(RAKE_TRANSACTION_PREFIX)
end

#stop(state, end_time, outermost_frame) ⇒ Object



201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
# File 'lib/ting_yun/agent/transaction.rb', line 201

def stop(state, end_time, outermost_frame)

  freeze_name_and_execute

  if @has_children
    name = Transaction.nested_transaction_name(outermost_frame.name)
    trace_options = TRACE_OPTIONS_SCOPED
  else
    name = @frozen_name
    trace_options = TRACE_OPTIONS_UNSCOPED
  end

  if needs_middleware_summary_metrics?(name)
    summary_metrics_with_exclusive_time = MIDDLEWARE_SUMMARY_METRICS
  else
    summary_metrics_with_exclusive_time = EMPTY_SUMMARY_METRICS
  end


  TingYun::Agent::MethodTracerHelpers.trace_execution_scoped_footer(
      state,
      start_time.to_f,
      name,
      summary_metrics_with_exclusive_time,
      outermost_frame,
      trace_options,
      end_time.to_f)

  commit!(state, end_time, name) unless ignore(best_name)
end

#transaction_samplerObject



486
487
488
# File 'lib/ting_yun/agent/transaction.rb', line 486

def transaction_sampler
  TingYun::Agent.instance.transaction_sampler
end

#web_category?(category) ⇒ Boolean

Returns:

  • (Boolean)


441
442
443
# File 'lib/ting_yun/agent/transaction.rb', line 441

def web_category?(category)
  WEB_TRANSACTION_CATEGORIES.include?(category)
end

#with_database_metric_name(model, method, product = nil) ⇒ Object



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
# File 'lib/ting_yun/agent/transaction.rb', line 387

def with_database_metric_name(model, method, product=nil)
  previous = self.instrumentation_state[:datastore_override]
  model_name = case model
                 when Class
                   model.name
                 when String
                   model
                 else
                   model.to_s
               end
  self.instrumentation_state[:datastore_override] = [method, model_name, product]
  yield
ensure
  self.instrumentation_state[:datastore_override] = previous
end