Class: NewRelic::Agent::Threading::BacktraceService
- Inherits:
-
Object
- Object
- NewRelic::Agent::Threading::BacktraceService
- Defined in:
- lib/new_relic/agent/threading/backtrace_service.rb
Constant Summary collapse
- ALL_TRANSACTIONS =
"**ALL**".freeze
- MAX_BUFFER_LENGTH =
500
Instance Attribute Summary collapse
-
#buffer ⇒ Object
readonly
Returns the value of attribute buffer.
-
#effective_polling_period ⇒ Object
Returns the value of attribute effective_polling_period.
-
#overhead_percent_threshold ⇒ Object
readonly
Returns the value of attribute overhead_percent_threshold.
-
#profile_agent_code ⇒ Object
Returns the value of attribute profile_agent_code.
-
#profiles ⇒ Object
readonly
This method is expected to be called with @lock held.
-
#worker_loop ⇒ Object
readonly
Returns the value of attribute worker_loop.
-
#worker_thread ⇒ Object
Returns the value of attribute worker_thread.
Class Method Summary collapse
Instance Method Summary collapse
-
#adjust_polling_time(now, poll_start) ⇒ Object
If our overhead % exceeds the threshold, bump the next poll period relative to how much larger our overhead is than allowed.
-
#aggregate_backtraces(backtraces, name, start, duration, thread) ⇒ Object
This method is expected to be called with @lock held.
-
#aggregate_global_backtrace(backtrace, bucket, thread) ⇒ Object
This method is expected to be called with @lock held.
-
#buffer_backtrace_for_thread(thread, timestamp, backtrace, bucket) ⇒ Object
This method is expected to be called with @lock held.
-
#find_effective_polling_period ⇒ Object
This method is expected to be called with @lock held.
- #harvest(transaction_name) ⇒ Object
-
#initialize(event_listener = nil) ⇒ BacktraceService
constructor
A new instance of BacktraceService.
-
#need_backtrace?(bucket) ⇒ Boolean
This method is expected to be called with @lock held.
- #on_transaction_finished(payload) ⇒ Object
- #poll ⇒ Object
- #record_polling_time(now, poll_start) ⇒ Object
- #record_skew(poll_start) ⇒ Object
- #record_supportability_metrics(now, poll_start) ⇒ Object
-
#running? ⇒ Boolean
Public interface.
-
#sample_thread(thread) ⇒ Object
This method is expected to be called with @lock held.
-
#should_buffer?(bucket) ⇒ Boolean
This method is expected to be called with @lock held.
-
#should_profile_agent_code? ⇒ Boolean
This method is expected to be called with @lock held.
- #start ⇒ Object
-
#stop ⇒ Object
This method is expected to be called with @lock held.
- #subscribe(transaction_name, command_arguments = {}) ⇒ Object
- #subscribed?(transaction_name) ⇒ Boolean
- #unsubscribe(transaction_name) ⇒ Object
- #update_values_from_profiles ⇒ Object
Constructor Details
#initialize(event_listener = nil) ⇒ BacktraceService
Returns a new instance of BacktraceService.
19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 19 def initialize(event_listener=nil) @profiles = {} @buffer = {} # synchronizes access to @profiles and @buffer above @lock = Mutex.new @running = false @profile_agent_code = false @worker_loop = NewRelic::Agent::WorkerLoop.new # Memoize overhead % to avoid getting stale OR looked up every poll @overhead_percent_threshold = NewRelic::Agent.config[:'xray_session.max_profile_overhead'] NewRelic::Agent.config.register_callback(:'xray_session.max_profile_overhead') do |new_value| @overhead_percent_threshold = new_value end if event_listener event_listener.subscribe(:transaction_finished, &method(:on_transaction_finished)) end end |
Instance Attribute Details
#buffer ⇒ Object (readonly)
Returns the value of attribute buffer.
15 16 17 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 15 def buffer @buffer end |
#effective_polling_period ⇒ Object
Returns the value of attribute effective_polling_period.
15 16 17 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 15 def effective_polling_period @effective_polling_period end |
#overhead_percent_threshold ⇒ Object (readonly)
Returns the value of attribute overhead_percent_threshold.
15 16 17 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 15 def overhead_percent_threshold @overhead_percent_threshold end |
#profile_agent_code ⇒ Object
Returns the value of attribute profile_agent_code.
17 18 19 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 17 def profile_agent_code @profile_agent_code end |
#profiles ⇒ Object (readonly)
This method is expected to be called with @lock held.
172 173 174 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 172 def profiles @profiles end |
#worker_loop ⇒ Object (readonly)
Returns the value of attribute worker_loop.
15 16 17 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 15 def worker_loop @worker_loop end |
#worker_thread ⇒ Object
Returns the value of attribute worker_thread.
17 18 19 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 17 def worker_thread @worker_thread end |
Class Method Details
.is_supported? ⇒ Boolean
11 12 13 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 11 def self.is_supported? RUBY_VERSION >= "1.9.2" end |
Instance Method Details
#adjust_polling_time(now, poll_start) ⇒ Object
If our overhead % exceeds the threshold, bump the next poll period relative to how much larger our overhead is than allowed
232 233 234 235 236 237 238 239 240 241 242 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 232 def adjust_polling_time(now, poll_start) duration = now - poll_start overhead_percent = duration / effective_polling_period if overhead_percent > self.overhead_percent_threshold scale_up_by = overhead_percent / self.overhead_percent_threshold worker_loop.period = effective_polling_period * scale_up_by else worker_loop.period = effective_polling_period end end |
#aggregate_backtraces(backtraces, name, start, duration, thread) ⇒ Object
This method is expected to be called with @lock held.
118 119 120 121 122 123 124 125 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 118 def aggregate_backtraces(backtraces, name, start, duration, thread) end_time = start + duration backtraces.each do |(, backtrace)| if >= start && < end_time @profiles[name].aggregate(backtrace, :request, thread) end end end |
#aggregate_global_backtrace(backtrace, bucket, thread) ⇒ Object
This method is expected to be called with @lock held.
202 203 204 205 206 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 202 def aggregate_global_backtrace(backtrace, bucket, thread) if @profiles[ALL_TRANSACTIONS] @profiles[ALL_TRANSACTIONS].aggregate(backtrace, bucket, thread) end end |
#buffer_backtrace_for_thread(thread, timestamp, backtrace, bucket) ⇒ Object
This method is expected to be called with @lock held.
190 191 192 193 194 195 196 197 198 199 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 190 def buffer_backtrace_for_thread(thread, , backtrace, bucket) if should_buffer?(bucket) @buffer[thread] ||= [] if @buffer[thread].length < MAX_BUFFER_LENGTH @buffer[thread] << [, backtrace] else NewRelic::Agent.increment_metric('Supportability/XraySessions/DroppedBacktraces') end end end |
#find_effective_polling_period ⇒ Object
This method is expected to be called with @lock held.
221 222 223 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 221 def find_effective_polling_period @profiles.values.map { |p| p.requested_period }.min end |
#harvest(transaction_name) ⇒ Object
91 92 93 94 95 96 97 98 99 100 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 91 def harvest(transaction_name) @lock.synchronize do if @profiles[transaction_name] profile = @profiles.delete(transaction_name) profile.finished_at = Time.now @profiles[transaction_name] = ThreadProfile.new(profile.command_arguments) profile end end end |
#need_backtrace?(bucket) ⇒ Boolean
This method is expected to be called with @lock held.
180 181 182 183 184 185 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 180 def need_backtrace?(bucket) ( bucket != :ignore && (@profiles[ALL_TRANSACTIONS] || should_buffer?(bucket)) ) end |
#on_transaction_finished(payload) ⇒ Object
102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 102 def on_transaction_finished(payload) name = payload[:name] start = payload[:start_timestamp] duration = payload[:duration] thread = payload[:thread] || Thread.current @lock.synchronize do backtraces = @buffer.delete(thread) if backtraces && @profiles.has_key?(name) aggregate_backtraces(backtraces, name, start, duration, thread) end end end |
#poll ⇒ Object
155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 155 def poll poll_start = Time.now @lock.synchronize do AgentThread.list.each do |thread| sample_thread(thread) end @profiles.values.each { |c| c.increment_poll_count } @buffer.delete_if { |thread, _| !thread.alive? } end end_time = Time.now adjust_polling_time(end_time, poll_start) record_supportability_metrics(end_time, poll_start) end |
#record_polling_time(now, poll_start) ⇒ Object
249 250 251 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 249 def record_polling_time(now, poll_start) NewRelic::Agent.record_metric('Supportability/ThreadProfiler/PollingTime', now - poll_start) end |
#record_skew(poll_start) ⇒ Object
253 254 255 256 257 258 259 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 253 def record_skew(poll_start) if @last_poll skew = poll_start - @last_poll - worker_loop.period NewRelic::Agent.record_metric('Supportability/ThreadProfiler/Skew', skew) end @last_poll = poll_start end |
#record_supportability_metrics(now, poll_start) ⇒ Object
244 245 246 247 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 244 def record_supportability_metrics(now, poll_start) record_polling_time(now, poll_start) record_skew(poll_start) end |
#running? ⇒ Boolean
Public interface
43 44 45 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 43 def running? @running end |
#sample_thread(thread) ⇒ Object
This method is expected to be called with @lock held.
209 210 211 212 213 214 215 216 217 218 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 209 def sample_thread(thread) bucket = AgentThread.bucket_thread(thread, @profile_agent_code) if need_backtrace?(bucket) = Time.now.to_f backtrace = AgentThread.scrub_backtrace(thread, @profile_agent_code) aggregate_global_backtrace(backtrace, bucket, thread) buffer_backtrace_for_thread(thread, , backtrace, bucket) end end |
#should_buffer?(bucket) ⇒ Boolean
This method is expected to be called with @lock held.
175 176 177 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 175 def should_buffer?(bucket) bucket == :request && @profiles.keys.any? { |k| k != ALL_TRANSACTIONS } end |
#should_profile_agent_code? ⇒ Boolean
This method is expected to be called with @lock held.
226 227 228 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 226 def should_profile_agent_code? @profiles.values.any? { |p| p.profile_agent_code } end |
#start ⇒ Object
127 128 129 130 131 132 133 134 135 136 137 138 139 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 127 def start return if @running || !self.class.is_supported? @running = true self.worker_thread = AgentThread.new('Backtrace Service') do begin # Not passing period because we expect it's already been set. self.worker_loop.run(&method(:poll)) ensure NewRelic::Agent.logger.debug("Exiting New Relic thread: Backtrace Service") end end end |
#stop ⇒ Object
This method is expected to be called with @lock held
142 143 144 145 146 147 148 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 142 def stop return unless @running @running = false self.worker_loop.stop @buffer = {} end |
#subscribe(transaction_name, command_arguments = {}) ⇒ Object
47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 47 def subscribe(transaction_name, command_arguments={}) if !self.class.is_supported? NewRelic::Agent.logger.debug("Backtracing not supported, so not subscribing transaction '#{transaction_name}'") return end NewRelic::Agent.logger.debug("Backtrace Service subscribing transaction '#{transaction_name}'") profile = ThreadProfile.new(command_arguments) @lock.synchronize do @profiles[transaction_name] = profile update_values_from_profiles end start profile end |
#subscribed?(transaction_name) ⇒ Boolean
85 86 87 88 89 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 85 def subscribed?(transaction_name) @lock.synchronize do @profiles.has_key?(transaction_name) end end |
#unsubscribe(transaction_name) ⇒ Object
66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 66 def unsubscribe(transaction_name) return unless self.class.is_supported? NewRelic::Agent.logger.debug("Backtrace Service unsubscribing transaction '#{transaction_name}'") @lock.synchronize do @profiles.delete(transaction_name) if @profiles.empty? stop else update_values_from_profiles end end end |
#update_values_from_profiles ⇒ Object
80 81 82 83 |
# File 'lib/new_relic/agent/threading/backtrace_service.rb', line 80 def update_values_from_profiles self.effective_polling_period = find_effective_polling_period self.profile_agent_code = should_profile_agent_code? end |