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.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
# 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 = 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.
24 25 26 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 24 def disabled @disabled end |
#explain_enabled ⇒ Object
Returns the value of attribute explain_enabled.
22 23 24 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 22 def explain_enabled @explain_enabled end |
#explain_threshold ⇒ Object
Returns the value of attribute explain_threshold.
22 23 24 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 22 def explain_threshold @explain_threshold 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.
21 22 23 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 21 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.
21 22 23 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 21 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 |
#stack_trace_threshold ⇒ Object
Returns the value of attribute stack_trace_threshold.
21 22 23 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 21 def stack_trace_threshold @stack_trace_threshold end |
#transaction_threshold ⇒ Object
Returns the value of attribute transaction_threshold.
22 23 24 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 22 def transaction_threshold @transaction_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
334 335 336 337 338 339 340 341 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 334 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 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
352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 352 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
306 307 308 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 306 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
296 297 298 299 300 301 302 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 296 def (, ) if + ";\n" + else end end |
#builder ⇒ Object
The current thread-local transaction sample builder
443 444 445 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 443 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
122 123 124 125 126 127 128 129 130 131 132 133 134 135 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 122 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
404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 404 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
449 450 451 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 449 def clear_builder Thread::current[BUILDER_KEY] = nil end |
#config ⇒ Object
61 62 63 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 61 def config NewRelic::Control.instance.fetch('transaction_tracer', {}) end |
#configure! ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 47 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) 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 |
#disable ⇒ Object
Disable the transaction sampler - this also deregisters it with the statistics engine.
80 81 82 83 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 80 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.
73 74 75 76 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 73 def enable @disabled = false NewRelic::Agent.instance.stats_engine.transaction_sampler = self end |
#enabled? ⇒ Boolean
85 86 87 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 85 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.
380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 380 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
251 252 253 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 251 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
101 102 103 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 101 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.
324 325 326 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 324 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
147 148 149 150 151 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 147 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
256 257 258 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 256 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
111 112 113 114 115 116 117 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 111 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`
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 160 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.
314 315 316 317 318 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 314 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
243 244 245 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 243 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
261 262 263 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 261 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
421 422 423 424 425 426 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 421 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
139 140 141 142 143 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 139 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
229 230 231 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 229 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
434 435 436 437 438 439 440 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 434 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
200 201 202 203 204 205 206 207 208 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 200 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
194 195 196 197 198 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 194 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
181 182 183 184 185 186 187 188 189 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 181 def store_sample(sample) store_random_sample(sample) store_sample_for_developer_mode(sample) store_slowest_sample(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
212 213 214 215 216 217 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 212 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
221 222 223 224 225 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 221 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
286 287 288 289 290 291 292 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 286 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
235 236 237 238 239 |
# File 'lib/new_relic/agent/transaction_sampler.rb', line 235 def truncate_samples if @samples.length > @max_samples @samples = @samples[-@max_samples..-1] end end |