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.
-
#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'] ::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
#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
339 340 341 342 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 339 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
328 329 330 331 332 333 334 335 336 337 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 328 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
348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 348 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
297 298 299 300 301 302 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 297 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
287 288 289 290 291 292 293 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 287 def (, ) if + ";\n" + else end end |
#builder ⇒ Object
The current thread-local transaction sample builder
440 441 442 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 440 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
104 105 106 107 108 109 110 111 112 113 114 115 116 117 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 104 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
401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 401 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
446 447 448 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 446 def clear_builder Thread::current[BUILDER_KEY] = nil end |
#current_sample_id ⇒ Object
Returns the current sample id, delegated from ‘builder`
62 63 64 65 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 62 def current_sample_id b=builder b and b.sample_id end |
#enabled? ⇒ Boolean
67 68 69 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 67 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.
377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 377 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
236 237 238 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 236 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
83 84 85 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 83 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.
318 319 320 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 318 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
129 130 131 132 133 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 129 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
241 242 243 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 241 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
93 94 95 96 97 98 99 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 93 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`
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 142 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.
308 309 310 311 312 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 308 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
228 229 230 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 228 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
246 247 248 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 246 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
418 419 420 421 422 423 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 418 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
121 122 123 124 125 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 121 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
214 215 216 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 214 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
431 432 433 434 435 436 437 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 431 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
185 186 187 188 189 190 191 192 193 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 185 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
181 182 183 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 181 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
162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 162 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
197 198 199 200 201 202 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 197 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
206 207 208 209 210 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 206 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
277 278 279 280 281 282 283 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 277 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
220 221 222 223 224 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 220 def truncate_samples if @samples.length > @max_samples @samples = @samples[-@max_samples..-1] end end |