newrelic_rpm 3.2.0.1 → 3.3.0.beta1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of newrelic_rpm might be problematic. Click here for more details.
- data/CHANGELOG +7 -3
- data/LICENSE +2 -29
- data/README.rdoc +2 -2
- data/lib/new_relic/agent.rb +14 -0
- data/lib/new_relic/agent/agent.rb +19 -9
- data/lib/new_relic/agent/beacon_configuration.rb +11 -0
- data/lib/new_relic/agent/browser_monitoring.rb +53 -13
- data/lib/new_relic/agent/database.rb +11 -1
- data/lib/new_relic/agent/instrumentation/active_merchant.rb +3 -1
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +3 -2
- data/lib/new_relic/agent/instrumentation/metric_frame.rb +23 -2
- data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +15 -12
- data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +11 -8
- data/lib/new_relic/agent/sql_sampler.rb +19 -7
- data/lib/new_relic/agent/stats_engine.rb +1 -0
- data/lib/new_relic/agent/stats_engine/gc_profiler.rb +120 -0
- data/lib/new_relic/agent/stats_engine/transactions.rb +2 -85
- data/lib/new_relic/agent/transaction_info.rb +49 -0
- data/lib/new_relic/agent/transaction_sample_builder.rb +2 -0
- data/lib/new_relic/agent/transaction_sampler.rb +65 -7
- data/lib/new_relic/rack/browser_monitoring.rb +38 -8
- data/lib/new_relic/transaction_sample.rb +8 -6
- data/lib/new_relic/version.rb +2 -2
- data/newrelic.yml +1 -1
- data/newrelic_rpm.gemspec +6 -3
- data/test/new_relic/agent/agent/connect_test.rb +4 -11
- data/test/new_relic/agent/beacon_configuration_test.rb +10 -7
- data/test/new_relic/agent/browser_monitoring_test.rb +69 -44
- data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +12 -8
- data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +0 -2
- data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +0 -1
- data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +3 -3
- data/test/new_relic/agent/sql_sampler_test.rb +25 -10
- data/test/new_relic/agent/stats_engine_test.rb +41 -6
- data/test/new_relic/agent/transaction_sampler_test.rb +22 -12
- data/test/new_relic/metric_parser/metric_parser_test.rb +11 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +7 -1
- metadata +321 -337
@@ -19,12 +19,6 @@ module NewRelic
|
|
19
19
|
|
20
20
|
sql, name, binds = args
|
21
21
|
|
22
|
-
# Capture db config if we are going to try to get the explain plans
|
23
|
-
if (defined?(ActiveRecord::ConnectionAdapters::MysqlAdapter) && self.is_a?(ActiveRecord::ConnectionAdapters::MysqlAdapter)) ||
|
24
|
-
(defined?(ActiveRecord::ConnectionAdapters::Mysql2Adapter) && self.is_a?(ActiveRecord::ConnectionAdapters::Mysql2Adapter)) ||
|
25
|
-
(defined?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter) && self.is_a?(ActiveRecord::ConnectionAdapters::PostgreSQLAdapter))
|
26
|
-
supported_config = @config
|
27
|
-
end
|
28
22
|
if name && (parts = name.split " ") && parts.size == 2
|
29
23
|
model = parts.first
|
30
24
|
operation = parts.last.downcase
|
@@ -60,14 +54,22 @@ module NewRelic
|
|
60
54
|
else
|
61
55
|
metrics = [metric, "ActiveRecord/all"]
|
62
56
|
metrics << "ActiveRecord/#{metric_name}" if metric_name
|
57
|
+
if NewRelic::Agent::Database.config && NewRelic::Agent::Database.config[:adapter]
|
58
|
+
type = NewRelic::Agent::Database.config[:adapter].sub(/\d*/, '')
|
59
|
+
host = NewRelic::Agent::Database.config[:host] || 'localhost'
|
60
|
+
metrics << "RemoteService/sql/#{type}/#{host}"
|
61
|
+
end
|
62
|
+
|
63
63
|
self.class.trace_execution_scoped(metrics) do
|
64
64
|
sql, name, binds = args
|
65
65
|
t0 = Time.now
|
66
66
|
begin
|
67
67
|
log_without_newrelic_instrumentation(*args, &block)
|
68
68
|
ensure
|
69
|
-
NewRelic::Agent.instance.transaction_sampler.notice_sql(sql,
|
70
|
-
|
69
|
+
NewRelic::Agent.instance.transaction_sampler.notice_sql(sql, NewRelic::Agent::Database.config,
|
70
|
+
(Time.now - t0).to_f)
|
71
|
+
NewRelic::Agent.instance.sql_sampler.notice_sql(sql, metric, NewRelic::Agent::Database.config,
|
72
|
+
(Time.now - t0).to_f)
|
71
73
|
end
|
72
74
|
end
|
73
75
|
end
|
@@ -112,6 +114,7 @@ DependencyDetection.defer do
|
|
112
114
|
executes do
|
113
115
|
Rails.configuration.after_initialize do
|
114
116
|
ActiveRecord::Base.class_eval do
|
117
|
+
NewRelic::Agent::Database::ConnectionManager.instance.config = connection.instance_eval{ @config }
|
115
118
|
class << self
|
116
119
|
add_method_tracer :find_by_sql, 'ActiveRecord/#{self.name}/find_by_sql', :metric => false
|
117
120
|
add_method_tracer :transaction, 'ActiveRecord/#{self.name}/transaction', :metric => false
|
@@ -33,6 +33,15 @@ module NewRelic
|
|
33
33
|
@explain_enabled = config.fetch('explain_enabled', true)
|
34
34
|
@stack_trace_threshold = config.fetch('stack_trace_threshold',
|
35
35
|
0.5).to_f
|
36
|
+
if config.fetch('enabled', true) &&
|
37
|
+
NewRelic::Control.instance['transaction_tracer'] &&
|
38
|
+
NewRelic::Control.instance['transaction_tracer'].fetch('enabled',
|
39
|
+
true) &&
|
40
|
+
NewRelic::Control.instance.fetch('collect_traces', true)
|
41
|
+
enable
|
42
|
+
else
|
43
|
+
disable
|
44
|
+
end
|
36
45
|
end
|
37
46
|
|
38
47
|
def config
|
@@ -45,14 +54,12 @@ module NewRelic
|
|
45
54
|
# the statistics engine.
|
46
55
|
def enable
|
47
56
|
@disabled = false
|
48
|
-
NewRelic::Agent.instance.stats_engine.sql_sampler = self
|
49
57
|
end
|
50
58
|
|
51
59
|
# Disable the sql sampler - this also deregisters it
|
52
60
|
# with the statistics engine.
|
53
61
|
def disable
|
54
62
|
@disabled = true
|
55
|
-
NewRelic::Agent.instance.stats_engine.remove_sql_sampler(self)
|
56
63
|
end
|
57
64
|
|
58
65
|
def enabled?
|
@@ -60,7 +67,10 @@ module NewRelic
|
|
60
67
|
end
|
61
68
|
|
62
69
|
def notice_transaction(path, uri=nil, params={})
|
63
|
-
|
70
|
+
if NewRelic::Agent.instance.transaction_sampler.builder
|
71
|
+
guid = NewRelic::Agent.instance.transaction_sampler.builder.sample.guid
|
72
|
+
end
|
73
|
+
transaction_data.set_transaction_info(path, uri, params, guid) if !disabled && transaction_data
|
64
74
|
end
|
65
75
|
|
66
76
|
def notice_first_scope_push(time)
|
@@ -149,15 +159,17 @@ module NewRelic
|
|
149
159
|
attr_reader :uri
|
150
160
|
attr_reader :params
|
151
161
|
attr_reader :sql_data
|
162
|
+
attr_reader :guid
|
152
163
|
|
153
164
|
def initialize
|
154
165
|
@sql_data = []
|
155
166
|
end
|
156
167
|
|
157
|
-
def set_transaction_info(path, uri, params)
|
168
|
+
def set_transaction_info(path, uri, params, guid)
|
158
169
|
@path = path
|
159
170
|
@uri = uri
|
160
171
|
@params = params
|
172
|
+
@guid = guid
|
161
173
|
end
|
162
174
|
end
|
163
175
|
|
@@ -255,12 +267,12 @@ module NewRelic
|
|
255
267
|
def consistent_hash(string)
|
256
268
|
if NewRelic::LanguageSupport.using_version?('1.9.2')
|
257
269
|
# String#hash is salted differently on every VM start in 1.9
|
258
|
-
# modulo ensures sql_id fits in an INT(11)
|
259
270
|
require 'digest/md5'
|
260
|
-
Digest::MD5.hexdigest(string).hex
|
271
|
+
Digest::MD5.hexdigest(string).hex
|
261
272
|
else
|
262
273
|
string.hash
|
263
|
-
end
|
274
|
+
end.modulo(2**31-1)
|
275
|
+
# modulo ensures sql_id fits in an INT(11)
|
264
276
|
end
|
265
277
|
end
|
266
278
|
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
module NewRelic
|
3
|
+
module Agent
|
4
|
+
class StatsEngine
|
5
|
+
module GCProfiler
|
6
|
+
def self.init
|
7
|
+
@profiler = RailsBench.new if RailsBench.enabled?
|
8
|
+
@profiler = Ruby19.new if Ruby19.enabled?
|
9
|
+
@profiler = Rubinius.new if Rubinius.enabled?
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.capture
|
13
|
+
@profiler.capture if @profiler
|
14
|
+
end
|
15
|
+
|
16
|
+
class Profiler
|
17
|
+
def initialize
|
18
|
+
if self.class.enabled?
|
19
|
+
@last_timestamp = call_time
|
20
|
+
@last_count = call_count
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def capture
|
25
|
+
return unless self.class.enabled?
|
26
|
+
return if !scope_stack.empty? && scope_stack.last.name == "GC/cumulative"
|
27
|
+
|
28
|
+
num_calls = call_count - @last_count
|
29
|
+
elapsed = (call_time - @last_timestamp).to_f
|
30
|
+
@last_timestamp = call_time
|
31
|
+
@last_count = call_count
|
32
|
+
reset
|
33
|
+
|
34
|
+
record_gc_metric(num_calls, elapsed)
|
35
|
+
end
|
36
|
+
|
37
|
+
def reset; end
|
38
|
+
|
39
|
+
protected
|
40
|
+
|
41
|
+
def record_gc_metric(num_calls, elapsed)
|
42
|
+
if num_calls > 0
|
43
|
+
# µs to seconds
|
44
|
+
elapsed = elapsed / 1_000_000.0
|
45
|
+
# Allocate the GC time to a scope as if the GC just ended
|
46
|
+
# right now.
|
47
|
+
time = Time.now.to_f
|
48
|
+
gc_scope = NewRelic::Agent.instance.stats_engine.push_scope("GC/cumulative", time - elapsed)
|
49
|
+
# GC stats are collected into a blamed metric which allows
|
50
|
+
# us to show the stats controller by controller
|
51
|
+
gc_stats = NewRelic::Agent.get_stats(gc_scope.name, true)
|
52
|
+
gc_stats.record_multiple_data_points(elapsed, num_calls)
|
53
|
+
NewRelic::Agent.instance.stats_engine.pop_scope(gc_scope, elapsed, time)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def scope_stack
|
58
|
+
Thread::current[:newrelic_scope_stack] ||= []
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
class RailsBench < Profiler
|
63
|
+
def self.enabled?
|
64
|
+
::GC.respond_to?(:time) && ::GC.respond_to?(:collections)
|
65
|
+
end
|
66
|
+
|
67
|
+
def call_time
|
68
|
+
::GC.time
|
69
|
+
end
|
70
|
+
|
71
|
+
def call_count
|
72
|
+
::GC.collections
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class Ruby19 < Profiler
|
77
|
+
def self.enabled?
|
78
|
+
defined?(::GC::Profiler) && ::GC::Profiler.enabled?
|
79
|
+
end
|
80
|
+
|
81
|
+
def call_time
|
82
|
+
::GC::Profiler.total_time * 1000.0
|
83
|
+
end
|
84
|
+
|
85
|
+
def call_count
|
86
|
+
::GC.count
|
87
|
+
end
|
88
|
+
|
89
|
+
def reset
|
90
|
+
::GC::Profiler.clear
|
91
|
+
@last_timestamp = 0
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
class Rubinius < Profiler
|
96
|
+
def self.enabled?
|
97
|
+
if NewRelic::LanguageSupport.using_engine?('rbx')
|
98
|
+
require 'rubinius/agent'
|
99
|
+
true
|
100
|
+
else
|
101
|
+
false
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def call_time
|
106
|
+
agent = ::Rubinius::Agent.loopback
|
107
|
+
(agent.get('system.gc.young.total_wallclock')[1] +
|
108
|
+
agent.get('system.gc.full.total_wallclock')[1]) * 1000
|
109
|
+
end
|
110
|
+
|
111
|
+
def call_count
|
112
|
+
agent = ::Rubinius::Agent.loopback
|
113
|
+
agent.get('system.gc.young.count')[1] +
|
114
|
+
agent.get('system.gc.full.count')[1]
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -25,7 +25,6 @@ module Agent
|
|
25
25
|
def end_transaction; end
|
26
26
|
def push_scope(*args); end
|
27
27
|
def transaction_sampler=(*args); end
|
28
|
-
def sql_sampler=(*args); end
|
29
28
|
def scope_name=(*args); end
|
30
29
|
def scope_name; end
|
31
30
|
def pop_scope(*args); end
|
@@ -43,29 +42,11 @@ module Agent
|
|
43
42
|
@transaction_sampler = nil
|
44
43
|
end
|
45
44
|
|
46
|
-
def sql_sampler= sampler
|
47
|
-
fail "Can't add a scope listener midflight in a transaction" if scope_stack.any?
|
48
|
-
@sql_sampler = sampler
|
49
|
-
end
|
50
|
-
|
51
|
-
def remove_sql_sampler(l)
|
52
|
-
@sql_sampler = nil
|
53
|
-
end
|
54
|
-
|
55
45
|
# Pushes a scope onto the transaction stack - this generates a
|
56
46
|
# TransactionSample::Segment at the end of transaction execution
|
57
47
|
def push_scope(metric, time = Time.now.to_f, deduct_call_time_from_parent = true)
|
58
|
-
|
59
48
|
stack = scope_stack
|
60
|
-
|
61
|
-
if stack.empty?
|
62
|
-
# reset the gc time so we only include gc time spent during this call
|
63
|
-
@last_gc_timestamp = gc_time
|
64
|
-
@last_gc_count = gc_collections
|
65
|
-
else
|
66
|
-
capture_gc_time
|
67
|
-
end
|
68
|
-
end
|
49
|
+
stack.empty? ? GCProfiler.init : GCProfiler.capture
|
69
50
|
@transaction_sampler.notice_push_scope metric, time if @transaction_sampler
|
70
51
|
scope = ScopeStackElement.new(metric, deduct_call_time_from_parent)
|
71
52
|
stack.push scope
|
@@ -75,7 +56,7 @@ module Agent
|
|
75
56
|
# Pops a scope off the transaction stack - this updates the
|
76
57
|
# transaction sampler that we've finished execution of a traced method
|
77
58
|
def pop_scope(expected_scope, duration, time=Time.now.to_f)
|
78
|
-
|
59
|
+
GCProfiler.capture
|
79
60
|
stack = scope_stack
|
80
61
|
scope = stack.pop
|
81
62
|
fail "unbalanced pop from blame stack, got #{scope ? scope.name : 'nil'}, expected #{expected_scope ? expected_scope.name : 'nil'}" if scope != expected_scope
|
@@ -106,7 +87,6 @@ module Agent
|
|
106
87
|
# via controller actions
|
107
88
|
def scope_name=(transaction)
|
108
89
|
Thread::current[:newrelic_scope_name] = transaction
|
109
|
-
Thread::current[:newrelic_most_recent_transaction] = transaction
|
110
90
|
end
|
111
91
|
|
112
92
|
# Returns the current scope name from the thread local
|
@@ -134,74 +114,11 @@ module Agent
|
|
134
114
|
end
|
135
115
|
|
136
116
|
private
|
137
|
-
|
138
|
-
# Make sure we don't do this in a multi-threaded environment
|
139
|
-
def collecting_gc?
|
140
|
-
if !defined?(@@collecting_gc)
|
141
|
-
@@collecting_gc = false
|
142
|
-
if !NewRelic::Control.instance.multi_threaded?
|
143
|
-
@@collecting_gc = true if GC.respond_to?(:time) && GC.respond_to?(:collections) # 1.8.x
|
144
|
-
@@collecting_gc = true if defined?(GC::Profiler) && GC::Profiler.enabled? # 1.9.2
|
145
|
-
end
|
146
|
-
end
|
147
|
-
@@collecting_gc
|
148
|
-
end
|
149
|
-
|
150
|
-
# The total number of times the garbage collector has run since
|
151
|
-
# profiling was enabled
|
152
|
-
def gc_collections
|
153
|
-
if GC.respond_to?(:count)
|
154
|
-
GC.count
|
155
|
-
elsif GC.respond_to?(:collections)
|
156
|
-
GC.collections
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
|
-
# The total amount of time taken by garbage collection since
|
161
|
-
# profiling was enabled
|
162
|
-
def gc_time
|
163
|
-
if GC.respond_to?(:time)
|
164
|
-
GC.time
|
165
|
-
elsif defined?(GC::Profiler) && GC::Profiler.respond_to?(:total_time)
|
166
|
-
# The 1.9 profiler returns a time in usec
|
167
|
-
GC::Profiler.total_time * 1000000.0
|
168
|
-
end
|
169
|
-
end
|
170
|
-
|
171
|
-
# Assumes collecting_gc?
|
172
|
-
def capture_gc_time
|
173
|
-
# Skip this if we are already in this segment
|
174
|
-
return if !scope_stack.empty? && scope_stack.last.name == "GC/cumulative"
|
175
|
-
num_calls = gc_collections - @last_gc_count
|
176
|
-
elapsed = (gc_time - @last_gc_timestamp).to_f
|
177
|
-
@last_gc_timestamp = gc_time
|
178
|
-
@last_gc_count = gc_collections
|
179
|
-
|
180
|
-
if defined?(GC::Profiler)
|
181
|
-
GC::Profiler.clear
|
182
|
-
@last_gc_timestamp = 0
|
183
|
-
end
|
184
|
-
|
185
|
-
if num_calls > 0
|
186
|
-
# µs to seconds
|
187
|
-
elapsed = elapsed / 1000000.0
|
188
|
-
# Allocate the GC time to a scope as if the GC just ended
|
189
|
-
# right now.
|
190
|
-
time = Time.now.to_f
|
191
|
-
gc_scope = push_scope("GC/cumulative", time - elapsed)
|
192
|
-
# GC stats are collected into a blamed metric which allows
|
193
|
-
# us to show the stats controller by controller
|
194
|
-
gc_stats = NewRelic::Agent.get_stats(gc_scope.name, true)
|
195
|
-
gc_stats.record_multiple_data_points(elapsed, num_calls)
|
196
|
-
pop_scope(gc_scope, elapsed, time)
|
197
|
-
end
|
198
|
-
end
|
199
117
|
|
200
118
|
# Returns the current scope stack, memoized to a thread local variable
|
201
119
|
def scope_stack
|
202
120
|
Thread::current[:newrelic_scope_stack] ||= []
|
203
121
|
end
|
204
|
-
|
205
122
|
end
|
206
123
|
end
|
207
124
|
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module NewRelic
|
2
|
+
module Agent
|
3
|
+
class TransactionInfo
|
4
|
+
|
5
|
+
attr_accessor :token, :capture_deep_tt, :transaction_name
|
6
|
+
attr_reader :start_time
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@guid = ""
|
10
|
+
@transaction_name = "(unknown)"
|
11
|
+
@start_time = Time.now
|
12
|
+
end
|
13
|
+
|
14
|
+
def force_persist_sample?(sample)
|
15
|
+
token && sample.duration > NewRelic::Control.instance.apdex_t
|
16
|
+
end
|
17
|
+
|
18
|
+
def include_guid?
|
19
|
+
token && duration > NewRelic::Control.instance.apdex_t
|
20
|
+
end
|
21
|
+
|
22
|
+
def guid
|
23
|
+
@guid
|
24
|
+
end
|
25
|
+
|
26
|
+
def guid=(value)
|
27
|
+
@guid = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def duration
|
31
|
+
Time.now - start_time
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.get()
|
35
|
+
Thread.current[:newrelic_transaction_info] ||= TransactionInfo.new
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.set(instance)
|
39
|
+
Thread.current[:newrelic_transaction_info] = instance
|
40
|
+
end
|
41
|
+
|
42
|
+
def self.clear
|
43
|
+
Thread.current[:newrelic_transaction_info] = nil
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
@@ -51,6 +51,8 @@ module NewRelic
|
|
51
51
|
end
|
52
52
|
@sample.root_segment.end_trace(time.to_f - @sample_start)
|
53
53
|
@sample.params[:custom_params] = normalize_params(NewRelic::Agent::Instrumentation::MetricFrame.custom_parameters)
|
54
|
+
|
55
|
+
@sample.force_persist = NewRelic::Agent::TransactionInfo.get.force_persist_sample?(sample)
|
54
56
|
@sample.freeze
|
55
57
|
@current_segment = nil
|
56
58
|
end
|
@@ -20,12 +20,14 @@ module NewRelic
|
|
20
20
|
|
21
21
|
attr_accessor :stack_trace_threshold, :random_sampling, :sampling_rate
|
22
22
|
attr_accessor :explain_threshold, :explain_enabled, :transaction_threshold
|
23
|
+
attr_accessor :slow_capture_threshold
|
23
24
|
attr_reader :samples, :last_sample, :disabled
|
24
25
|
|
25
26
|
def initialize
|
26
27
|
# @samples is an array of recent samples up to @max_samples in
|
27
28
|
# size - it's only used by developer mode
|
28
29
|
@samples = []
|
30
|
+
@force_persist = []
|
29
31
|
@max_samples = 100
|
30
32
|
|
31
33
|
# @harvest_count is a count of harvests used for random
|
@@ -33,6 +35,7 @@ module NewRelic
|
|
33
35
|
@harvest_count = 0
|
34
36
|
@random_sample = nil
|
35
37
|
@sampling_rate = 10
|
38
|
+
@slow_capture_threshold = 2.0
|
36
39
|
configure!
|
37
40
|
|
38
41
|
# This lock is used to synchronize access to the @last_sample
|
@@ -179,6 +182,10 @@ module NewRelic
|
|
179
182
|
store_random_sample(sample)
|
180
183
|
store_sample_for_developer_mode(sample)
|
181
184
|
store_slowest_sample(sample)
|
185
|
+
|
186
|
+
if NewRelic::Agent::TransactionInfo.get.force_persist_sample?(sample)
|
187
|
+
store_force_persist(sample)
|
188
|
+
end
|
182
189
|
end
|
183
190
|
|
184
191
|
# Only active when random sampling is true - this is very rarely
|
@@ -189,6 +196,16 @@ module NewRelic
|
|
189
196
|
@random_sample = sample
|
190
197
|
end
|
191
198
|
end
|
199
|
+
|
200
|
+
def store_force_persist(sample)
|
201
|
+
@force_persist << sample
|
202
|
+
|
203
|
+
# WARNING - this clamp should be configurable
|
204
|
+
if @force_persist.length > 15
|
205
|
+
@force_persist.sort! {|a,b| b.duration <=> a.duration}
|
206
|
+
@force_persist = @force_persist[0..14]
|
207
|
+
end
|
208
|
+
end
|
192
209
|
|
193
210
|
# Samples take up a ton of memory, so we only store a lot of
|
194
211
|
# them in developer mode - we truncate to @max_samples
|
@@ -202,7 +219,9 @@ module NewRelic
|
|
202
219
|
# Sets @slowest_sample to the passed in sample if it is slower
|
203
220
|
# than the current sample in @slowest_sample
|
204
221
|
def store_slowest_sample(sample)
|
205
|
-
|
222
|
+
if slowest_sample?(@slowest_sample, sample)
|
223
|
+
@slowest_sample = sample
|
224
|
+
end
|
206
225
|
end
|
207
226
|
|
208
227
|
# Checks to see if the old sample exists, or if it's duration is
|
@@ -285,7 +304,7 @@ module NewRelic
|
|
285
304
|
# Appends a backtrace to a segment if that segment took longer
|
286
305
|
# than the specified duration
|
287
306
|
def append_backtrace(segment, duration)
|
288
|
-
segment[:backtrace] = caller.join("\n") if duration >= @stack_trace_threshold
|
307
|
+
segment[:backtrace] = caller.join("\n") if (duration >= @stack_trace_threshold || Thread.current[:capture_deep_tt])
|
289
308
|
end
|
290
309
|
|
291
310
|
# some statements (particularly INSERTS with large BLOBS
|
@@ -318,23 +337,40 @@ module NewRelic
|
|
318
337
|
if (@harvest_count.to_i % @sampling_rate.to_i) == 0
|
319
338
|
result << @random_sample if @random_sample
|
320
339
|
end
|
321
|
-
result.uniq!
|
322
340
|
nil # don't assume this method returns anything
|
323
341
|
end
|
342
|
+
|
343
|
+
def add_force_persist_to(result)
|
344
|
+
result.concat(@force_persist)
|
345
|
+
@force_persist = []
|
346
|
+
end
|
324
347
|
|
325
348
|
# Returns an array of slow samples, with either one or two
|
326
349
|
# elements - one element unless random sampling is enabled. The
|
327
350
|
# sample returned will be the slowest sample among those
|
328
351
|
# available during this harvest
|
329
352
|
def add_samples_to(result, slow_threshold)
|
353
|
+
|
354
|
+
# pull out force persist
|
355
|
+
force_persist = result.select {|sample| sample.force_persist} || []
|
356
|
+
result.reject! {|sample| sample.force_persist}
|
357
|
+
|
358
|
+
force_persist.each {|sample| store_force_persist(sample)}
|
359
|
+
|
360
|
+
|
361
|
+
# Now get the slowest sample
|
330
362
|
if @slowest_sample && @slowest_sample.duration >= slow_threshold
|
331
363
|
result << @slowest_sample
|
332
364
|
end
|
365
|
+
|
333
366
|
result.compact!
|
334
367
|
result = result.sort_by { |x| x.duration }
|
335
|
-
result = result[-1..-1] || []
|
368
|
+
result = result[-1..-1] || [] # take the slowest sample
|
369
|
+
|
336
370
|
add_random_sample_to(result)
|
337
|
-
result
|
371
|
+
add_force_persist_to(result)
|
372
|
+
|
373
|
+
result.uniq
|
338
374
|
end
|
339
375
|
|
340
376
|
# get the set of collected samples, merging into previous samples,
|
@@ -344,18 +380,42 @@ module NewRelic
|
|
344
380
|
def harvest(previous = [], slow_threshold = 2.0)
|
345
381
|
return [] if disabled
|
346
382
|
result = Array(previous)
|
383
|
+
|
347
384
|
@samples_lock.synchronize do
|
348
385
|
result = add_samples_to(result, slow_threshold)
|
386
|
+
|
349
387
|
# clear previous transaction samples
|
350
388
|
@slowest_sample = nil
|
351
389
|
@random_sample = nil
|
352
390
|
@last_sample = nil
|
353
391
|
end
|
392
|
+
|
393
|
+
# Clamp the number of TTs we'll keep in memory and send
|
394
|
+
#
|
395
|
+
result = clamp_number_tts(result, 20) if result.length > 20
|
396
|
+
|
354
397
|
# Truncate the samples at 2100 segments. The UI will clamp them at 2000 segments anyway.
|
355
398
|
# This will save us memory and bandwidth.
|
356
399
|
result.each { |sample| sample.truncate(@segment_limit) }
|
357
400
|
result
|
358
401
|
end
|
402
|
+
|
403
|
+
# JON - THIS CODE NEEDS A UNIT TEST
|
404
|
+
def clamp_number_tts(tts, limit)
|
405
|
+
tts.sort! do |a,b|
|
406
|
+
if a.force_persist && b.force_persist
|
407
|
+
b.duration <=> a.duration
|
408
|
+
elsif a.force_persist
|
409
|
+
-1
|
410
|
+
elsif b.force_persist
|
411
|
+
1
|
412
|
+
else
|
413
|
+
b.duration <=> a.duration
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
tts[0..(limit-1)]
|
418
|
+
end
|
359
419
|
|
360
420
|
# reset samples without rebooting the web server
|
361
421
|
def reset!
|
@@ -365,8 +425,6 @@ module NewRelic
|
|
365
425
|
@slowest_sample = nil
|
366
426
|
end
|
367
427
|
|
368
|
-
private
|
369
|
-
|
370
428
|
# Checks to see if the transaction sampler is disabled, if
|
371
429
|
# transaction trace recording is disabled by a thread local, or
|
372
430
|
# if execution is untraced - if so it clears the transaction
|