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.

Files changed (38) hide show
  1. data/CHANGELOG +7 -3
  2. data/LICENSE +2 -29
  3. data/README.rdoc +2 -2
  4. data/lib/new_relic/agent.rb +14 -0
  5. data/lib/new_relic/agent/agent.rb +19 -9
  6. data/lib/new_relic/agent/beacon_configuration.rb +11 -0
  7. data/lib/new_relic/agent/browser_monitoring.rb +53 -13
  8. data/lib/new_relic/agent/database.rb +11 -1
  9. data/lib/new_relic/agent/instrumentation/active_merchant.rb +3 -1
  10. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +3 -2
  11. data/lib/new_relic/agent/instrumentation/metric_frame.rb +23 -2
  12. data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +15 -12
  13. data/lib/new_relic/agent/instrumentation/rails3/active_record_instrumentation.rb +11 -8
  14. data/lib/new_relic/agent/sql_sampler.rb +19 -7
  15. data/lib/new_relic/agent/stats_engine.rb +1 -0
  16. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +120 -0
  17. data/lib/new_relic/agent/stats_engine/transactions.rb +2 -85
  18. data/lib/new_relic/agent/transaction_info.rb +49 -0
  19. data/lib/new_relic/agent/transaction_sample_builder.rb +2 -0
  20. data/lib/new_relic/agent/transaction_sampler.rb +65 -7
  21. data/lib/new_relic/rack/browser_monitoring.rb +38 -8
  22. data/lib/new_relic/transaction_sample.rb +8 -6
  23. data/lib/new_relic/version.rb +2 -2
  24. data/newrelic.yml +1 -1
  25. data/newrelic_rpm.gemspec +6 -3
  26. data/test/new_relic/agent/agent/connect_test.rb +4 -11
  27. data/test/new_relic/agent/beacon_configuration_test.rb +10 -7
  28. data/test/new_relic/agent/browser_monitoring_test.rb +69 -44
  29. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +12 -8
  30. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +0 -2
  31. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +0 -1
  32. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +3 -3
  33. data/test/new_relic/agent/sql_sampler_test.rb +25 -10
  34. data/test/new_relic/agent/stats_engine_test.rb +41 -6
  35. data/test/new_relic/agent/transaction_sampler_test.rb +22 -12
  36. data/test/new_relic/metric_parser/metric_parser_test.rb +11 -0
  37. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +7 -1
  38. 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, supported_config, (Time.now - t0).to_f)
70
- NewRelic::Agent.instance.sql_sampler.notice_sql(sql, metric, supported_config, (Time.now - t0).to_f)
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
- transaction_data.set_transaction_info(path, uri, params) if !disabled && transaction_data
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.modulo(2**31-1)
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
@@ -1,6 +1,7 @@
1
1
  require 'new_relic/agent/stats_engine/metric_stats'
2
2
  require 'new_relic/agent/stats_engine/samplers'
3
3
  require 'new_relic/agent/stats_engine/transactions'
4
+ require 'new_relic/agent/stats_engine/gc_profiler'
4
5
 
5
6
  module NewRelic
6
7
  module Agent
@@ -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
- if collecting_gc?
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
- capture_gc_time if collecting_gc?
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
- @slowest_sample = sample if slowest_sample?(@slowest_sample, sample)
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