Class: NewRelic::Agent::TransactionSampler

Inherits:
Object
  • Object
show all
Defined in:
lib/new_relic/agent/transaction_sampler.rb

Overview

This class contains the logic of sampling a transaction - creation and modification of transaction samples

Defined Under Namespace

Modules: Shim

Constant Summary collapse

MAX_DATA_LENGTH =
16384

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTransactionSampler

Returns a new instance of TransactionSampler.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/new_relic/agent/transaction_sampler.rb', line 31

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::ForcePersistSampleBuffer.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

Instance Attribute Details

#dev_mode_sample_bufferObject (readonly)

Returns the value of attribute dev_mode_sample_buffer.



29
30
31
# File 'lib/new_relic/agent/transaction_sampler.rb', line 29

def dev_mode_sample_buffer
  @dev_mode_sample_buffer
end

#last_sampleObject (readonly)

Returns the value of attribute last_sample.



29
30
31
# File 'lib/new_relic/agent/transaction_sampler.rb', line 29

def last_sample
  @last_sample
end

#xray_sample_bufferObject (readonly)

Returns the value of attribute xray_sample_buffer.



29
30
31
# File 'lib/new_relic/agent/transaction_sampler.rb', line 29

def xray_sample_buffer
  @xray_sample_buffer
end

Class Method Details

.truncate_message(message) ⇒ Object

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



173
174
175
176
177
178
179
# File 'lib/new_relic/agent/transaction_sampler.rb', line 173

def self.truncate_message(message)
  if message.length > (MAX_DATA_LENGTH - 4)
    message[0..MAX_DATA_LENGTH - 4] + '...'
  else
    message
  end
end

Instance Method Details

#add_segment_parameters(params) ⇒ Object

Set parameters on the current segment.



232
233
234
235
# File 'lib/new_relic/agent/transaction_sampler.rb', line 232

def add_segment_parameters( params )
  return unless builder
  params.each { |k,v| builder.current_segment[k] = v }
end

#append_backtrace(segment, duration) ⇒ Object

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



193
194
195
196
197
# File 'lib/new_relic/agent/transaction_sampler.rb', line 193

def append_backtrace(segment, duration)
  if duration >= Agent.config[:'transaction_tracer.stack_trace_threshold']
    segment[:backtrace] = caller.join("\n")
  end
end

#append_new_message(old_message, message) ⇒ Object

Allows the addition of multiple pieces of metadata to one segment - i.e. traced method calls multiple sql queries



183
184
185
186
187
188
189
# File 'lib/new_relic/agent/transaction_sampler.rb', line 183

def append_new_message(old_message, message)
  if old_message
    old_message + ";\n" + message
  else
    message
  end
end

#append_previous_samples_to_buffers(previous_samples) ⇒ Object

Previous samples are added to buffers to enforce limiting rules



260
261
262
263
264
# File 'lib/new_relic/agent/transaction_sampler.rb', line 260

def append_previous_samples_to_buffers(previous_samples)
  @sample_buffers.each do |buffer|
    buffer.store_previous(previous_samples)
  end
end

#build_database_statement(sql, config, explainer) ⇒ Object



210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/new_relic/agent/transaction_sampler.rb', line 210

def build_database_statement(sql, config, explainer)
  statement = Database::Statement.new(self.class.truncate_message(sql))
  if config
    statement.adapter = config[:adapter]
    statement.config = config
  end
  if Agent.config[:override_sql_obfuscation_adapter]
    statement.adapter = Agent.config[:override_sql_obfuscation_adapter]
  end
  statement.explainer = explainer

  statement
end

#builderObject

The current thread-local transaction sample builder



298
299
300
# File 'lib/new_relic/agent/transaction_sampler.rb', line 298

def builder
  TransactionState.get.transaction_sample_builder
end

#choose_samples(previous_samples) ⇒ Object

Runs previously untransmitted samples into buffers, then chooses what to send based on each buffer’s internal logic



254
255
256
257
# File 'lib/new_relic/agent/transaction_sampler.rb', line 254

def choose_samples(previous_samples)
  append_previous_samples_to_buffers(previous_samples)
  harvest_from_sample_buffers
end

#clear_builderObject

Sets the thread local variable storing the transaction sample builder to nil to clear it



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

def clear_builder
  TransactionState.get.transaction_sample_builder = nil
end

#enabled?Boolean

Returns:

  • (Boolean)


62
63
64
# File 'lib/new_relic/agent/transaction_sampler.rb', line 62

def enabled?
  Agent.config[:'transaction_tracer.enabled'] || Agent.config[:developer_mode]
end

#harvest(previous = []) ⇒ Object

Gather transaction traces that we’d like to transmit to the server. choose_samples is responsible for determining the contents of that transmission, along with limits and ordering.



240
241
242
243
244
245
246
247
248
249
250
# File 'lib/new_relic/agent/transaction_sampler.rb', line 240

def harvest(previous=[])
  return [] if !enabled?

  # If no unsent transactions from last time, we explicitly pass nil!
  previous ||= []

  @samples_lock.synchronize do
    @last_sample = nil
    choose_samples(previous)
  end
