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.
-
#explain_enabled ⇒ Object
Returns the value of attribute explain_enabled.
-
#explain_threshold ⇒ Object
Returns the value of attribute explain_threshold.
-
#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.
-
#stack_trace_threshold ⇒ Object
Returns the value of attribute stack_trace_threshold.
-
#transaction_threshold ⇒ Object
Returns the value of attribute transaction_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, slow_threshold) ⇒ 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.
- #config ⇒ Object
- #configure! ⇒ Object
-
#current_sample_id ⇒ Object
Returns the current sample id, delegated from ‘builder`.
-
#disable ⇒ Object
Disable the transaction sampler - this also deregisters it with the statistics engine.
-
#enable ⇒ Object
Enable the transaction sampler - this also registers it with the statistics engine.
- #enabled? ⇒ Boolean
-
#harvest(previous = [], slow_threshold = 2.0) ⇒ 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 it’s 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.
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
#disabled ⇒ Object (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_enabled ⇒ Object
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_threshold ⇒ Object
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_sample ⇒ Object (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_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.
25 26 27 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 25 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.
24 25 26 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 24 def slow_capture_threshold @slow_capture_threshold end |
#stack_trace_threshold ⇒ Object
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_threshold ⇒ Object
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 (, ) if + ";\n" + else end end |
#builder ⇒ Object
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_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
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_builder ⇒ Object
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 |
#config ⇒ Object
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_id ⇒ Object
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 |
#disable ⇒ Object
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 |
#enable ⇒ Object
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
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_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
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_depth ⇒ Object
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
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 () 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
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 |