Class: OneApm::Agent::Threading::BacktraceService

Inherits:
Object
  • Object
show all
Defined in:
lib/one_apm/support/backtrace/backtrace_service.rb

Constant Summary collapse

OA_ALL_TRANSACTIONS =
"**ALL**".freeze
OA_MAX_BUFFER_LENGTH =
500

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(event_listener = nil) ⇒ BacktraceService

Returns a new instance of BacktraceService.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 18

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 = OneApm::Support::WorkerLoop.new

  # Memoize overhead % to avoid getting stale OR looked up every poll
  @overhead_percent_threshold = OneApm::Manager.config[:'xray_session.max_profile_overhead']
  OneApm::Manager.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

#bufferObject (readonly)

Returns the value of attribute buffer.



13
14
15
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 13

def buffer
  @buffer
end

#effective_polling_periodObject

Returns the value of attribute effective_polling_period.



13
14
15
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 13

def effective_polling_period
  @effective_polling_period
end

#overhead_percent_thresholdObject (readonly)

Returns the value of attribute overhead_percent_threshold.



13
14
15
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 13

def overhead_percent_threshold
  @overhead_percent_threshold
end

#profile_agent_codeObject

Returns the value of attribute profile_agent_code.



16
17
18
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 16

def profile_agent_code
  @profile_agent_code
end

#profilesObject (readonly)

This method is expected to be called with @lock held.



167
168
169
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 167

def profiles
  @profiles
end

#worker_loopObject (readonly)

Returns the value of attribute worker_loop.



13
14
15
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 13

def worker_loop
  @worker_loop
end

#worker_threadObject

Returns the value of attribute worker_thread.



16
17
18
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 16

def worker_thread
  @worker_thread
end

Class Method Details

.is_supported?Boolean

Returns:

  • (Boolean)


9
10
11
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 9

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



227
228
229
230
231
232
233
234
235
236
237
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 227

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.



117
118
119
120
121
122
123
124
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 117

def aggregate_backtraces(backtraces, name, start, duration, thread)
  end_time = start + duration
  backtraces.each do |(timestamp, backtrace)|
    if timestamp >= start && timestamp < 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.



197
198
199
200
201
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 197

def aggregate_global_backtrace(backtrace, bucket, thread)
  if @profiles[OA_ALL_TRANSACTIONS]
    @profiles[OA_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.



185
186
187
188
189
190
191
192
193
194
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 185

def buffer_backtrace_for_thread(thread, timestamp, backtrace, bucket)
  if should_buffer?(bucket)
    @buffer[thread] ||= []
    if @buffer[thread].length < OA_MAX_BUFFER_LENGTH
      @buffer[thread] << [timestamp, backtrace]
    else
      OneApm::Manager.increment_metric('Supportability/XraySessions/DroppedBacktraces')
    end
  end
end

#find_effective_polling_periodObject

This method is expected to be called with @lock held.



216
217
218
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 216

def find_effective_polling_period
  @profiles.values.map { |p| p.requested_period }.min
end

#harvest(transaction_name) ⇒ Object



90
91
92
93
94
95
96
97
98
99
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 90

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.

Returns:

  • (Boolean)


175
176
177
178
179
180
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 175

def need_backtrace?(bucket)
  (
    bucket != :ignore &&
    (@profiles[OA_ALL_TRANSACTIONS] || should_buffer?(bucket))
  )
end

#on_transaction_finished(payload) ⇒ Object



101
102
103
104
105
106
107
108
109
110
111
112
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 101

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

#pollObject



150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 150

def poll
  poll_start = Time.now

  @lock.synchronize do
    AgentThread.list.each do |thread|
      sample_thread(thread)
    end
    @profiles.each_value { |p| p.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



244
245
246
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 244

def record_polling_time(now, poll_start)
  OneApm::Manager.record_metric('Supportability/ThreadProfiler/PollingTime', now - poll_start)
end

#record_skew(poll_start) ⇒ Object



248
249
250
251
252
253
254
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 248

def record_skew(poll_start)
  if @last_poll
    skew = poll_start - @last_poll - worker_loop.period
    OneApm::Manager.record_metric('Supportability/ThreadProfiler/Skew', skew)
  end
  @last_poll = poll_start
end

#record_supportability_metrics(now, poll_start) ⇒ Object



239
240
241
242
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 239

def record_supportability_metrics(now, poll_start)
  record_polling_time(now, poll_start)
  record_skew(poll_start)
end

#running?Boolean

Public interface

Returns:

  • (Boolean)


42
43
44
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 42

def running?
  @running
end

#sample_thread(thread) ⇒ Object

This method is expected to be called with @lock held.



204
205
206
207
208
209
210
211
212
213
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 204

def sample_thread(thread)
  bucket = AgentThread.bucket_thread(thread, @profile_agent_code)

  if need_backtrace?(bucket)
    timestamp = Time.now.to_f
    backtrace = AgentThread.scrub_backtrace(thread, @profile_agent_code)
    aggregate_global_backtrace(backtrace, bucket, thread)
    buffer_backtrace_for_thread(thread, timestamp, backtrace, bucket)
  end
end

#should_buffer?(bucket) ⇒ Boolean

This method is expected to be called with @lock held.

Returns:

  • (Boolean)


170
171
172
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 170

def should_buffer?(bucket)
  bucket == :request && @profiles.keys.any? { |k| k != OA_ALL_TRANSACTIONS }
end

#should_profile_agent_code?Boolean

This method is expected to be called with @lock held.

Returns:

  • (Boolean)


221
222
223
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 221

def should_profile_agent_code?
  @profiles.values.any? { |p| p.profile_agent_code }
end

#startObject



126
127
128
129
130
131
132
133
134
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 126

def start
  return if @running || !self.class.is_supported?

  @running = true
  self.worker_thread = AgentThread.create('Backtrace Service') do
    # Not passing period because we expect it's already been set.
    self.worker_loop.run(&method(:poll))
  end
end

#stopObject

This method is expected to be called with @lock held



137
138
139
140
141
142
143
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 137

def stop
  return unless @running
  @running = false
  self.worker_loop.stop

  @buffer = {}
end

#subscribe(transaction_name, command_arguments = {}) ⇒ Object



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 46

def subscribe(transaction_name, command_arguments={})
  if !self.class.is_supported?
    OneApm::Manager.logger.debug("Backtracing not supported, so not subscribing transaction '#{transaction_name}'")
    return
  end

  OneApm::Manager.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

Returns:

  • (Boolean)


84
85
86
87
88
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 84

def subscribed?(transaction_name)
  @lock.synchronize do
    @profiles.has_key?(transaction_name)
  end
end

#unsubscribe(transaction_name) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 65

def unsubscribe(transaction_name)
  return unless self.class.is_supported?

  OneApm::Manager.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_profilesObject



79
80
81
82
# File 'lib/one_apm/support/backtrace/backtrace_service.rb', line 79

def update_values_from_profiles
  self.effective_polling_period = find_effective_polling_period
  self.profile_agent_code = should_profile_agent_code?
end