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

BUILDER_KEY =
:transaction_sample_builder
MAX_DATA_LENGTH =
16384

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeTransactionSampler

Returns a new instance of TransactionSampler.



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
# File 'lib/new_relic/agent/transaction_sampler.rb', line 27

def initialize

  # @samples is an array of recent samples up to @max_samples in
  # size - it's only used by developer mode
  @samples = []
  @force_persist = []
  @max_samples = 100

  # @harvest_count is a count of harvests used for random
  # sampling - we pull 1 @random_sample in every @sampling_rate harvests
  @harvest_count = 0
  @random_sample = nil
  @sampling_rate = 10
  @slow_capture_threshold = 2.0
  configure!

  # 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
end

Instance Attribute Details

#disabledObject (readonly)

Returns the value of attribute disabled.



25
26
27
# File 'lib/new_relic/agent/transaction_sampler.rb', line 25

def disabled
  @disabled
end

#explain_enabledObject

Returns the value of attribute explain_enabled.



23
24
25
# File 'lib/new_relic/agent/transaction_sampler.rb', line 23

def explain_enabled
  @explain_enabled
end

#explain_thresholdObject

Returns the value of attribute explain_threshold.



23
24
25
# File 'lib/new_relic/agent/transaction_sampler.rb', line 23

def explain_threshold
  @explain_threshold
end

#last_sampleObject (readonly)

Returns the value of attribute last_sample.



25
26
27
# File 'lib/new_relic/agent/transaction_sampler.rb', line 25

def last_sample
  @last_sample
end

#random_samplingObject

Returns the value of attribute random_sampling.



22
23
24
# File 'lib/new_relic/agent/transaction_sampler.rb', line 22

def random_sampling
  @random_sampling
end

#samplesObject (readonly)

Returns the value of attribute samples.



25
26
27
# File 'lib/new_relic/agent/transaction_sampler.rb', line 25

def samples
  @samples
end

#sampling_rateObject

Returns the value of attribute sampling_rate.



22
23
24
# File 'lib/new_relic/agent/transaction_sampler.rb', line 22

def sampling_rate
  @sampling_rate
end

#slow_capture_thresholdObject

Returns the value of attribute slow_capture_threshold.



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

def slow_capture_threshold
  @slow_capture_threshold
end

#stack_trace_thresholdObject

Returns the value of attribute stack_trace_threshold.



22
23
24
# File 'lib/new_relic/agent/transaction_sampler.rb', line 22

def stack_trace_threshold
  @stack_trace_threshold
end

#transaction_thresholdObject

Returns the value of attribute transaction_threshold.



23
24
25
# File 'lib/new_relic/agent/transaction_sampler.rb', line 23

def transaction_threshold
  @transaction_threshold
end

Instance Method Details

#add_force_persist_to(result) ⇒ Object



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

def add_force_persist_to(result)
  result.concat(@force_persist)
  @force_persist = []
end

#add_random_sample_to(result) ⇒ Object

