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.

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