class NewRelic::Agent::TransactionSampler

This class contains the logic for recording and storing transaction traces (sometimes referred to as 'transaction samples').

A transaction trace is a detailed timeline of the events that happened during the processing of a single transaction, including database calls, template rendering calls, and other instrumented method calls.

@api public

Constants

MAX_DATA_LENGTH

Attributes

dev_mode_sample_buffer[R]
last_sample[R]
xray_sample_buffer[R]

Public Class Methods

new() click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 24
def initialize
  @dev_mode_sample_buffer = NewRelic::Agent::Transaction::DeveloperModeSampleBuffer.new
  @xray_sample_buffer = NewRelic::Agent::Transaction::XraySampleBuffer.new

  @sample_buffers = []
  @sample_buffers << @dev_mode_sample_buffer
  @sample_buffers << @xray_sample_buffer
  @sample_buffers << NewRelic::Agent::Transaction::SlowestSampleBuffer.new
  @sample_buffers << NewRelic::Agent::Transaction::SyntheticsSampleBuffer.new

  # This lock is used to synchronize access to the @last_sample
  # and related variables. It can become necessary on JRuby or
  # any 'honest-to-god'-multithreaded system
  @samples_lock = Mutex.new

  Agent.config.register_callback(:'transaction_tracer.enabled') do |enabled|
    if enabled
      threshold = Agent.config[:'transaction_tracer.transaction_threshold']
      ::NewRelic::Agent.logger.debug "Transaction tracing threshold is #{threshold} seconds."
    else
      ::NewRelic::Agent.logger.debug "Transaction traces will not be sent to the New Relic service."
    end
  end

  Agent.config.register_callback(:'transaction_tracer.record_sql') do |config|
    if config == 'raw'
      ::NewRelic::Agent.logger.warn("Agent is configured to send raw SQL to the service")
    end
  end
end
truncate_message(message) click to toggle source

Truncates the message to `MAX_DATA_LENGTH` if needed, and appends an ellipsis because it makes the trucation clearer in the UI

# File lib/new_relic/agent/transaction_sampler.rb, line 153
def self.truncate_message(message)
  if message.length > (MAX_DATA_LENGTH - 4)
    message.slice!(MAX_DATA_LENGTH - 4..message.length)
    message << "..."
  else
    message
  end
end

Public Instance Methods

add_node_parameters(params) click to toggle source

Set parameters on the current node.

# File lib/new_relic/agent/transaction_sampler.rb, line 223
def add_node_parameters(params) #THREAD_LOCAL_ACCESS
  builder = tl_builder
  return unless builder
  params.each { |k,v| builder.current_node[k] = v }
end
append_backtrace(node, duration) click to toggle source

Appends a backtrace to a node if that node took longer than the specified duration

# File lib/new_relic/agent/transaction_sampler.rb, line 164
def append_backtrace(node, duration)
  if duration >= Agent.config[:'transaction_tracer.stack_trace_threshold']
    node[:backtrace] = caller.join("\n")
  end
end
count() click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 261
def count
  @samples_lock.synchronize do
    samples = @sample_buffers.inject([]) { |all, b| all.concat(b.samples) }
    samples.uniq.size
  end
end
enabled?() click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 55
def enabled?
  Agent.config[:'transaction_tracer.enabled'] || Agent.config[:developer_mode]
end
harvest!() click to toggle source

Gather transaction traces that we'd like to transmit to the server.

# File lib/new_relic/agent/transaction_sampler.rb, line 230
def harvest!
  return [] unless enabled?

  samples = @samples_lock.synchronize do
    @last_sample = nil
    harvest_from_sample_buffers
  end
  prepare_samples(samples)
end
harvest_from_sample_buffers() click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 268
def harvest_from_sample_buffers
  # map + flatten hit mocking issues calling to_ary on 1.9.2.  We only
  # want a single level flatten anyway, but, as you probably already
  # know, Ruby 1.8.6 :/
  result = []
  @sample_buffers.each { |buffer| result.concat(buffer.harvest_samples) }
  result.uniq
end
merge!(previous) click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 253
def merge!(previous)
  @samples_lock.synchronize do
    @sample_buffers.each do |buffer|
      buffer.store_previous(previous)
    end
  end
end
notice_nosql(key, duration) click to toggle source

Attaches an additional non-SQL query parameter to the current transaction trace node.

This may be used for recording a query against a key-value store like memcached or redis.

This method should be used only by gem authors wishing to extend the Ruby agent to instrument uninstrumented key-value stores - it should generally not be called directly from application code.

@param key [String] the name of the key that was queried @param duration [Float] number of seconds the query took to execute

@api public @deprecated Use {Datastores.notice_statement} instead.

# File lib/new_relic/agent/transaction_sampler.rb, line 212
def notice_nosql(key, duration) #THREAD_LOCAL_ACCESS
  builder = tl_builder
  notice_extra_data(builder, key, duration, :key)
end
notice_nosql_statement(statement, duration) click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 217
def notice_nosql_statement(statement, duration) #THREAD_LOCAL_ACCESS
  builder = tl_builder
  notice_extra_data(builder, statement, duration, :statement)
end
notice_pop_frame(state, frame, time = Time.now) click to toggle source

Informs the transaction sample builder about the end of a traced frame

# File lib/new_relic/agent/transaction_sampler.rb, line 82
def notice_pop_frame(state, frame, time = Time.now)
  builder = state.transaction_sample_builder
  return unless builder
  raise "finished already???" if builder.sample.finished
  builder.trace_exit(frame, time.to_f)