Every 1/n harvests, adds the most recent sample to the harvest array if it exists. Makes sure that the random sample is not also the slowest sample for this harvest by ‘uniq!`ing the result array

random sampling is very, very seldom used



346
347
348
349
350
351
352
353
354
# File 'lib/new_relic/agent/transaction_sampler.rb', line 346

def add_random_sample_to(result)
  return unless @random_sampling && @sampling_rate && @sampling_rate.to_i > 0
  @harvest_count += 1
  if (@harvest_count.to_i % @sampling_rate.to_i) == 0
    result << @random_sample if @random_sample
    @harvest_count = 0
  end
  nil # don't assume this method returns anything
end

#add_samples_to(result, slow_threshold) ⇒ Object

Returns an array of slow samples, with either one or two elements - one element unless random sampling is enabled. The sample returned will be the slowest sample among those available during this harvest



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
# File 'lib/new_relic/agent/transaction_sampler.rb', line 365

def add_samples_to(result, slow_threshold)

  # pull out force persist
  force_persist = result.select {|sample| sample.force_persist} || []
  result.reject! {|sample| sample.force_persist}

  force_persist.each {|sample| store_force_persist(sample)}


  # Now get the slowest sample
  if @slowest_sample && @slowest_sample.duration >= slow_threshold
    result << @slowest_sample
  end

  result.compact!
  result = result.sort_by { |x| x.duration }
  result = result[-1..-1] || []               # take the slowest sample

  add_random_sample_to(result)
  add_force_persist_to(result)

  result.uniq
end

#append_backtrace(segment, duration) ⇒ Object

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



318
319
320
# File 'lib/new_relic/agent/transaction_sampler.rb', line 318

def append_backtrace(segment, duration)
  segment[:backtrace] = caller.join("\n") if (duration >= @stack_trace_threshold || Thread.current[:capture_deep_tt])
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



308
309
310
311
312
313
314
# File 'lib/new_relic/agent/transaction_sampler.rb', line 308

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

#builderObject

The current thread-local transaction sample builder



456
457
458
# File 'lib/new_relic/agent/transaction_sampler.rb', line 456

def builder
  Thread::current[BUILDER_KEY]
end

#capture_segment_traceObject

in developer mode, capture the stack trace with the segment. this is cpu and memory expensive and therefore should not be turned on in production mode



130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/new_relic/agent/transaction_sampler.rb', line 130

def capture_segment_trace
  return unless NewRelic::Control.instance.developer_mode?
  segment = builder.current_segment
  if segment
    # Strip stack frames off the top that match /new_relic/agent/
    trace = caller
    while trace.first =~/\/lib\/new_relic\/agent\//
      trace.shift
    end

    trace = trace[0..39] if trace.length > 40
    segment[:backtrace] = trace
  end
end

#clamp_number_tts(tts, limit) ⇒ Object

JON - THIS CODE NEEDS A UNIT TEST



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

def clamp_number_tts(tts, limit)
  tts.sort! do |a,b|
    if a.force_persist && b.force_persist
      b.duration <=> a.duration
    elsif a.force_persist
      -1
    elsif b.force_persist
      1
    else
      b.duration <=> a.duration
    end
  end

  tts[0..(limit-1)]
end

#clear_builderObject

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



462
463
464
# File 'lib/new_relic/agent/transaction_sampler.rb', line 462

def clear_builder
  Thread::current[BUILDER_KEY] = nil
end

#configObject



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

def config
  NewRelic::Control.instance.fetch('transaction_tracer', {})
end

#configure!Object



49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# File 'lib/new_relic/agent/transaction_sampler.rb', line 49

def configure!
  # @segment_limit and @stack_trace_threshold come from the
  # configuration file, with built-in defaults that should
  # suffice for most customers

  # enable if config.fetch('enabled', true)

  @segment_limit = config.fetch('limit_segments', 4000)
  @stack_trace_threshold = config.fetch('stack_trace_threshold', 0.500).to_f
  @explain_threshold = config.fetch('explain_threshold', 0.5).to_f
  @explain_enabled = config.fetch('explain_enabled', true)
  @transaction_threshold = config.fetch('transation_threshold', 2.0)

  # Configure the sample storage policy.  Create a list of methods to be called.
  @store_sampler_methods = [ :store_random_sample, :store_slowest_sample ]
  if NewRelic::Control.instance.developer_mode?
    @store_sampler_methods << :store_sample_for_developer_mode
  end
end

#current_sample_idObject

Returns the current sample id, delegated from ‘builder`



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

def current_sample_id
  b=builder
  b and b.sample_id
end

#disableObject

Disable the transaction sampler - this also deregisters it with the statistics engine.



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

def disable
  @disabled = true
  NewRelic::Agent.instance.stats_engine.remove_transaction_sampler(self)
end

#enableObject

Enable the transaction sampler - this also registers it with the statistics engine.



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

def enable
  @disabled = false
  NewRelic::Agent.instance.stats_engine.transaction_sampler = self
end

#enabled?Boolean

Returns:

  • (Boolean)


93
94
95
# File 'lib/new_relic/agent/transaction_sampler.rb', line 93

def enabled?
  !@disabled
end

#harvest(previous = [], slow_threshold = 2.0) ⇒ Object

get the set of collected samples, merging into previous samples, and clear the collected sample list. Truncates samples to a specified @segment_limit to save memory and bandwith transmitting samples to the server.



393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# File 'lib/new_relic/agent/transaction_sampler.rb', line 393

def harvest(previous = [], slow_threshold = 2.0)
  return [] if disabled
  result = Array(previous)

  @samples_lock.synchronize do
    result = add_samples_to(result, slow_threshold)

    # clear previous transaction samples
    @slowest_sample = nil
    @random_sample = nil
    @last_sample = nil
  end

  # Clamp the number of TTs we'll keep in memory and send
  #
  result = clamp_number_tts(result, 20) if result.length > 20

  # Truncate the samples at 2100 segments. The UI will clamp them at 2000 segments anyway.
  # This will save us memory and bandwidth.
  result.each { |sample| sample.truncate(@segment_limit) }
  result
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



257
258
259
# File 'lib/new_relic/agent/transaction_sampler.rb', line 257

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



109
110
111
# File 'lib/new_relic/agent/transaction_sampler.rb', line 109

def notice_first_scope_push(time)
  start_builder(time.to_f) unless disabled
end

#notice_nosql(key, duration) ⇒ Object

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

duration is seconds, float value.



336
337
338
# File 'lib/new_relic/agent/transaction_sampler.rb', line 336

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



155
156
157
158
159
# File 'lib/new_relic/agent/transaction_sampler.rb', line 155

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

#notice_profile(profile) ⇒ Object

For developer mode profiling support - delegates to the builder



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

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

#notice_push_scope(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



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

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

  builder.trace_entry(scope, time.to_f)

  capture_segment_trace if NewRelic::Control.instance.developer_mode?
