Class: OneApm::Collector::TransactionSampler
- Inherits:
-
Object
- Object
- OneApm::Collector::TransactionSampler
- Defined in:
- lib/one_apm/collector/containers/transaction_sampler.rb
Overview
This class contains the logic for recording and storing transaction traces (sometimes referred to as ‘transaction samples’).
A transaction trace is a detailed timeline of the events that happened during the processing of a single transaction, including database calls, template rendering calls, and other instrumented method calls.
Defined Under Namespace
Modules: Shim
Constant Summary collapse
- OA_MAX_DATA_LENGTH =
16384
Instance Attribute Summary collapse
- #cross_sample_buffer ⇒ Object readonly
- #dev_mode_sample_buffer ⇒ Object readonly
- #last_sample ⇒ Object readonly
- #xray_sample_buffer ⇒ Object readonly
Class Method Summary collapse
-
.truncate_message(message) ⇒ Object
Truncates the message to ‘OA_MAX_DATA_LENGTH` if needed, and appends an ellipsis because it makes the trucation clearer in the UI.
Instance Method Summary collapse
-
#add_segment_parameters(params) ⇒ Object
Set parameters on the current segment.
-
#append_backtrace(segment, duration) ⇒ Object
Appends a backtrace to a segment if that segment took longer than the specified duration.
- #build_database_statement(sql, config, explainer) ⇒ Object
- #count ⇒ Object
- #custom_parameters_from_transaction(txn) ⇒ Object
- #enabled? ⇒ Boolean
-
#harvest! ⇒ Object
Gather transaction traces that we’d like to transmit to the server.
- #harvest_from_sample_buffers ⇒ Object
-
#ignore_transaction(state) ⇒ Object
Tells the builder to ignore a transaction, if we are currently creating one.
-
#initialize(events = OneApm::Manager.agent.events) ⇒ TransactionSampler
constructor
A new instance of TransactionSampler.
- #merge!(previous) ⇒ Object
-
#notice_nosql(key, duration) ⇒ Object
Attaches an additional non-SQL query parameter to the current transaction trace segment.
- #notice_nosql_statement(statement, duration) ⇒ Object
-
#notice_pop_frame(state, frame, time = Time.now) ⇒ Object
Informs the transaction sample builder about the end of a traced frame.
-
#notice_push_frame(state, time = Time.now) ⇒ Object
This delegates to the builder to create a new open transaction segment for the frame, beginning at the optionally specified time.
-
#notice_sql(sql, config, duration, state = nil, &explainer) ⇒ Object
Attaches an SQL query on the current transaction trace segment.
-
#notice_transaction_cpu_time(state, cpu_time) ⇒ Object
Sets the CPU time used by a transaction, delegates to the builder.
-
#on_finishing_transaction(state, txn, time = Time.now, gc_time = nil) ⇒ Object
This is called when we are done with the transaction.
- #on_start_transaction(state, start_time, uri = nil) ⇒ Object
- #prepare_samples(samples) ⇒ Object
-
#reset! ⇒ Object
reset samples without rebooting the web server (used by dev mode).
-
#start_builder(state, 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_sample(sample) ⇒ Object
-
#tl_builder ⇒ Object
The current thread-local transaction sample builder.
Constructor Details
#initialize(events = OneApm::Manager.agent.events) ⇒ TransactionSampler
Returns a new instance of TransactionSampler.
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 60 61 62 63 64 65 66 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 34 def initialize(events = OneApm::Manager.agent.events) @xray_sample_buffer = OneApm::Transaction::XraySampleBuffer.new @dev_mode_sample_buffer = OneApm::Transaction::DeveloperModeSampleBuffer.new @cross_sample_buffer = OneApm::Transaction::CrossSampleBuffer.new(events) @sample_buffers = [] @sample_buffers << @cross_sample_buffer @sample_buffers << @xray_sample_buffer @sample_buffers << @dev_mode_sample_buffer @sample_buffers << OneApm::Transaction::SlowestSampleBuffer.new @sample_buffers << OneApm::Transaction::SyntheticsSampleBuffer.new @sample_buffers << OneApm::Transaction::ForcePersistSampleBuffer.new # 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 OneApm::Manager.config.register_callback(:'transaction_tracer.enabled') do |enabled| if enabled threshold = OneApm::Manager.config[:'transaction_tracer.transaction_threshold'] OneApm::Manager.logger.debug "Transaction tracing threshold is #{threshold} seconds." else OneApm::Manager.logger.debug "Transaction traces will not be sent to the OneApm service." end end OneApm::Manager.config.register_callback(:'transaction_tracer.record_sql') do |config| if config == 'raw' OneApm::Manager.logger.warn("Agent is configured to send raw SQL to the service") end end end |
Instance Attribute Details
#cross_sample_buffer ⇒ Object (readonly)
32 33 34 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 32 def cross_sample_buffer @cross_sample_buffer end |
#dev_mode_sample_buffer ⇒ Object (readonly)
32 33 34 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 32 def dev_mode_sample_buffer @dev_mode_sample_buffer end |
#last_sample ⇒ Object (readonly)
32 33 34 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 32 def last_sample @last_sample end |
#xray_sample_buffer ⇒ Object (readonly)
32 33 34 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 32 def xray_sample_buffer @xray_sample_buffer end |
Class Method Details
.truncate_message(message) ⇒ Object
Truncates the message to ‘OA_MAX_DATA_LENGTH` if needed, and appends an ellipsis because it makes the trucation clearer in the UI
200 201 202 203 204 205 206 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 200 def self.() if .length > (OA_MAX_DATA_LENGTH - 4) [0..OA_MAX_DATA_LENGTH - 4] + '...' else end end |
Instance Method Details
#add_segment_parameters(params) ⇒ Object
Set parameters on the current segment.
280 281 282 283 284 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 280 def add_segment_parameters(params) builder = tl_builder return unless builder params.each { |k,v| builder.current_segment[k] = v } end |
#append_backtrace(segment, duration) ⇒ Object
Appends a backtrace to a segment if that segment took longer than the specified duration
210 211 212 213 214 215 216 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 210 def append_backtrace(segment, duration) if duration >= OneApm::Manager.config[:'transaction_tracer.stack_trace_threshold'] backtrace = caller backtrace.reject! { |t| t.include?('one_apm') } segment[:backtrace] = backtrace.join("\n") end end |
#build_database_statement(sql, config, explainer) ⇒ Object
243 244 245 246 247 248 249 250 251 252 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 243 def build_database_statement(sql, config, explainer) statement = OneApm::Agent::Database::Statement.new(OneApm::Agent::Database.capture_query(sql)) if config statement.adapter = config[:adapter] statement.config = config end statement.explainer = explainer statement end |
#count ⇒ Object
317 318 319 320 321 322 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 317 def count @samples_lock.synchronize do samples = @sample_buffers.inject([]) { |all, b| all.concat(b.samples) } samples.uniq.size end end |
#custom_parameters_from_transaction(txn) ⇒ Object
104 105 106 107 108 109 110 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 104 def custom_parameters_from_transaction(txn) if OneApm::Manager.config[:'transaction_tracer.capture_attributes'] txn.custom_parameters else {} end end |
#enabled? ⇒ Boolean
68 69 70 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 68 def enabled? OneApm::Manager.config[:'transaction_tracer.enabled'] end |
#harvest! ⇒ Object
Gather transaction traces that we’d like to transmit to the server.
287 288 289 290 291 292 293 294 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 287 def harvest! return [] unless enabled? samples = @samples_lock.synchronize do @last_sample = nil harvest_from_sample_buffers end prepare_samples(samples) end |
#harvest_from_sample_buffers ⇒ Object
324 325 326 327 328 329 330 331 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 324 def harvest_from_sample_buffers # map + flatten hit mocking issues calling to_ary on 1.9.2. We only # want a single level flatten anyway, but, as you probably already # know, Ruby 1.8.6 :/ result = [] @sample_buffers.each { |buffer| result.concat(buffer.harvest_samples) } result.uniq end |
#ignore_transaction(state) ⇒ 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
165 166 167 168 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 165 def ignore_transaction(state) builder = state.transaction_sample_builder builder.ignore_transaction if builder end |
#merge!(previous) ⇒ Object
309 310 311 312 313 314 315 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 309 def merge!(previous) @samples_lock.synchronize do @sample_buffers.each do |buffer| buffer.store_previous(previous) end end end |
#notice_nosql(key, duration) ⇒ Object
Attaches an additional non-SQL query parameter to the current transaction trace segment.
This may be used for recording a query against a key-value store like memcached or redis.
This method should be used only by gem authors wishing to extend the Ruby agent to instrument uninstrumented key-value stores - it should generally not be called directly from application code.
269 270 271 272 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 269 def notice_nosql(key, duration) builder = tl_builder notice_extra_data(builder, key, duration, :key) end |
#notice_nosql_statement(statement, duration) ⇒ Object
274 275 276 277 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 274 def notice_nosql_statement(statement, duration) builder = tl_builder notice_extra_data(builder, statement, duration, :statement) end |
#notice_pop_frame(state, frame, time = Time.now) ⇒ Object
Informs the transaction sample builder about the end of a traced frame
97 98 99 100 101 102 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 97 def notice_pop_frame(state, frame, time = Time.now) builder = state.transaction_sample_builder return unless builder raise "finished already???" if builder.sample.finished builder.trace_exit(frame, time.to_f) end |
#notice_push_frame(state, time = Time.now) ⇒ Object
This delegates to the builder to create a new open transaction segment for the frame, 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
85 86 87 88 89 90 91 92 93 94 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 85 def notice_push_frame(state, time=Time.now) builder = state.transaction_sample_builder return unless builder segment = builder.trace_entry(time.to_f) if @dev_mode_sample_buffer @dev_mode_sample_buffer.visit_segment(segment) end segment end |
#notice_sql(sql, config, duration, state = nil, &explainer) ⇒ Object
Attaches an SQL query on the current transaction trace segment.
This method should be used only by gem authors wishing to extend the Ruby agent to instrument new database interfaces - it should generally not be called directly from application code.
232 233 234 235 236 237 238 239 240 241 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 232 def notice_sql(sql, config, duration, state=nil, &explainer) # some statements (particularly INSERTS with large BLOBS # may be very large; we should trim them to a maximum usable length state ||= OneApm::TransactionState.tl_get builder = state.transaction_sample_builder if state.is_sql_recorded? statement = build_database_statement(sql, config, explainer) notice_extra_data(builder, statement, duration, :sql) end end |
#notice_transaction_cpu_time(state, cpu_time) ⇒ Object
Sets the CPU time used by a transaction, delegates to the builder
171 172 173 174 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 171 def notice_transaction_cpu_time(state, cpu_time) builder = state.transaction_sample_builder builder.set_transaction_cpu_time(cpu_time) if builder end |
#on_finishing_transaction(state, txn, time = Time.now, gc_time = nil) ⇒ 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 frames appended to it.
It sets various instance variables to the finished sample, depending on which settings are active. See ‘store_sample`
119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 119 def on_finishing_transaction(state, txn, time=Time.now, gc_time=nil) last_builder = state.transaction_sample_builder return unless last_builder && enabled? state.transaction_sample_builder = nil return if last_builder.ignored? last_builder.set_request_params(txn.filtered_params) last_builder.set_transaction_name(txn.best_name) last_builder.finish_trace(time.to_f, custom_parameters_from_transaction(txn)) last_sample = last_builder.sample last_sample.guid = txn.guid last_sample.set_custom_param(:gc_time, gc_time) if gc_time last_sample.force_persist = txn.apdex_bucket(time - txn.apdex_start) != :apdex_s if state.is_cross_app? last_sample.set_custom_param(:'bw.trip_id', txn.cat_trip_id(state)) last_sample.set_custom_param(:'bw.path_hash', txn.cat_path_hash(state)) end if txn.is_synthetics_request? last_sample.set_custom_param(:'bw.synthetics_resource_id', txn.synthetics_resource_id) last_sample.set_custom_param(:'bw.synthetics_job_id', txn.synthetics_job_id) last_sample.set_custom_param(:'bw.synthetics_monitor_id', txn.synthetics_monitor_id) last_sample.synthetics_resource_id = txn.synthetics_resource_id end @samples_lock.synchronize do @last_sample = last_sample store_sample(@last_sample) @last_sample end end |
#on_start_transaction(state, start_time, uri = nil) ⇒ Object
72 73 74 75 76 77 78 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 72 def on_start_transaction(state, start_time, uri=nil) if enabled? start_builder(state, start_time.to_f) builder = state.transaction_sample_builder builder.set_transaction_uri(uri) if builder end end |
#prepare_samples(samples) ⇒ Object
296 297 298 299 300 301 302 303 304 305 306 307 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 296 def prepare_samples(samples) samples.select do |sample| begin sample.prepare_to_send! rescue => e OneApm::Manager.logger.error("Failed to prepare transaction trace. Error: ", e) false else true end end end |
#reset! ⇒ Object
reset samples without rebooting the web server (used by dev mode)
334 335 336 337 338 339 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 334 def reset! @samples_lock.synchronize do @last_sample = nil @sample_buffers.each { |sample_buffer| sample_buffer.reset! } end end |
#start_builder(state, 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
347 348 349 350 351 352 353 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 347 def start_builder(state, time=nil) if !enabled? || !state.is_transaction_traced? || !state.is_execution_traced? state.transaction_sample_builder = nil else state.transaction_sample_builder ||= OneApm::TransactionSampleBuilder.new(time) end end |
#store_sample(sample) ⇒ Object
155 156 157 158 159 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 155 def store_sample(sample) @sample_buffers.each do |sample_buffer| sample_buffer.store(sample) end end |
#tl_builder ⇒ Object
The current thread-local transaction sample builder
356 357 358 |
# File 'lib/one_apm/collector/containers/transaction_sampler.rb', line 356 def tl_builder OneApm::TransactionState.tl_get.transaction_sample_builder end |