newrelic_rpm 3.6.7.159 → 3.6.8.164
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +14 -0
- data/lib/new_relic/agent/agent.rb +38 -35
- data/lib/new_relic/agent/agent_logger.rb +6 -47
- data/lib/new_relic/agent/beacon_configuration.rb +10 -4
- data/lib/new_relic/agent/browser_monitoring.rb +39 -33
- data/lib/new_relic/agent/commands/agent_command.rb +4 -4
- data/lib/new_relic/agent/commands/agent_command_router.rb +72 -10
- data/lib/new_relic/agent/commands/thread_profiler_session.rb +110 -0
- data/lib/new_relic/agent/commands/xray_session.rb +55 -0
- data/lib/new_relic/agent/commands/xray_session_collection.rb +158 -0
- data/lib/new_relic/agent/configuration/default_source.rb +61 -24
- data/lib/new_relic/agent/configuration/mask_defaults.rb +2 -2
- data/lib/new_relic/agent/configuration/server_source.rb +1 -1
- data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +2 -0
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +4 -10
- data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +10 -11
- data/lib/new_relic/agent/memory_logger.rb +52 -0
- data/lib/new_relic/agent/new_relic_service.rb +4 -0
- data/lib/new_relic/agent/request_sampler.rb +32 -13
- data/lib/new_relic/agent/samplers/cpu_sampler.rb +6 -3
- data/lib/new_relic/agent/threading/agent_thread.rb +2 -1
- data/lib/new_relic/agent/threading/backtrace_node.rb +80 -27
- data/lib/new_relic/agent/threading/backtrace_service.rb +264 -0
- data/lib/new_relic/agent/threading/thread_profile.rb +79 -118
- data/lib/new_relic/agent/transaction/developer_mode_sample_buffer.rb +56 -0
- data/lib/new_relic/agent/transaction/force_persist_sample_buffer.rb +25 -0
- data/lib/new_relic/agent/transaction/slowest_sample_buffer.rb +25 -0
- data/lib/new_relic/agent/transaction/transaction_sample_buffer.rb +86 -0
- data/lib/new_relic/agent/transaction/xray_sample_buffer.rb +64 -0
- data/lib/new_relic/agent/transaction.rb +25 -4
- data/lib/new_relic/agent/transaction_sample_builder.rb +6 -10
- data/lib/new_relic/agent/transaction_sampler.rb +47 -202
- data/lib/new_relic/agent/worker_loop.rb +47 -39
- data/lib/new_relic/agent.rb +1 -1
- data/lib/new_relic/build.rb +2 -2
- data/lib/new_relic/coerce.rb +8 -0
- data/lib/new_relic/control/instance_methods.rb +1 -0
- data/lib/new_relic/rack/browser_monitoring.rb +15 -1
- data/lib/new_relic/rack/developer_mode.rb +1 -1
- data/lib/new_relic/transaction_sample.rb +20 -5
- data/lib/new_relic/version.rb +1 -1
- data/newrelic.yml +4 -6
- data/newrelic_rpm.gemspec +1 -1
- data/test/agent_helper.rb +11 -0
- data/test/environments/lib/environments/runner.rb +5 -1
- data/test/environments/rails21/Gemfile +2 -2
- data/test/environments/rails22/Gemfile +2 -2
- data/test/environments/rails23/Gemfile +2 -2
- data/test/environments/rails31/Gemfile +2 -2
- data/test/environments/rails32/Gemfile +2 -2
- data/test/multiverse/suites/agent_only/marshaling_test.rb +1 -1
- data/test/multiverse/suites/agent_only/testing_app.rb +6 -0
- data/test/multiverse/suites/agent_only/thread_profiling_test.rb +5 -5
- data/test/multiverse/suites/agent_only/xray_sessions_test.rb +163 -0
- data/test/multiverse/suites/rails/request_statistics_test.rb +2 -2
- data/test/multiverse/suites/rails/view_instrumentation_test.rb +20 -21
- data/test/new_relic/agent/agent/connect_test.rb +0 -10
- data/test/new_relic/agent/agent_test.rb +27 -44
- data/test/new_relic/agent/browser_monitoring_test.rb +0 -52
- data/test/new_relic/agent/commands/agent_command_router_test.rb +150 -12
- data/test/new_relic/agent/commands/{thread_profiler_test.rb → thread_profiler_session_test.rb} +58 -19
- data/test/new_relic/agent/commands/xray_session_collection_test.rb +332 -0
- data/test/new_relic/agent/commands/xray_session_test.rb +42 -0
- data/test/new_relic/agent/configuration/manager_test.rb +2 -1
- data/test/new_relic/agent/configuration/server_source_test.rb +10 -10
- data/test/new_relic/agent/cpu_sampler_test.rb +50 -0
- data/test/new_relic/agent/instrumentation/action_controller_subscriber_test.rb +31 -0
- data/test/new_relic/agent/instrumentation/queue_time_test.rb +0 -1
- data/test/new_relic/agent/instrumentation/sequel_test.rb +1 -1
- data/test/new_relic/agent/instrumentation/task_instrumentation_test.rb +0 -1
- data/test/new_relic/agent/memory_logger_test.rb +53 -0
- data/test/new_relic/agent/new_relic_service_test.rb +1 -1
- data/test/new_relic/agent/pipe_channel_manager_test.rb +4 -5
- data/test/new_relic/agent/request_sampler_test.rb +70 -20
- data/test/new_relic/agent/rules_engine_test.rb +6 -0
- data/test/new_relic/agent/threading/agent_thread_test.rb +2 -2
- data/test/new_relic/agent/threading/backtrace_node_test.rb +110 -17
- data/test/new_relic/agent/threading/backtrace_service_test.rb +567 -0
- data/test/new_relic/agent/threading/fake_thread.rb +4 -0
- data/test/new_relic/agent/threading/thread_profile_test.rb +141 -217
- data/test/new_relic/agent/threading/threaded_test_case.rb +3 -8
- data/test/new_relic/agent/transaction/developer_mode_sample_buffer_test.rb +69 -0
- data/test/new_relic/agent/transaction/force_persist_sample_buffer_test.rb +52 -0
- data/test/new_relic/agent/transaction/slowest_sample_buffer_test.rb +67 -0
- data/test/new_relic/agent/transaction/xray_sample_buffer_test.rb +71 -0
- data/test/new_relic/agent/transaction_sampler_test.rb +171 -307
- data/test/new_relic/agent/transaction_test.rb +33 -5
- data/test/new_relic/agent/worker_loop_test.rb +33 -11
- data/test/new_relic/coerce_test.rb +13 -0
- data/test/new_relic/fake_collector.rb +26 -3
- data/test/new_relic/multiverse_helpers.rb +2 -0
- data/test/new_relic/rack/browser_monitoring_test.rb +12 -0
- data/test/new_relic/rack/developer_mode_test.rb +2 -2
- data/test/new_relic/transaction_sample_test.rb +19 -2
- data/test/performance/lib/performance/console_reporter.rb +1 -1
- data/test/performance/lib/performance/test_case.rb +7 -3
- data/test/performance/script/runner +3 -0
- data/test/performance/suites/thread_profiling.rb +83 -0
- data/test/test_helper.rb +2 -2
- data.tar.gz.sig +0 -0
- metadata +32 -32
- metadata.gz.sig +1 -1
- data/lib/new_relic/agent/commands/thread_profiler.rb +0 -80
@@ -6,8 +6,8 @@ module NewRelic
|
|
6
6
|
module Agent
|
7
7
|
module Configuration
|
8
8
|
MASK_DEFAULTS = {
|
9
|
-
:'thread_profiler' => Proc.new { !NewRelic::Agent::
|
10
|
-
:'thread_profiler.enabled' => Proc.new { !NewRelic::Agent::
|
9
|
+
:'thread_profiler' => Proc.new { !NewRelic::Agent::Threading::BacktraceService.is_supported? },
|
10
|
+
:'thread_profiler.enabled' => Proc.new { !NewRelic::Agent::Threading::BacktraceService.is_supported? }
|
11
11
|
}
|
12
12
|
end
|
13
13
|
end
|
@@ -33,7 +33,7 @@ module NewRelic
|
|
33
33
|
:'transaction_tracer.enabled' => 'collect_traces',
|
34
34
|
:'slow_sql.enabled' => 'collect_traces',
|
35
35
|
:'error_collector.enabled' => 'collect_errors',
|
36
|
-
:'
|
36
|
+
:'analytics_events.enabled' => 'collect_analytics_events'
|
37
37
|
}
|
38
38
|
gated_features.each do |feature, gate_key|
|
39
39
|
if server_config.has_key?(gate_key)
|
@@ -99,6 +99,8 @@ module NewRelic
|
|
99
99
|
:request => event.payload[:request],
|
100
100
|
:filtered_params => filter(event.payload[:params]))
|
101
101
|
txn.apdex_start = (event.queue_start || event.time)
|
102
|
+
txn.name = event.metric_name
|
103
|
+
|
102
104
|
event.scope = Agent.instance.stats_engine \
|
103
105
|
.push_scope(:action_controller, event.time)
|
104
106
|
end
|
@@ -320,17 +320,11 @@ module NewRelic
|
|
320
320
|
|
321
321
|
begin
|
322
322
|
NewRelic::Agent::BusyCalculator.dispatcher_start txn.start_time
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
end
|
328
|
-
if defined?(request) && request && defined?(response) && response
|
329
|
-
if !Agent.config[:disable_mobile_headers]
|
330
|
-
NewRelic::Agent::BrowserMonitoring.insert_mobile_response_header(request, response)
|
331
|
-
end
|
323
|
+
if block_given?
|
324
|
+
yield
|
325
|
+
else
|
326
|
+
perform_action_without_newrelic_trace(*args)
|
332
327
|
end
|
333
|
-
result
|
334
328
|
rescue => e
|
335
329
|
txn.notice_error(e)
|
336
330
|
raise
|
@@ -132,7 +132,7 @@ end
|
|
132
132
|
DependencyDetection.defer do
|
133
133
|
@name = :rails31_view
|
134
134
|
|
135
|
-
# We can't be sure that this
|
135
|
+
# We can't be sure that this will work with future versions of Rails 3.
|
136
136
|
# Currently enabled for Rails 3.1 and 3.2
|
137
137
|
depends_on do
|
138
138
|
defined?(::Rails) && ::Rails::VERSION::MAJOR.to_i == 3 && ([1,2].member?(::Rails::VERSION::MINOR.to_i))
|
@@ -155,8 +155,8 @@ DependencyDetection.defer do
|
|
155
155
|
# This is needed for rails 3.2 compatibility
|
156
156
|
@details = extract_details(options) if respond_to? :extract_details, true
|
157
157
|
identifier = determine_template(options) ? determine_template(options).identifier : nil
|
158
|
-
|
159
|
-
trace_execution_scoped
|
158
|
+
scope_name = "View/#{NewRelic::Agent::Instrumentation::Rails3::ActionView::NewRelic.template_metric(identifier, options)}/Rendering"
|
159
|
+
trace_execution_scoped scope_name do
|
160
160
|
render_without_newrelic(context, options)
|
161
161
|
end
|
162
162
|
end
|
@@ -168,17 +168,16 @@ DependencyDetection.defer do
|
|
168
168
|
ActionView::PartialRenderer.class_eval do
|
169
169
|
include NewRelic::Agent::MethodTracer
|
170
170
|
|
171
|
-
def
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
render_without_newrelic(*args, &block)
|
171
|
+
def instrument_with_newrelic(name, payload = {}, &block)
|
172
|
+
identifier = payload[:identifier]
|
173
|
+
scope_name = "View/#{NewRelic::Agent::Instrumentation::Rails3::ActionView::NewRelic.template_metric(identifier)}/Partial"
|
174
|
+
trace_execution_scoped(scope_name) do
|
175
|
+
instrument_without_newrelic(name, payload, &block)
|
177
176
|
end
|
178
177
|
end
|
179
178
|
|
180
|
-
alias_method :
|
181
|
-
alias_method :
|
179
|
+
alias_method :instrument_without_newrelic, :instrument
|
180
|
+
alias_method :instrument, :instrument_with_newrelic
|
182
181
|
end
|
183
182
|
end
|
184
183
|
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is distributed under New Relic's license terms.
|
3
|
+
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
|
4
|
+
|
5
|
+
# Base class for startup logging and testing in multiverse
|
6
|
+
|
7
|
+
module NewRelic
|
8
|
+
module Agent
|
9
|
+
class MemoryLogger
|
10
|
+
def initialize
|
11
|
+
@messages = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def is_startup_logger?
|
15
|
+
true
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_accessor :messages, :level
|
19
|
+
|
20
|
+
def fatal(*msgs)
|
21
|
+
messages << [:fatal, msgs]
|
22
|
+
end
|
23
|
+
|
24
|
+
def error(*msgs)
|
25
|
+
messages << [:error, msgs]
|
26
|
+
end
|
27
|
+
|
28
|
+
def warn(*msgs)
|
29
|
+
messages << [:warn, msgs]
|
30
|
+
end
|
31
|
+
|
32
|
+
def info(*msgs)
|
33
|
+
messages << [:info, msgs]
|
34
|
+
end
|
35
|
+
|
36
|
+
def debug(*msgs)
|
37
|
+
messages << [:debug, msgs]
|
38
|
+
end
|
39
|
+
|
40
|
+
def log_exception(level, e, backtrace_level=level)
|
41
|
+
messages << [:log_exception, [level, e, backtrace_level]]
|
42
|
+
end
|
43
|
+
|
44
|
+
def dump(logger)
|
45
|
+
messages.each do |(method, args)|
|
46
|
+
logger.send(method, *args)
|
47
|
+
end
|
48
|
+
messages.clear
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -141,6 +141,10 @@ module NewRelic
|
|
141
141
|
invoke_remote(:agent_command_results, @agent_id, results)
|
142
142
|
end
|
143
143
|
|
144
|
+
def get_xray_metadata(xray_ids)
|
145
|
+
invoke_remote(:get_xray_metadata, @agent_id, *xray_ids)
|
146
|
+
end
|
147
|
+
|
144
148
|
# Send fine-grained analytic data to the collector.
|
145
149
|
def analytic_event_data(data)
|
146
150
|
data.collect! {|hash| [hash] }
|
@@ -13,9 +13,9 @@ class NewRelic::Agent::RequestSampler
|
|
13
13
|
MonitorMixin
|
14
14
|
|
15
15
|
# The namespace and keys of config values
|
16
|
-
|
17
|
-
|
18
|
-
|
16
|
+
MAX_SAMPLES_KEY = :'analytics_events.max_samples_stored'
|
17
|
+
ENABLED_KEY = :'analytics_events.enabled'
|
18
|
+
ENABLED_TXN_KEY = :'analytics_events.transactions.enabled'
|
19
19
|
|
20
20
|
# The type field of the sample
|
21
21
|
SAMPLE_TYPE = 'Transaction'
|
@@ -42,26 +42,39 @@ class NewRelic::Agent::RequestSampler
|
|
42
42
|
public
|
43
43
|
######
|
44
44
|
|
45
|
-
|
45
|
+
# Fetch a copy of the sampler's gathered samples. (Synchronized)
|
46
46
|
def samples
|
47
47
|
return self.synchronize { @samples.to_a }
|
48
48
|
end
|
49
49
|
|
50
|
-
|
51
|
-
#
|
52
|
-
def
|
50
|
+
# Clear any existing samples, reset the last sample time, and return the
|
51
|
+
# previous set of samples. (Synchronized)
|
52
|
+
def harvest
|
53
53
|
NewRelic::Agent.logger.debug "Resetting RequestSampler"
|
54
54
|
|
55
55
|
sample_count, request_count = 0
|
56
|
+
old_samples = nil
|
56
57
|
|
57
58
|
self.synchronize do
|
58
59
|
sample_count = @samples.size
|
59
60
|
request_count = @samples.seen
|
61
|
+
old_samples = @samples.to_a
|
60
62
|
@samples.reset
|
61
63
|
@notified_full = false
|
62
64
|
end
|
63
65
|
|
64
66
|
record_sampling_rate(request_count, sample_count) if @enabled
|
67
|
+
old_samples
|
68
|
+
end
|
69
|
+
|
70
|
+
alias_method :reset, :harvest
|
71
|
+
|
72
|
+
# Merge samples back into the buffer, for example after a failed
|
73
|
+
# transmission to the collector. (Synchronized)
|
74
|
+
def merge(old_samples)
|
75
|
+
self.synchronize do
|
76
|
+
old_samples.each { |s| @samples.append(s) }
|
77
|
+
end
|
65
78
|
end
|
66
79
|
|
67
80
|
def record_sampling_rate(request_count, sample_count)
|
@@ -90,7 +103,12 @@ class NewRelic::Agent::RequestSampler
|
|
90
103
|
|
91
104
|
NewRelic::Agent.config.register_callback(ENABLED_KEY) do |enabled|
|
92
105
|
NewRelic::Agent.logger.info "%sabling the Request Sampler." % [ enabled ? 'En' : 'Dis' ]
|
93
|
-
@enabled = enabled
|
106
|
+
@enabled = enabled && NewRelic::Agent.config[ENABLED_TXN_KEY]
|
107
|
+
end
|
108
|
+
|
109
|
+
NewRelic::Agent.config.register_callback(ENABLED_TXN_KEY) do |enabled|
|
110
|
+
NewRelic::Agent.logger.info "%sabling the Request Sampler." % [ enabled ? 'En' : 'Dis' ]
|
111
|
+
@enabled = enabled && NewRelic::Agent.config[ENABLED_KEY]
|
94
112
|
end
|
95
113
|
end
|
96
114
|
|
@@ -100,14 +118,15 @@ class NewRelic::Agent::RequestSampler
|
|
100
118
|
end
|
101
119
|
|
102
120
|
# Event handler for the :transaction_finished event.
|
103
|
-
def on_transaction_finished(
|
121
|
+
def on_transaction_finished(payload)
|
104
122
|
return unless @enabled
|
123
|
+
return unless NewRelic::Agent::Transaction.transaction_type_is_web?(payload[:type])
|
105
124
|
sample = {
|
106
|
-
TIMESTAMP_KEY => float(start_timestamp),
|
107
|
-
NAME_KEY => string(
|
108
|
-
DURATION_KEY => float(duration),
|
125
|
+
TIMESTAMP_KEY => float(payload[:start_timestamp]),
|
126
|
+
NAME_KEY => string(payload[:name]),
|
127
|
+
DURATION_KEY => float(payload[:duration]),
|
109
128
|
TYPE_KEY => SAMPLE_TYPE
|
110
|
-
}.merge(
|
129
|
+
}.merge((payload[:overview_metrics] || {}))
|
111
130
|
|
112
131
|
is_full = self.synchronize { @samples.append(sample) }
|
113
132
|
notify_full if is_full && !@notified_full
|
@@ -36,9 +36,12 @@ module NewRelic
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def self.supported_on_this_platform?
|
39
|
-
# Process.times on JRuby reports wall clock elapsed time,
|
40
|
-
# not actual cpu time used, so
|
41
|
-
|
39
|
+
# Process.times on JRuby < 1.7.0 reports wall clock elapsed time,
|
40
|
+
# not actual cpu time used, so this sampler can only be used on JRuby >= 1.7.0.
|
41
|
+
if defined?(JRuby)
|
42
|
+
return JRUBY_VERSION >= '1.7.0'
|
43
|
+
end
|
44
|
+
true
|
42
45
|
end
|
43
46
|
|
44
47
|
def poll
|
@@ -32,7 +32,8 @@ module NewRelic
|
|
32
32
|
::NewRelic::Agent.logger.debug("Failed to backtrace #{thread.inspect}: #{e.class.name}: #{e.to_s}")
|
33
33
|
end
|
34
34
|
return nil unless bt
|
35
|
-
|
35
|
+
bt.reject! { |t| t.include?('/newrelic_rpm-') } unless profile_agent_code
|
36
|
+
bt
|
36
37
|
end
|
37
38
|
end
|
38
39
|
|
@@ -7,62 +7,115 @@ module NewRelic
|
|
7
7
|
module Threading
|
8
8
|
|
9
9
|
class BacktraceNode
|
10
|
-
attr_reader :file, :method, :line_no, :children
|
11
|
-
attr_accessor :runnable_count, :
|
12
|
-
|
13
|
-
def initialize(line
|
14
|
-
line
|
15
|
-
@
|
16
|
-
|
17
|
-
|
10
|
+
attr_reader :file, :method, :line_no, :children, :raw_line
|
11
|
+
attr_accessor :runnable_count, :depth
|
12
|
+
|
13
|
+
def initialize(line)
|
14
|
+
if line
|
15
|
+
@raw_line = line
|
16
|
+
@root = false
|
17
|
+
else
|
18
|
+
@root = true
|
19
|
+
end
|
20
|
+
|
18
21
|
@children = []
|
19
22
|
@runnable_count = 0
|
20
|
-
@to_prune = false
|
21
23
|
@depth = 0
|
24
|
+
end
|
25
|
+
|
26
|
+
def root?
|
27
|
+
@root
|
28
|
+
end
|
29
|
+
|
30
|
+
def empty?
|
31
|
+
root? && @children.empty?
|
32
|
+
end
|
22
33
|
|
23
|
-
|
34
|
+
def find(raw_line)
|
35
|
+
@children.find { |child| child.raw_line == raw_line }
|
24
36
|
end
|
25
37
|
|
26
38
|
def ==(other)
|
27
|
-
|
28
|
-
@
|
29
|
-
|
39
|
+
(
|
40
|
+
(root? && other.root? || @raw_line == other.raw_line) &&
|
41
|
+
(
|
42
|
+
@depth == other.depth &&
|
43
|
+
@runnable_count == other.runnable_count
|
44
|
+
)
|
45
|
+
)
|
30
46
|
end
|
31
47
|
|
32
48
|
def total_count
|
33
49
|
@runnable_count
|
34
50
|
end
|
35
51
|
|
52
|
+
def aggregate(backtrace)
|
53
|
+
current = self
|
54
|
+
|
55
|
+
backtrace.reverse_each do |frame|
|
56
|
+
existing_node = current.find(frame)
|
57
|
+
if existing_node
|
58
|
+
node = existing_node
|
59
|
+
else
|
60
|
+
node = Threading::BacktraceNode.new(frame)
|
61
|
+
current.add_child_unless_present(node)
|
62
|
+
end
|
63
|
+
|
64
|
+
node.runnable_count += 1
|
65
|
+
current = node
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
36
69
|
# Descending order on count, ascending on depth of nodes
|
37
|
-
def
|
38
|
-
[-runnable_count, depth] <=> [-
|
70
|
+
def <=>(other)
|
71
|
+
[-runnable_count, depth] <=> [-other.runnable_count, other.depth]
|
72
|
+
end
|
73
|
+
|
74
|
+
def flatten
|
75
|
+
initial = self.root? ? [] : [self]
|
76
|
+
@children.inject(initial) { |all, child| all.concat(child.flatten) }
|
39
77
|
end
|
40
78
|
|
41
79
|
include NewRelic::Coerce
|
42
80
|
|
43
81
|
def to_array
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
82
|
+
child_arrays = @children.map { |c| c.to_array }
|
83
|
+
return child_arrays if root?
|
84
|
+
file, method, line = parse_backtrace_frame(@raw_line)
|
85
|
+
[
|
86
|
+
[
|
87
|
+
string(file),
|
88
|
+
string(method),
|
89
|
+
int(line)
|
90
|
+
],
|
49
91
|
int(@runnable_count),
|
50
92
|
0,
|
51
|
-
|
93
|
+
child_arrays
|
94
|
+
]
|
52
95
|
end
|
53
96
|
|
54
|
-
def
|
97
|
+
def add_child_unless_present(child)
|
55
98
|
child.depth = @depth + 1
|
56
99
|
@children << child unless @children.include? child
|
57
100
|
end
|
58
101
|
|
59
|
-
def prune!
|
60
|
-
|
102
|
+
def prune!(targets)
|
103
|
+
@children.delete_if { |child| targets.include?(child) }
|
104
|
+
@children.each { |child| child.prune!(targets) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def dump_string(indent=0)
|
108
|
+
file, method, line = parse_backtrace_frame(@raw_line)
|
109
|
+
result = "#{" " * indent}#<BacktraceNode:#{object_id} [#{@runnable_count}] #{@file}:#{@line_no} in #{@method}>"
|
110
|
+
child_results = @children.map { |c| c.dump_string(indent+2) }.join("\n")
|
111
|
+
result << "\n" unless child_results.empty?
|
112
|
+
result << child_results
|
61
113
|
end
|
62
114
|
|
63
|
-
|
64
|
-
|
65
|
-
|
115
|
+
# Returns [filename, method, line number]
|
116
|
+
def parse_backtrace_frame(frame)
|
117
|
+
frame =~ /(.*)\:(\d+)\:in `(.*)'/
|
118
|
+
[$1, $3, $2] # sic
|
66
119
|
end
|
67
120
|
end
|
68
121
|
|
@@ -0,0 +1,264 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# This file is distributed under New Relic's license terms.
|
3
|
+
# See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
|
4
|
+
|
5
|
+
module NewRelic
|
6
|
+
module Agent
|
7
|
+
module Threading
|
8
|
+
class BacktraceService
|
9
|
+
ALL_TRANSACTIONS = "**ALL**".freeze
|
10
|
+
|
11
|
+
def self.is_supported?
|
12
|
+
RUBY_VERSION >= "1.9.2"
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_reader :worker_loop, :buffer, :effective_polling_period,
|
16
|
+
:overhead_percent_threshold
|
17
|
+
attr_accessor :worker_thread, :profile_agent_code
|
18
|
+
|
19
|
+
def initialize(event_listener=nil)
|
20
|
+
@profiles = {}
|
21
|
+
@buffer = {}
|
22
|
+
|
23
|
+
# synchronizes access to @profiles and @buffer above
|
24
|
+
@lock = Mutex.new
|
25
|
+
|
26
|
+
@running = false
|
27
|
+
@profile_agent_code = false
|
28
|
+
@worker_loop = NewRelic::Agent::WorkerLoop.new
|
29
|
+
|
30
|
+
# Memoize overhead % to avoid getting stale OR looked up every poll
|
31
|
+
@overhead_percent_threshold = NewRelic::Agent.config[:'xray_session.max_profile_overhead']
|
32
|
+
NewRelic::Agent.config.register_callback(:'xray_session.max_profile_overhead') do |new_value|
|
33
|
+
@overhead_percent_threshold = new_value
|
34
|
+
end
|
35
|
+
|
36
|
+
if event_listener
|
37
|
+
event_listener.subscribe(:transaction_finished, &method(:on_transaction_finished))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Public interface
|
42
|
+
|
43
|
+
def running?
|
44
|
+
@running
|
45
|
+
end
|
46
|
+
|
47
|
+
def subscribe(transaction_name, command_arguments={})
|
48
|
+
if !self.class.is_supported?
|
49
|
+
NewRelic::Agent.logger.debug("Backtracing not supported, so not subscribing transaction '#{transaction_name}'")
|
50
|
+
return
|
51
|
+
end
|
52
|
+
|
53
|
+
NewRelic::Agent.logger.debug("Backtrace Service subscribing transaction '#{transaction_name}'")
|
54
|
+
|
55
|
+
profile = ThreadProfile.new(command_arguments)
|
56
|
+
|
57
|
+
@lock.synchronize do
|
58
|
+
@profiles[transaction_name] = profile
|
59
|
+
update_values_from_profiles
|
60
|
+
end
|
61
|
+
|
62
|
+
start
|
63
|
+
profile
|
64
|
+
end
|
65
|
+
|
66
|
+
def unsubscribe(transaction_name)
|
67
|
+
return unless self.class.is_supported?
|
68
|
+
|
69
|
+
NewRelic::Agent.logger.debug("Backtrace Service unsubscribing transaction '#{transaction_name}'")
|
70
|
+
@lock.synchronize do
|
71
|
+
@profiles.delete(transaction_name)
|
72
|
+
if @profiles.empty?
|
73
|
+
stop
|
74
|
+
else
|
75
|
+
update_values_from_profiles
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def update_values_from_profiles
|
81
|
+
self.effective_polling_period = find_effective_polling_period
|
82
|
+
self.profile_agent_code = should_profile_agent_code?
|
83
|
+
end
|
84
|
+
|
85
|
+
def subscribed?(transaction_name)
|
86
|
+
@lock.synchronize do
|
87
|
+
@profiles.has_key?(transaction_name)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def harvest(transaction_name)
|
92
|
+
@lock.synchronize do
|
93
|
+
if @profiles[transaction_name]
|
94
|
+
profile = @profiles.delete(transaction_name)
|
95
|
+
profile.finished_at = Time.now
|
96
|
+
@profiles[transaction_name] = ThreadProfile.new(profile.command_arguments)
|
97
|
+
profile
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def on_transaction_finished(payload)
|
103
|
+
name = payload[:name]
|
104
|
+
start = payload[:start_timestamp]
|
105
|
+
duration = payload[:duration]
|
106
|
+
thread = payload[:thread] || Thread.current
|
107
|
+
@lock.synchronize do
|
108
|
+
backtraces = @buffer.delete(thread)
|
109
|
+
if backtraces && @profiles.has_key?(name)
|
110
|
+
aggregate_backtraces(backtraces, name, start, duration, thread)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
# Internals
|
116
|
+
|
117
|
+
# This method is expected to be called with @lock held.
|
118
|
+
def aggregate_backtraces(backtraces, name, start, duration, thread)
|
119
|
+
end_time = start + duration
|
120
|
+
backtraces.each do |(timestamp, backtrace)|
|
121
|
+
if timestamp >= start && timestamp < end_time
|
122
|
+
@profiles[name].aggregate(backtrace, :request, thread)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def start
|
128
|
+
return if @running || !self.class.is_supported?
|
129
|
+
|
130
|
+
@running = true
|
131
|
+
self.worker_thread = AgentThread.new('Backtrace Service') do
|
132
|
+
begin
|
133
|
+
# Not passing period because we expect it's already been set.
|
134
|
+
self.worker_loop.run(&method(:poll))
|
135
|
+
ensure
|
136
|
+
NewRelic::Agent.logger.debug("Exiting New Relic thread: Backtrace Service")
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
# This method is expected to be called with @lock held
|
142
|
+
def stop
|
143
|
+
return unless @running
|
144
|
+
@running = false
|
145
|
+
self.worker_loop.stop
|
146
|
+
|
147
|
+
@buffer = {}
|
148
|
+
end
|
149
|
+
|
150
|
+
def effective_polling_period=(new_period)
|
151
|
+
@effective_polling_period = new_period
|
152
|
+
self.worker_loop.period = new_period
|
153
|
+
end
|
154
|
+
|
155
|
+
def poll
|
156
|
+
poll_start = Time.now
|
157
|
+
|
158
|
+
@lock.synchronize do
|
159
|
+
AgentThread.list.each do |thread|
|
160
|
+
sample_thread(thread)
|
161
|
+
end
|
162
|
+
@profiles.values.each { |c| c.increment_poll_count }
|
163
|
+
@buffer.delete_if { |thread, _| !thread.alive? }
|
164
|
+
end
|
165
|
+
|
166
|
+
end_time = Time.now
|
167
|
+
adjust_polling_time(end_time, poll_start)
|
168
|
+
record_supportability_metrics(end_time, poll_start)
|
169
|
+
end
|
170
|
+
|
171
|
+
# This method is expected to be called with @lock held.
|
172
|
+
attr_reader :profiles
|
173
|
+
|
174
|
+
# This method is expected to be called with @lock held.
|
175
|
+
def should_buffer?(bucket)
|
176
|
+
bucket == :request && @profiles.keys.any? { |k| k != ALL_TRANSACTIONS }
|
177
|
+
end
|
178
|
+
|
179
|
+
# This method is expected to be called with @lock held.
|
180
|
+
def need_backtrace?(bucket)
|
181
|
+
(
|
182
|
+
bucket != :ignore &&
|
183
|
+
(@profiles[ALL_TRANSACTIONS] || should_buffer?(bucket))
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
MAX_BUFFER_LENGTH = 500
|
188
|
+
|
189
|
+
# This method is expected to be called with @lock held.
|
190
|
+
def buffer_backtrace_for_thread(thread, timestamp, backtrace, bucket)
|
191
|
+
if should_buffer?(bucket)
|
192
|
+
@buffer[thread] ||= []
|
193
|
+
if @buffer[thread].length < MAX_BUFFER_LENGTH
|
194
|
+
@buffer[thread] << [timestamp, backtrace]
|
195
|
+
else
|
196
|
+
NewRelic::Agent.increment_metric('Supportability/XraySessions/DroppedBacktraces')
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# This method is expected to be called with @lock held.
|
202
|
+
def aggregate_global_backtrace(backtrace, bucket, thread)
|
203
|
+
if @profiles[ALL_TRANSACTIONS]
|
204
|
+
@profiles[ALL_TRANSACTIONS].aggregate(backtrace, bucket, thread)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# This method is expected to be called with @lock held.
|
209
|
+
def sample_thread(thread)
|
210
|
+
bucket = AgentThread.bucket_thread(thread, @profile_agent_code)
|
211
|
+
|
212
|
+
if need_backtrace?(bucket)
|
213
|
+
timestamp = Time.now.to_f
|
214
|
+
backtrace = AgentThread.scrub_backtrace(thread, @profile_agent_code)
|
215
|
+
aggregate_global_backtrace(backtrace, bucket, thread)
|
216
|
+
buffer_backtrace_for_thread(thread, timestamp, backtrace, bucket)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
# This method is expected to be called with @lock held.
|
221
|
+
def find_effective_polling_period
|
222
|
+
@profiles.values.map { |p| p.requested_period }.min
|
223
|
+
end
|
224
|
+
|
225
|
+
# This method is expected to be called with @lock held.
|
226
|
+
def should_profile_agent_code?
|
227
|
+
@profiles.values.any? { |p| p.profile_agent_code }
|
228
|
+
end
|
229
|
+
|
230
|
+
# If our overhead % exceeds the threshold, bump the next poll period
|
231
|
+
# relative to how much larger our overhead is than allowed
|
232
|
+
def adjust_polling_time(now, poll_start)
|
233
|
+
duration = now - poll_start
|
234
|
+
overhead_percent = duration / effective_polling_period
|
235
|
+
|
236
|
+
if overhead_percent > self.overhead_percent_threshold
|
237
|
+
scale_up_by = overhead_percent / self.overhead_percent_threshold
|
238
|
+
worker_loop.period = effective_polling_period * scale_up_by
|
239
|
+
else
|
240
|
+
worker_loop.period = effective_polling_period
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def record_supportability_metrics(now, poll_start)
|
245
|
+
record_polling_time(now, poll_start)
|
246
|
+
record_skew(poll_start)
|
247
|
+
end
|
248
|
+
|
249
|
+
def record_polling_time(now, poll_start)
|
250
|
+
NewRelic::Agent.record_metric('Supportability/ThreadProfiler/PollingTime', now - poll_start)
|
251
|
+
end
|
252
|
+
|
253
|
+
def record_skew(poll_start)
|
254
|
+
if @last_poll
|
255
|
+
skew = poll_start - @last_poll - worker_loop.period
|
256
|
+
NewRelic::Agent.record_metric('Supportability/ThreadProfiler/Skew', skew)
|
257
|
+
end
|
258
|
+
@last_poll = poll_start
|
259
|
+
end
|
260
|
+
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|