end

#notice_scope_empty(time = Time.now) ⇒ 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`



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/new_relic/agent/transaction_sampler.rb', line 168

def notice_scope_empty(time=Time.now)

  last_builder = builder
  return unless last_builder

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

  @samples_lock.synchronize do
    # NB this instance variable may be used elsewhere, it's not
    # just a side effect
    @last_sample = last_builder.sample
    store_sample(@last_sample)
  end
end

#notice_sql(sql, config, duration) ⇒ 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.



326
327
328
329
330
# File 'lib/new_relic/agent/transaction_sampler.rb', line 326

def notice_sql(sql, config, duration)
  if NewRelic::Agent.is_sql_recorded?
    notice_extra_data(sql, duration, :sql, config, :connection_config)
  end
end

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

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



249
250
251
# File 'lib/new_relic/agent/transaction_sampler.rb', line 249

def notice_transaction(path, uri=nil, params={})
  builder.set_transaction_info(path, uri, params) if !disabled && builder
end

#notice_transaction_cpu_time(cpu_time) ⇒ Object

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



267
268
269
# File 'lib/new_relic/agent/transaction_sampler.rb', line 267

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



434
435
436
437
438
439
# File 'lib/new_relic/agent/transaction_sampler.rb', line 434

def reset!
  @samples = []
  @last_sample = nil
  @random_sample = nil
  @slowest_sample = nil
end

#scope_depthObject

Defaults to zero, otherwise delegated to the transaction sample builder



147
148
149
150
151
# File 'lib/new_relic/agent/transaction_sampler.rb', line 147

def scope_depth
  return 0 unless builder

  builder.scope_depth
end

#slowest_sample?(old_sample, new_sample) ⇒ Boolean

Checks to see if the old sample exists, or if it’s duration is less than the new sample

Returns:

  • (Boolean)


235
236
237
# File 'lib/new_relic/agent/transaction_sampler.rb', line 235

def slowest_sample?(old_sample, new_sample)
  old_sample.nil? || (new_sample.duration > old_sample.duration)
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



447
448
449
450
451
452
453
# File 'lib/new_relic/agent/transaction_sampler.rb', line 447

def start_builder(time=nil)
  if disabled || !NewRelic::Agent.is_transaction_traced? || !NewRelic::Agent.is_execution_traced?
    clear_builder
  else
    Thread::current[BUILDER_KEY] ||= TransactionSampleBuilder.new(time)
  end
end

#store_force_persist(sample) ⇒ Object



206
207
208
209
210
211
212
213
214
# File 'lib/new_relic/agent/transaction_sampler.rb', line 206

def store_force_persist(sample)
  @force_persist << sample

  # WARNING - this clamp should be configurable
  if @force_persist.length > 15
    @force_persist.sort! {|a,b| b.duration <=> a.duration}
    @force_persist = @force_persist[0..14]
  end
end

#store_random_sample(sample) ⇒ Object

Only active when random sampling is true - this is very rarely used. Always store the most recent sample so that random sampling can pick a few of the samples to store, upon harvest



200
201
202
203
204
# File 'lib/new_relic/agent/transaction_sampler.rb', line 200

def store_random_sample(sample)
  if @random_sampling
    @random_sample = sample
  end
end

#store_sample(sample) ⇒ Object

Samples can be stored in three places: the random sample variable, when random sampling is active, the developer mode slower than the current occupant of that slot



189
190
191
192
193
194
195
# File 'lib/new_relic/agent/transaction_sampler.rb', line 189

def store_sample(sample)
  @store_sampler_methods.each{|sym| send sym, sample}
  if NewRelic::Agent::TransactionInfo.get.force_persist_sample?(sample)
    store_force_persist(sample)
  end

end

#store_sample_for_developer_mode(sample) ⇒ Object

Samples take up a ton of memory, so we only store a lot of them in developer mode - we truncate to @max_samples



218
219
220
221
222
223
# File 'lib/new_relic/agent/transaction_sampler.rb', line 218

def store_sample_for_developer_mode(sample)
  return unless NewRelic::Control.instance.developer_mode?
  @samples = [] unless @samples
  @samples << sample
  truncate_samples
end

#store_slowest_sample(sample) ⇒ Object

Sets @slowest_sample to the passed in sample if it is slower than the current sample in @slowest_sample



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

def store_slowest_sample(sample)
  if slowest_sample?(@slowest_sample, sample)
    @slowest_sample = sample
  end
end

#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



298
299
300
301
302
303
304
# File 'lib/new_relic/agent/transaction_sampler.rb', line 298

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

#truncate_samplesObject

Smashes the @samples array down to the length of @max_samples by taking the last @max_samples elements of the array



241
242
243
244
245
# File 'lib/new_relic/agent/transaction_sampler.rb', line 241

def truncate_samples
  if @samples.length > @max_samples
    @samples = @samples[-@max_samples..-1]
  end
end