end
notice_push_frame(state, time=Time.now) click to toggle source

This delegates to the builder to create a new open transaction node for the frame, beginning at the optionally specified time.

Note that in developer mode, this captures a stacktrace for the beginning of each node, which can be fairly slow

# File lib/new_relic/agent/transaction_sampler.rb, line 70
def notice_push_frame(state, time=Time.now)
  builder = state.transaction_sample_builder
  return unless builder

  node = builder.trace_entry(time.to_f)
  if @dev_mode_sample_buffer
    @dev_mode_sample_buffer.visit_node(node)
  end
  node
end
notice_sql(sql, config, duration, state=nil, explainer=nil) click to toggle source

Attaches an SQL query on the current transaction trace node.

This method should be used only by gem authors wishing to extend the Ruby agent to instrument new database interfaces - it should generally not be called directly from application code.

@param sql [String] the SQL query being recorded @param config [Object] the driver configuration for the connection @param duration [Float] number of seconds the query took to execute @param explainer [Proc] for internal use only - 3rd-party clients must

not pass this parameter.

@api public @deprecated Use {Datastores.notice_sql} instead.

# File lib/new_relic/agent/transaction_sampler.rb, line 185
def notice_sql(sql, config, duration, state=nil, explainer=nil) #THREAD_LOCAL_ACCESS sometimes
  # some statements (particularly INSERTS with large BLOBS
  # may be very large; we should trim them to a maximum usable length
  state ||= TransactionState.tl_get
  builder = state.transaction_sample_builder
  if state.is_sql_recorded?
    statement = Database::Statement.new(sql, config, explainer)
    notice_extra_data(builder, statement, duration, :sql)
  end
end
on_finishing_transaction(state, txn, time=Time.now) click to toggle source

This is called when we are done with the transaction. We've unwound the stack to the top level. It also clears the transaction sample builder so that it won't continue to have frames appended to it.

It sets various instance variables to the finished sample, depending on which settings are active. See `store_sample`

# File lib/new_relic/agent/transaction_sampler.rb, line 96
def on_finishing_transaction(state, txn, time=Time.now)
  last_builder = state.transaction_sample_builder
  return unless last_builder && enabled?

  state.transaction_sample_builder = nil
  return if txn.ignore_trace?

  last_builder.finish_trace(time.to_f)

  last_sample = last_builder.sample
  last_sample.transaction_name = txn.best_name
  last_sample.uri = txn.request_path
  last_sample.guid = txn.guid
  last_sample.attributes = txn.attributes

  @samples_lock.synchronize do
    @last_sample = last_sample
    store_sample(@last_sample)
    @last_sample
  end
end
on_start_transaction(state, start_time) click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 59
def on_start_transaction(state, start_time)
  if enabled?
    start_builder(state, start_time.to_f)
  end
end
prepare_samples(samples) click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 240
def prepare_samples(samples)
  samples.select do |sample|
    begin
      sample.prepare_to_send!
    rescue => e
      NewRelic::Agent.logger.error("Failed to prepare transaction trace. Error: ", e)
      false
    else
      true
    end
  end
end
reset!() click to toggle source

reset samples without rebooting the web server (used by dev mode)

# File lib/new_relic/agent/transaction_sampler.rb, line 278
def reset!
  @samples_lock.synchronize do
    @last_sample = nil
    @sample_buffers.each { |sample_buffer| sample_buffer.reset! }
  end
end
start_builder(state, time=nil) click to toggle source

Checks to see if the transaction sampler is disabled, if transaction trace recording is disabled by a thread local, or if execution is untraced - if so it clears the transaction sample builder from the thread local, otherwise it generates a new transaction sample builder with the stated time as a starting point and saves it in the thread local variable

# File lib/new_relic/agent/transaction_sampler.rb, line 291
def start_builder(state, time=nil)
  if !enabled? || !state.is_transaction_traced? || !state.is_execution_traced?
    state.transaction_sample_builder = nil
  else
    state.transaction_sample_builder ||= TransactionSampleBuilder.new(time)
  end
end
store_sample(sample) click to toggle source
# File lib/new_relic/agent/transaction_sampler.rb, line 118
def store_sample(sample)
  @sample_buffers.each do |sample_buffer|
    sample_buffer.store(sample)
  end
end
tl_builder() click to toggle source

The current thread-local transaction sample builder

# File lib/new_relic/agent/transaction_sampler.rb, line 300
def tl_builder
  TransactionState.tl_get.transaction_sample_builder
end

Private Instance Methods

notice_extra_data(builder, message, duration, key) click to toggle source

This method is used to record metadata into the currently active node like a sql query, memcache key, or Net::HTTP uri

duration is seconds, float value.

# File lib/new_relic/agent/transaction_sampler.rb, line 129
def notice_extra_data(builder, message, duration, key)
  return unless builder
  node = builder.current_node
  if node
    if key == :sql
      statement = node[:sql]
      if(statement && !statement.sql.empty?)
        statement.sql = self.class.truncate_message(statement.sql + "\n#{message.sql}") if statement.sql.length <= MAX_DATA_LENGTH
      else
        # message is expected to have been pre-truncated by notice_sql
        node[:sql] = message
      end
    else
      node[key] = self.class.truncate_message(message)
    end
    append_backtrace(node, duration)
  end
end