end

#harvest_from_sample_buffersObject



266
267
268
269
270
271
272
273
# File 'lib/new_relic/agent/transaction_sampler.rb', line 266

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

#ignore_transactionObject

Tells the builder to ignore a transaction, if we are currently creating one. Only causes the sample to be ignored upon end of the transaction, and does not change the metrics gathered outside of the sampler



135
136
137
# File 'lib/new_relic/agent/transaction_sampler.rb', line 135

def ignore_transaction
  builder.ignore_transaction if builder
end

#notice_first_scope_push(time) ⇒ Object

Creates a new transaction sample builder, unless the transaction sampler is disabled. Takes a time parameter for the start of the transaction sample



69
70
71
# File 'lib/new_relic/agent/transaction_sampler.rb', line 69

def notice_first_scope_push(time)
  start_builder(time.to_f) if enabled?
end

#notice_nosql(key, duration) ⇒ Object

Adds non-sql metadata to a segment - generally the memcached key

duration is seconds, float value.



227
228
229
# File 'lib/new_relic/agent/transaction_sampler.rb', line 227

def notice_nosql(key, duration)
  notice_extra_data(key, duration, :key)
end

#notice_pop_scope(scope, time = Time.now) ⇒ Object

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



89
90
91
92
93
# File 'lib/new_relic/agent/transaction_sampler.rb', line 89

def notice_pop_scope(scope, time = Time.now)
  return unless builder
  raise "finished already???" if builder.sample.finished
  builder.trace_exit(scope, time.to_f)
end

#notice_profile(profile) ⇒ Object

For developer mode profiling support - delegates to the builder



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

def notice_profile(profile)
  builder.set_profile(profile) if builder
end

#notice_push_scope(time = Time.now) ⇒ Object

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

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



79
80
81
82
83
84
85
# File 'lib/new_relic/agent/transaction_sampler.rb', line 79

def notice_push_scope(time=Time.now)
  return unless builder

  segment = builder.trace_entry(time.to_f)
  @sample_buffers.each { |sample_buffer| sample_buffer.visit_segment(segment) }
  return segment
end

#notice_scope_empty(txn, time = Time.now, gc_time = nil) ⇒ Object

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 scopes appended to it.

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



102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/new_relic/agent/transaction_sampler.rb', line 102

def notice_scope_empty(txn, time=Time.now, gc_time=nil)
  last_builder = builder
  last_builder.set_transaction_name(txn.name) if enabled? && last_builder

  return unless last_builder

  last_builder.finish_trace(time.to_f, txn.custom_parameters)
  clear_builder
  return if last_builder.ignored?

  @samples_lock.synchronize do
    @last_sample = last_builder.sample
    @last_sample.set_custom_param(:gc_time, gc_time) if gc_time
    store_sample(@last_sample)
  end
end

#notice_sql(sql, config, duration, &explainer) ⇒ Object

some statements (particularly INSERTS with large BLOBS may be very large; we should trim them to a maximum usable length config is the driver configuration for the connection duration is seconds, float value.



203
204
205
206
207
208
# File 'lib/new_relic/agent/transaction_sampler.rb', line 203

def notice_sql(sql, config, duration, &explainer)
  if NewRelic::Agent.is_sql_recorded?
    statement = build_database_statement(sql, config, explainer)
    notice_extra_data(statement, duration, :sql)
  end
end

#notice_transaction(uri = nil, params = {}) ⇒ Object

Delegates to the builder to store the uri, and parameters if the sampler is active



127
128
129
# File 'lib/new_relic/agent/transaction_sampler.rb', line 127

def notice_transaction(uri=nil, params={})
  builder.set_transaction_info(uri, params) if enabled? && builder
end

#notice_transaction_cpu_time(cpu_time) ⇒ Object

Sets the CPU time used by a transaction, delegates to the builder



145
146
147
# File 'lib/new_relic/agent/transaction_sampler.rb', line 145

def notice_transaction_cpu_time(cpu_time)
  builder.set_transaction_cpu_time(cpu_time) if builder
end

#reset!Object

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



276
277
278
279
280
281
# File 'lib/new_relic/agent/transaction_sampler.rb', line 276

def reset!
  @samples_lock.synchronize do
    @last_sample = nil
    @sample_buffers.each { |sample_buffer| sample_buffer.reset! }
  end
end

#start_builder(time = nil) ⇒ Object

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



289
290
291
292
293
294
295
# File 'lib/new_relic/agent/transaction_sampler.rb', line 289

def start_builder(time=nil)
  if !enabled? || !NewRelic::Agent.is_transaction_traced? || !NewRelic::Agent.is_execution_traced?
    clear_builder
  else
    TransactionState.get.transaction_sample_builder ||= TransactionSampleBuilder.new(time)
  end
end

#store_sample(sample) ⇒ Object



119
120
121
122
123
# File 'lib/new_relic/agent/transaction_sampler.rb', line 119

def store_sample(sample)
  @sample_buffers.each do |sample_buffer|
    sample_buffer.store(sample)
  end
end