Class: NewRelic::Agent::TransactionSampler
- Inherits:
-
Object
- Object
- NewRelic::Agent::TransactionSampler
- 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
-
#disabled ⇒ Object
readonly
Returns the value of attribute disabled.
-
#last_sample ⇒ Object
readonly
Returns the value of attribute last_sample.
-
#random_sampling ⇒ Object
Returns the value of attribute random_sampling.
-
#samples ⇒ Object
readonly
Returns the value of attribute samples.
-
#sampling_rate ⇒ Object
Returns the value of attribute sampling_rate.
-
#slow_capture_threshold ⇒ Object
Returns the value of attribute slow_capture_threshold.
Instance Method Summary collapse
- #add_force_persist_to(result) ⇒ Object
-
#add_random_sample_to(result) ⇒ Object
Every 1/n harvests, adds the most recent sample to the harvest array if it exists.
-
#add_samples_to(result) ⇒ Object
Returns an array of slow samples, with either one or two elements - one element unless random sampling is enabled.
-
#append_backtrace(segment, duration) ⇒ Object
Appends a backtrace to a segment if that segment took longer than the specified duration.
-
#append_new_message(old_message, message) ⇒ Object
Allows the addition of multiple pieces of metadata to one segment - i.e.
-
#builder ⇒ Object
The current thread-local transaction sample builder.
-
#capture_segment_trace ⇒ Object
in developer mode, capture the stack trace with the segment.
-
#clamp_number_tts(tts, limit) ⇒ Object
JON - THIS CODE NEEDS A UNIT TEST.
-
#clear_builder ⇒ Object
Sets the thread local variable storing the transaction sample builder to nil to clear it.
-
#current_sample_id ⇒ Object
Returns the current sample id, delegated from ‘builder`.
- #enabled? ⇒ Boolean
-
#harvest(previous = []) ⇒ Object
get the set of collected samples, merging into previous samples, and clear the collected sample list.
-
#ignore_transaction ⇒ Object
Tells the builder to ignore a transaction, if we are currently creating one.
-
#initialize ⇒ TransactionSampler
constructor
A new instance of TransactionSampler.
- #log ⇒ Object
-
#notice_first_scope_push(time) ⇒ Object
Creates a new transaction sample builder, unless the transaction sampler is disabled.
-
#notice_nosql(key, duration) ⇒ Object
Adds non-sql metadata to a segment - generally the memcached key.
-
#notice_pop_scope(scope, time = Time.now) ⇒ Object
Informs the transaction sample builder about the end of a traced scope.
-
#notice_profile(profile) ⇒ Object
For developer mode profiling support - delegates to the builder.
-
#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.
-
#notice_scope_empty(time = Time.now) ⇒ Object
This is called when we are done with the transaction.
-
#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..
-
#notice_transaction(path, uri = nil, params = {}) ⇒ Object
Delegates to the builder to store the path, uri, and parameters if the sampler is active.
-
#notice_transaction_cpu_time(cpu_time) ⇒ Object
Sets the CPU time used by a transaction, delegates to the builder.
-
#reset! ⇒ Object
reset samples without rebooting the web server.
-
#scope_depth ⇒ Object
Defaults to zero, otherwise delegated to the transaction sample builder.
-
#slowest_sample?(old_sample, new_sample) ⇒ Boolean
Checks to see if the old sample exists, or if its duration is less than the new sample.
-
#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.
- #store_force_persist(sample) ⇒ Object
-
#store_random_sample(sample) ⇒ Object
Only active when random sampling is true - this is very rarely used.
-
#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.
-
#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.
-
#store_slowest_sample(sample) ⇒ Object
Sets @slowest_sample to the passed in sample if it is slower than the current sample in @slowest_sample.
-
#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.
-
#truncate_samples ⇒ Object
Smashes the @samples array down to the length of @max_samples by taking the last @max_samples elements of the array.
Constructor Details
#initialize ⇒ TransactionSampler
Returns a new instance of TransactionSampler.
26 27 28 29 30 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 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 26 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 = Agent.config[:sample_rate] # 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'] log.debug "Transaction tracing threshold is #{threshold} seconds." else log.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' log.warn("Agent is configured to send raw SQL to the service") end end end |
Instance Attribute Details
#disabled ⇒ Object (readonly)
Returns the value of attribute disabled.
24 25 26 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 24 def disabled @disabled end |
#last_sample ⇒ Object (readonly)
Returns the value of attribute last_sample.
24 25 26 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 24 def last_sample @last_sample end |
#random_sampling ⇒ Object
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 |
#samples ⇒ Object (readonly)
Returns the value of attribute samples.
24 25 26 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 24 def samples @samples end |
#sampling_rate ⇒ Object
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_threshold ⇒ Object
Returns the value of attribute slow_capture_threshold.
23 24 25 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 23 def slow_capture_threshold @slow_capture_threshold end |
Instance Method Details
#add_force_persist_to(result) ⇒ Object
343 344 345 346 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 343 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
332 333 334 335 336 337 338 339 340 341 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 332 def add_random_sample_to(result) return unless @random_sample && Agent.config[:sample_rate] && Agent.config[:sample_rate].to_i > 0 @harvest_count += 1 if (@harvest_count.to_i % Agent.config[:sample_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) ⇒ 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
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 352 def add_samples_to(result) # 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 >= Agent.config[:'transaction_tracer.transaction_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
301 302 303 304 305 306 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 301 def append_backtrace(segment, duration) if (duration >= Agent.config[:'transaction_tracer.stack_trace_threshold'] || Thread.current[:capture_deep_tt]) 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
291 292 293 294 295 296 297 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 291 def (, ) if + ";\n" + else end end |
#builder ⇒ Object
The current thread-local transaction sample builder
444 445 446 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 444 def builder Thread::current[BUILDER_KEY] end |
#capture_segment_trace ⇒ Object
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
108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 108 def capture_segment_trace return unless Agent.config[: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
405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 405 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_builder ⇒ Object
Sets the thread local variable storing the transaction sample builder to nil to clear it
450 451 452 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 450 def clear_builder Thread::current[BUILDER_KEY] = nil end |
#current_sample_id ⇒ Object
Returns the current sample id, delegated from ‘builder`
66 67 68 69 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 66 def current_sample_id b=builder b and b.sample_id end |
#enabled? ⇒ Boolean
71 72 73 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 71 def enabled? Agent.config[:'transaction_tracer.enabled'] || Agent.config[:developer_mode] end |
#harvest(previous = []) ⇒ 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.
381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 381 def harvest(previous=[]) return [] if !enabled? result = Array(previous) @samples_lock.synchronize do result = add_samples_to(result) # 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(Agent.config[:'transaction_tracer.limit_segments']) } result end |
#ignore_transaction ⇒ Object
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
240 241 242 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 240 def ignore_transaction builder.ignore_transaction if builder end |
#log ⇒ Object
61 62 63 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 61 def log NewRelic::Control.instance.log 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
87 88 89 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 87 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.
322 323 324 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 322 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
133 134 135 136 137 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 133 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
245 246 247 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 245 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
97 98 99 100 101 102 103 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 97 def notice_push_scope(scope, time=Time.now) return unless builder builder.trace_entry(scope, time.to_f) capture_segment_trace if Agent.config[: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`
146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 146 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.
312 313 314 315 316 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 312 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
232 233 234 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 232 def notice_transaction(path, uri=nil, params={}) builder.set_transaction_info(path, 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
250 251 252 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 250 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
422 423 424 425 426 427 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 422 def reset! @samples = [] @last_sample = nil @random_sample = nil @slowest_sample = nil end |
#scope_depth ⇒ Object
Defaults to zero, otherwise delegated to the transaction sample builder
125 126 127 128 129 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 125 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 its duration is less than the new sample
218 219 220 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 218 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
435 436 437 438 439 440 441 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 435 def start_builder(time=nil) if !enabled? || !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
189 190 191 192 193 194 195 196 197 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 189 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
185 186 187 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 185 def store_random_sample(sample) @random_sample = sample if Agent.config[:'transaction_tracer.random_sample'] 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
166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 166 def store_sample(sample) sampler_methods = [ :store_slowest_sample ] if Agent.config[:developer_mode] sampler_methods << :store_sample_for_developer_mode end if Agent.config[:'transaction_tracer.random_sample'] sampler_methods << :store_random_sample end 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
201 202 203 204 205 206 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 201 def store_sample_for_developer_mode(sample) return unless Agent.config[: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
210 211 212 213 214 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 210 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
281 282 283 284 285 286 287 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 281 def () if .length > (MAX_DATA_LENGTH - 4) [0..MAX_DATA_LENGTH - 4] + '...' else end end |
#truncate_samples ⇒ Object
Smashes the @samples array down to the length of @max_samples by taking the last @max_samples elements of the array
224 225 226 227 228 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 224 def truncate_samples if @samples.length > @max_samples @samples = @samples[-@max_samples..-1] end end |