newrelic_rpm 3.2.0.1 → 3.3.0.beta1
Sign up to get free protection for your applications and to get access to all the features.
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
|