dolores_rpm 3.2.0.6 → 3.3.4.fork
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +32 -6
- data/dolores_rpm.gemspec +15 -11
- data/lib/new_relic/agent/agent.rb +28 -14
- 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 +34 -14
- data/lib/new_relic/agent/error_collector.rb +1 -1
- data/lib/new_relic/agent/instrumentation/active_merchant.rb +3 -1
- data/lib/new_relic/agent/instrumentation/active_record.rb +137 -0
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +24 -5
- data/lib/new_relic/agent/instrumentation/data_mapper.rb +4 -36
- data/lib/new_relic/agent/instrumentation/metric_frame.rb +24 -3
- data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +3 -2
- data/lib/new_relic/agent/instrumentation/queue_time.rb +1 -1
- data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +88 -30
- data/lib/new_relic/agent/instrumentation/sinatra.rb +40 -20
- data/lib/new_relic/agent/samplers/memory_sampler.rb +5 -6
- data/lib/new_relic/agent/sql_sampler.rb +35 -16
- data/lib/new_relic/agent/stats_engine/gc_profiler.rb +123 -0
- data/lib/new_relic/agent/stats_engine/samplers.rb +1 -1
- data/lib/new_relic/agent/stats_engine/transactions.rb +2 -85
- data/lib/new_relic/agent/stats_engine.rb +1 -0
- data/lib/new_relic/agent/transaction_info.rb +74 -0
- data/lib/new_relic/agent/transaction_sample_builder.rb +17 -3
- data/lib/new_relic/agent/transaction_sampler.rb +86 -15
- data/lib/new_relic/agent/worker_loop.rb +1 -1
- data/lib/new_relic/agent.rb +15 -2
- data/lib/new_relic/collection_helper.rb +8 -6
- data/lib/new_relic/command.rb +1 -1
- data/lib/new_relic/commands/deployments.rb +1 -1
- data/lib/new_relic/control/configuration.rb +6 -2
- data/lib/new_relic/control/frameworks/merb.rb +1 -1
- data/lib/new_relic/control/frameworks/rails.rb +5 -5
- data/lib/new_relic/control/frameworks/rails3.rb +2 -2
- data/lib/new_relic/control/instance_methods.rb +3 -3
- data/lib/new_relic/control/instrumentation.rb +1 -1
- data/lib/new_relic/control/server_methods.rb +2 -2
- data/lib/new_relic/data_serialization.rb +10 -16
- data/lib/new_relic/delayed_job_injection.rb +6 -1
- data/lib/new_relic/language_support.rb +11 -7
- data/lib/new_relic/local_environment.rb +24 -10
- data/lib/new_relic/metric_spec.rb +7 -6
- data/lib/new_relic/noticed_error.rb +6 -1
- data/lib/new_relic/rack/browser_monitoring.rb +21 -13
- data/lib/new_relic/rack/developer_mode.rb +2 -2
- data/lib/new_relic/recipes.rb +8 -4
- data/lib/new_relic/stats.rb +0 -53
- data/lib/new_relic/transaction_sample/segment.rb +2 -0
- data/lib/new_relic/transaction_sample.rb +39 -23
- data/lib/new_relic/version.rb +3 -3
- data/test/active_record_fixtures.rb +3 -3
- data/test/fixtures/proc_cpuinfo.txt +575 -0
- data/test/new_relic/agent/agent/connect_test.rb +4 -11
- data/test/new_relic/agent/agent_test.rb +1 -0
- data/test/new_relic/agent/agent_test_controller_test.rb +20 -1
- data/test/new_relic/agent/beacon_configuration_test.rb +10 -7
- data/test/new_relic/agent/browser_monitoring_test.rb +90 -45
- data/test/new_relic/agent/database_test.rb +34 -47
- data/test/new_relic/agent/error_collector_test.rb +32 -15
- data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +56 -18
- 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/instrumentation/queue_time_test.rb +6 -11
- data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +1 -1
- data/test/new_relic/agent/method_tracer_test.rb +2 -2
- data/test/new_relic/agent/rpm_agent_test.rb +1 -1
- data/test/new_relic/agent/sql_sampler_test.rb +48 -16
- data/test/new_relic/agent/stats_engine_test.rb +41 -6
- data/test/new_relic/agent/transaction_info_test.rb +13 -0
- data/test/new_relic/agent/transaction_sample_builder_test.rb +19 -3
- data/test/new_relic/agent/transaction_sampler_test.rb +49 -37
- data/test/new_relic/agent/worker_loop_test.rb +1 -1
- data/test/new_relic/agent_test.rb +12 -0
- data/test/new_relic/control/configuration_test.rb +12 -0
- data/test/new_relic/control_test.rb +2 -0
- data/test/new_relic/data_serialization_test.rb +12 -12
- data/test/new_relic/local_environment_test.rb +14 -1
- data/test/new_relic/metric_parser/metric_parser_test.rb +11 -0
- data/test/new_relic/rack/browser_monitoring_test.rb +81 -23
- data/test/new_relic/rack/developer_mode_test.rb +31 -0
- data/test/new_relic/stats_test.rb +0 -15
- data/test/new_relic/transaction_sample_test.rb +13 -0
- data/test/script/build_test_gem.sh +51 -0
- data/test/script/ci.sh +94 -0
- data/test/script/ci_bench.sh +52 -0
- data/test/test_helper.rb +1 -0
- data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +5 -0
- data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +17 -4
- metadata +25 -19
- data/dolores_rpm-3.2.0.2.gem +0 -0
- data/dolores_rpm-3.2.0.5.gem +0 -0
- data/dolores_rpm-3.3.4.fork.gem +0 -0
- data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +0 -115
- data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +0 -122
@@ -0,0 +1,123 @@
|
|
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
|
+
# microseconds 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
|
+
# microseconds spent in GC
|
68
|
+
def call_time
|
69
|
+
::GC.time # this should already be microseconds
|
70
|
+
end
|
71
|
+
|
72
|
+
def call_count
|
73
|
+
::GC.collections
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
class Ruby19 < Profiler
|
78
|
+
def self.enabled?
|
79
|
+
defined?(::GC::Profiler) && ::GC::Profiler.enabled?
|
80
|
+
end
|
81
|
+
|
82
|
+
# microseconds spent in GC
|
83
|
+
# 1.9 total_time returns seconds. Don't trust the docs. It's seconds.
|
84
|
+
def call_time
|
85
|
+
::GC::Profiler.total_time * 1_000_000.0 # convert seconds to microseconds
|
86
|
+
end
|
87
|
+
|
88
|
+
def call_count
|
89
|
+
::GC.count
|
90
|
+
end
|
91
|
+
|
92
|
+
def reset
|
93
|
+
::GC::Profiler.clear
|
94
|
+
@last_timestamp = 0
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
class Rubinius < Profiler
|
99
|
+
def self.enabled?
|
100
|
+
if NewRelic::LanguageSupport.using_engine?('rbx')
|
101
|
+
require 'rubinius/agent'
|
102
|
+
true
|
103
|
+
else
|
104
|
+
false
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def call_time
|
109
|
+
agent = ::Rubinius::Agent.loopback
|
110
|
+
(agent.get('system.gc.young.total_wallclock')[1] +
|
111
|
+
agent.get('system.gc.full.total_wallclock')[1]) * 1000
|
112
|
+
end
|
113
|
+
|
114
|
+
def call_count
|
115
|
+
agent = ::Rubinius::Agent.loopback
|
116
|
+
agent.get('system.gc.young.count')[1] +
|
117
|
+
agent.get('system.gc.full.count')[1]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
123
|
+
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,74 @@
|
|
1
|
+
require 'erb'
|
2
|
+
|
3
|
+
module NewRelic
|
4
|
+
module Agent
|
5
|
+
class TransactionInfo
|
6
|
+
|
7
|
+
attr_accessor :token, :capture_deep_tt, :transaction_name
|
8
|
+
attr_reader :start_time
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@guid = ""
|
12
|
+
@transaction_name = "(unknown)"
|
13
|
+
@start_time = Time.now
|
14
|
+
end
|
15
|
+
|
16
|
+
def force_persist_sample?(sample)
|
17
|
+
token && sample.duration > NewRelic::Control.instance.apdex_t
|
18
|
+
end
|
19
|
+
|
20
|
+
def include_guid?
|
21
|
+
token && duration > NewRelic::Control.instance.apdex_t
|
22
|
+
end
|
23
|
+
|
24
|
+
def guid
|
25
|
+
@guid
|
26
|
+
end
|
27
|
+
|
28
|
+
def guid=(value)
|
29
|
+
@guid = value
|
30
|
+
end
|
31
|
+
|
32
|
+
def duration
|
33
|
+
Time.now - start_time
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.get()
|
37
|
+
Thread.current[:newrelic_transaction_info] ||= TransactionInfo.new
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.set(instance)
|
41
|
+
Thread.current[:newrelic_transaction_info] = instance
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.clear
|
45
|
+
Thread.current[:newrelic_transaction_info] = nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# clears any existing transaction info object and initializes a new one.
|
49
|
+
# This starts the timer for the transaction.
|
50
|
+
def self.reset(request=nil)
|
51
|
+
clear
|
52
|
+
instance = get
|
53
|
+
instance.token = get_token(request)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.get_token(request)
|
57
|
+
return nil unless request
|
58
|
+
|
59
|
+
agent_flag = request.cookies['NRAGENT']
|
60
|
+
if agent_flag
|
61
|
+
s = agent_flag.split("=")
|
62
|
+
if s.length == 2
|
63
|
+
if s[0] == "tk" && s[1]
|
64
|
+
ERB::Util.h(s[1])
|
65
|
+
end
|
66
|
+
end
|
67
|
+
else
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
@@ -9,6 +9,7 @@ module NewRelic
|
|
9
9
|
# accessed by any other thread so no need for synchronization.
|
10
10
|
class TransactionSampleBuilder
|
11
11
|
attr_reader :current_segment, :sample
|
12
|
+
attr_accessor :segment_limit
|
12
13
|
|
13
14
|
include NewRelic::CollectionHelper
|
14
15
|
|
@@ -16,24 +17,35 @@ module NewRelic
|
|
16
17
|
@sample = NewRelic::TransactionSample.new(time.to_f)
|
17
18
|
@sample_start = time.to_f
|
18
19
|
@current_segment = @sample.root_segment
|
20
|
+
@segment_limit = NewRelic::Control.instance.fetch('transaction_tracer', {}) \
|
21
|
+
.fetch('limit_segments', 4000)
|
19
22
|
end
|
20
23
|
|
21
24
|
def sample_id
|
22
25
|
@sample.sample_id
|
23
26
|
end
|
27
|
+
|
24
28
|
def ignored?
|
25
29
|
@ignore || @sample.params[:path].nil?
|
26
30
|
end
|
31
|
+
|
27
32
|
def ignore_transaction
|
28
33
|
@ignore = true
|
29
34
|
end
|
35
|
+
|
30
36
|
def trace_entry(metric_name, time)
|
31
|
-
|
32
|
-
|
33
|
-
|
37
|
+
if @sample.count_segments < @segment_limit
|
38
|
+
segment = @sample.create_segment(time.to_f - @sample_start, metric_name)
|
39
|
+
@current_segment.add_called_segment(segment)
|
40
|
+
@current_segment = segment
|
41
|
+
if @sample.count_segments == @segment_limit
|
42
|
+
NewRelic::Control.instance.log.debug("Segment limit of #{@segment_limit} reached, ceasing collection.")
|
43
|
+
end
|
44
|
+
end
|
34
45
|
end
|
35
46
|
|
36
47
|
def trace_exit(metric_name, time)
|
48
|
+
return unless @sample.count_segments < @segment_limit
|
37
49
|
if metric_name != @current_segment.metric_name
|
38
50
|
fail "unbalanced entry/exit: #{metric_name} != #{@current_segment.metric_name}"
|
39
51
|
end
|
@@ -51,6 +63,8 @@ module NewRelic
|
|
51
63
|
end
|
52
64
|
@sample.root_segment.end_trace(time.to_f - @sample_start)
|
53
65
|
@sample.params[:custom_params] = normalize_params(NewRelic::Agent::Instrumentation::MetricFrame.custom_parameters)
|
66
|
+
|
67
|
+
@sample.force_persist = NewRelic::Agent::TransactionInfo.get.force_persist_sample?(sample)
|
54
68
|
@sample.freeze
|
55
69
|
@current_segment = nil
|
56
70
|
end
|
@@ -1,9 +1,10 @@
|
|
1
1
|
require 'new_relic/agent'
|
2
2
|
require 'new_relic/control'
|
3
3
|
require 'new_relic/agent/transaction_sample_builder'
|
4
|
+
|
4
5
|
module NewRelic
|
5
6
|
module Agent
|
6
|
-
|
7
|
+
|
7
8
|
# This class contains the logic of sampling a transaction -
|
8
9
|
# creation and modification of transaction samples
|
9
10
|
class TransactionSampler
|
@@ -20,12 +21,15 @@ module NewRelic
|
|
20
21
|
|
21
22
|
attr_accessor :stack_trace_threshold, :random_sampling, :sampling_rate
|
22
23
|
attr_accessor :explain_threshold, :explain_enabled, :transaction_threshold
|
24
|
+
attr_accessor :slow_capture_threshold
|
23
25
|
attr_reader :samples, :last_sample, :disabled
|
24
26
|
|
25
27
|
def initialize
|
28
|
+
|
26
29
|
# @samples is an array of recent samples up to @max_samples in
|
27
30
|
# size - it's only used by developer mode
|
28
31
|
@samples = []
|
32
|
+
@force_persist = []
|
29
33
|
@max_samples = 100
|
30
34
|
|
31
35
|
# @harvest_count is a count of harvests used for random
|
@@ -33,6 +37,7 @@ module NewRelic
|
|
33
37
|
@harvest_count = 0
|
34
38
|
@random_sample = nil
|
35
39
|
@sampling_rate = 10
|
40
|
+
@slow_capture_threshold = 2.0
|
36
41
|
configure!
|
37
42
|
|
38
43
|
# This lock is used to synchronize access to the @last_sample
|
@@ -45,14 +50,20 @@ module NewRelic
|
|
45
50
|
# @segment_limit and @stack_trace_threshold come from the
|
46
51
|
# configuration file, with built-in defaults that should
|
47
52
|
# suffice for most customers
|
48
|
-
|
53
|
+
|
49
54
|
# enable if config.fetch('enabled', true)
|
50
|
-
|
55
|
+
|
51
56
|
@segment_limit = config.fetch('limit_segments', 4000)
|
52
57
|
@stack_trace_threshold = config.fetch('stack_trace_threshold', 0.500).to_f
|
53
58
|
@explain_threshold = config.fetch('explain_threshold', 0.5).to_f
|
54
59
|
@explain_enabled = config.fetch('explain_enabled', true)
|
55
60
|
@transaction_threshold = config.fetch('transation_threshold', 2.0)
|
61
|
+
|
62
|
+
# Configure the sample storage policy. Create a list of methods to be called.
|
63
|
+
@store_sampler_methods = [ :store_random_sample, :store_slowest_sample ]
|
64
|
+
if NewRelic::Control.instance.developer_mode?
|
65
|
+
@store_sampler_methods << :store_sample_for_developer_mode
|
66
|
+
end
|
56
67
|
end
|
57
68
|
|
58
69
|
def config
|
@@ -176,9 +187,11 @@ module NewRelic
|
|
176
187
|
# @samples array, and the @slowest_sample variable if it is
|
177
188
|
# slower than the current occupant of that slot
|
178
189
|
def store_sample(sample)
|
179
|
-
|
180
|
-
|
181
|
-
|
190
|
+
@store_sampler_methods.each{|sym| send sym, sample}
|
191
|
+
if NewRelic::Agent::TransactionInfo.get.force_persist_sample?(sample)
|
192
|
+
store_force_persist(sample)
|
193
|
+
end
|
194
|
+
|
182
195
|
end
|
183
196
|
|
184
197
|
# Only active when random sampling is true - this is very rarely
|
@@ -190,6 +203,16 @@ module NewRelic
|
|
190
203
|
end
|
191
204
|
end
|
192
205
|
|
206
|
+
def store_force_persist(sample)
|
207
|
+
@force_persist << sample
|
208
|
+
|
209
|
+
# WARNING - this clamp should be configurable
|
210
|
+
if @force_persist.length > 15
|
211
|
+
@force_persist.sort! {|a,b| b.duration <=> a.duration}
|
212
|
+
@force_persist = @force_persist[0..14]
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
193
216
|
# Samples take up a ton of memory, so we only store a lot of
|
194
217
|
# them in developer mode - we truncate to @max_samples
|
195
218
|
def store_sample_for_developer_mode(sample)
|
@@ -202,7 +225,9 @@ module NewRelic
|
|
202
225
|
# Sets @slowest_sample to the passed in sample if it is slower
|
203
226
|
# than the current sample in @slowest_sample
|
204
227
|
def store_slowest_sample(sample)
|
205
|
-
|
228
|
+
if slowest_sample?(@slowest_sample, sample)
|
229
|
+
@slowest_sample = sample
|
230
|
+
end
|
206
231
|
end
|
207
232
|
|
208
233
|
# Checks to see if the old sample exists, or if it's duration is
|
@@ -252,8 +277,14 @@ module NewRelic
|
|
252
277
|
return unless builder
|
253
278
|
segment = builder.current_segment
|
254
279
|
if segment
|
255
|
-
|
256
|
-
|
280
|
+
new_message = truncate_message(append_new_message(segment[key],
|
281
|
+
message))
|
282
|
+
if key == :sql && config.respond_to?(:has_key?) && config.has_key?(:adapter)
|
283
|
+
segment[key] = Database::Statement.new(new_message)
|
284
|
+
segment[key].adapter = config[:adapter]
|
285
|
+
else
|
286
|
+
segment[key] = new_message
|
287
|
+
end
|
257
288
|
segment[config_key] = config if config_key
|
258
289
|
append_backtrace(segment, duration)
|
259
290
|
end
|
@@ -285,7 +316,7 @@ module NewRelic
|
|
285
316
|
# Appends a backtrace to a segment if that segment took longer
|
286
317
|
# than the specified duration
|
287
318
|
def append_backtrace(segment, duration)
|
288
|
-
segment[:backtrace] = caller.join("\n") if duration >= @stack_trace_threshold
|
319
|
+
segment[:backtrace] = caller.join("\n") if (duration >= @stack_trace_threshold || Thread.current[:capture_deep_tt])
|
289
320
|
end
|
290
321
|
|
291
322
|
# some statements (particularly INSERTS with large BLOBS
|
@@ -317,24 +348,42 @@ module NewRelic
|
|
317
348
|
@harvest_count += 1
|
318
349
|
if (@harvest_count.to_i % @sampling_rate.to_i) == 0
|
319
350
|
result << @random_sample if @random_sample
|
351
|
+
@harvest_count = 0
|
320
352
|
end
|
321
|
-
result.uniq!
|
322
353
|
nil # don't assume this method returns anything
|
323
354
|
end
|
324
355
|
|
356
|
+
def add_force_persist_to(result)
|
357
|
+
result.concat(@force_persist)
|
358
|
+
@force_persist = []
|
359
|
+
end
|
360
|
+
|
325
361
|
# Returns an array of slow samples, with either one or two
|
326
362
|
# elements - one element unless random sampling is enabled. The
|
327
363
|
# sample returned will be the slowest sample among those
|
328
364
|
# available during this harvest
|
329
365
|
def add_samples_to(result, slow_threshold)
|
366
|
+
|
367
|
+
# pull out force persist
|
368
|
+
force_persist = result.select {|sample| sample.force_persist} || []
|
369
|
+
result.reject! {|sample| sample.force_persist}
|
370
|
+
|
371
|
+
force_persist.each {|sample| store_force_persist(sample)}
|
372
|
+
|
373
|
+
|
374
|
+
# Now get the slowest sample
|
330
375
|
if @slowest_sample && @slowest_sample.duration >= slow_threshold
|
331
376
|
result << @slowest_sample
|
332
377
|
end
|
378
|
+
|
333
379
|
result.compact!
|
334
380
|
result = result.sort_by { |x| x.duration }
|
335
|
-
result = result[-1..-1] || []
|
381
|
+
result = result[-1..-1] || [] # take the slowest sample
|
382
|
+
|
336
383
|
add_random_sample_to(result)
|
337
|
-
result
|
384
|
+
add_force_persist_to(result)
|
385
|
+
|
386
|
+
result.uniq
|
338
387
|
end
|
339
388
|
|
340
389
|
# get the set of collected samples, merging into previous samples,
|
@@ -344,19 +393,43 @@ module NewRelic
|
|
344
393
|
def harvest(previous = [], slow_threshold = 2.0)
|
345
394
|
return [] if disabled
|
346
395
|
result = Array(previous)
|
396
|
+
|
347
397
|
@samples_lock.synchronize do
|
348
398
|
result = add_samples_to(result, slow_threshold)
|
399
|
+
|
349
400
|
# clear previous transaction samples
|
350
401
|
@slowest_sample = nil
|
351
402
|
@random_sample = nil
|
352
403
|
@last_sample = nil
|
353
404
|
end
|
405
|
+
|
406
|
+
# Clamp the number of TTs we'll keep in memory and send
|
407
|
+
#
|
408
|
+
result = clamp_number_tts(result, 20) if result.length > 20
|
409
|
+
|
354
410
|
# Truncate the samples at 2100 segments. The UI will clamp them at 2000 segments anyway.
|
355
411
|
# This will save us memory and bandwidth.
|
356
412
|
result.each { |sample| sample.truncate(@segment_limit) }
|
357
413
|
result
|
358
414
|
end
|
359
415
|
|
416
|
+
# JON - THIS CODE NEEDS A UNIT TEST
|
417
|
+
def clamp_number_tts(tts, limit)
|
418
|
+
tts.sort! do |a,b|
|
419
|
+
if a.force_persist && b.force_persist
|
420
|
+
b.duration <=> a.duration
|
421
|
+
elsif a.force_persist
|
422
|
+
-1
|
423
|
+
elsif b.force_persist
|
424
|
+
1
|
425
|
+
else
|
426
|
+
b.duration <=> a.duration
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
tts[0..(limit-1)]
|
431
|
+
end
|
432
|
+
|
360
433
|
# reset samples without rebooting the web server
|
361
434
|
def reset!
|
362
435
|
@samples = []
|
@@ -365,8 +438,6 @@ module NewRelic
|
|
365
438
|
@slowest_sample = nil
|
366
439
|
end
|
367
440
|
|
368
|
-
private
|
369
|
-
|
370
441
|
# Checks to see if the transaction sampler is disabled, if
|
371
442
|
# transaction trace recording is disabled by a thread local, or
|
372
443
|
# if execution is untraced - if so it clears the transaction
|
@@ -74,7 +74,7 @@ module NewRelic
|
|
74
74
|
# Want to ignore these because they are handled already
|
75
75
|
rescue SystemExit, NoMemoryError, SignalException
|
76
76
|
raise
|
77
|
-
rescue
|
77
|
+
rescue => e
|
78
78
|
# Don't blow out the stack for anything that hasn't already propagated
|
79
79
|
log.error "Error running task in Agent Worker Loop '#{e}': #{e.backtrace.first}"
|
80
80
|
log.debug e.backtrace.join("\n")
|
data/lib/new_relic/agent.rb
CHANGED
@@ -86,6 +86,7 @@ module NewRelic
|
|
86
86
|
require 'new_relic/agent/busy_calculator'
|
87
87
|
require 'new_relic/agent/sampler'
|
88
88
|
require 'new_relic/agent/database'
|
89
|
+
require 'new_relic/agent/transaction_info'
|
89
90
|
|
90
91
|
require 'new_relic/agent/instrumentation/controller_instrumentation'
|
91
92
|
|
@@ -123,8 +124,7 @@ module NewRelic
|
|
123
124
|
|
124
125
|
# The singleton Agent instance. Used internally.
|
125
126
|
def agent #:nodoc:
|
126
|
-
raise
|
127
|
-
@agent
|
127
|
+
@agent || raise("Plugin not initialized!")
|
128
128
|
end
|
129
129
|
|
130
130
|
def agent=(new_instance)#:nodoc:
|
@@ -387,6 +387,19 @@ module NewRelic
|
|
387
387
|
def add_custom_parameters(params)
|
388
388
|
NewRelic::Agent::Instrumentation::MetricFrame.add_custom_parameters(params)
|
389
389
|
end
|
390
|
+
|
391
|
+
# Set attributes about the user making this request. These attributes will be automatically
|
392
|
+
# appended to any Transaction Trace or Error that is collected. These attributes
|
393
|
+
# will also be collected for RUM requests.
|
394
|
+
#
|
395
|
+
# Attributes (hash)
|
396
|
+
# * <tt>:user</tt> => user name or ID
|
397
|
+
# * <tt>:account</tt> => account name or ID
|
398
|
+
# * <tt>:product</tt> => product name or level
|
399
|
+
#
|
400
|
+
def set_user_attributes(attributes)
|
401
|
+
NewRelic::Agent::Instrumentation::MetricFrame.set_user_attributes(attributes)
|
402
|
+
end
|
390
403
|
|
391
404
|
# The #add_request_parameters method is aliased to #add_custom_parameters
|
392
405
|
# and is now deprecated.
|