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.
Files changed (95) hide show
  1. data/CHANGELOG +32 -6
  2. data/dolores_rpm.gemspec +15 -11
  3. data/lib/new_relic/agent/agent.rb +28 -14
  4. data/lib/new_relic/agent/beacon_configuration.rb +11 -0
  5. data/lib/new_relic/agent/browser_monitoring.rb +53 -13
  6. data/lib/new_relic/agent/database.rb +34 -14
  7. data/lib/new_relic/agent/error_collector.rb +1 -1
  8. data/lib/new_relic/agent/instrumentation/active_merchant.rb +3 -1
  9. data/lib/new_relic/agent/instrumentation/active_record.rb +137 -0
  10. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +24 -5
  11. data/lib/new_relic/agent/instrumentation/data_mapper.rb +4 -36
  12. data/lib/new_relic/agent/instrumentation/metric_frame.rb +24 -3
  13. data/lib/new_relic/agent/instrumentation/passenger_instrumentation.rb +3 -2
  14. data/lib/new_relic/agent/instrumentation/queue_time.rb +1 -1
  15. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +88 -30
  16. data/lib/new_relic/agent/instrumentation/sinatra.rb +40 -20
  17. data/lib/new_relic/agent/samplers/memory_sampler.rb +5 -6
  18. data/lib/new_relic/agent/sql_sampler.rb +35 -16
  19. data/lib/new_relic/agent/stats_engine/gc_profiler.rb +123 -0
  20. data/lib/new_relic/agent/stats_engine/samplers.rb +1 -1
  21. data/lib/new_relic/agent/stats_engine/transactions.rb +2 -85
  22. data/lib/new_relic/agent/stats_engine.rb +1 -0
  23. data/lib/new_relic/agent/transaction_info.rb +74 -0
  24. data/lib/new_relic/agent/transaction_sample_builder.rb +17 -3
  25. data/lib/new_relic/agent/transaction_sampler.rb +86 -15
  26. data/lib/new_relic/agent/worker_loop.rb +1 -1
  27. data/lib/new_relic/agent.rb +15 -2
  28. data/lib/new_relic/collection_helper.rb +8 -6
  29. data/lib/new_relic/command.rb +1 -1
  30. data/lib/new_relic/commands/deployments.rb +1 -1
  31. data/lib/new_relic/control/configuration.rb +6 -2
  32. data/lib/new_relic/control/frameworks/merb.rb +1 -1
  33. data/lib/new_relic/control/frameworks/rails.rb +5 -5
  34. data/lib/new_relic/control/frameworks/rails3.rb +2 -2
  35. data/lib/new_relic/control/instance_methods.rb +3 -3
  36. data/lib/new_relic/control/instrumentation.rb +1 -1
  37. data/lib/new_relic/control/server_methods.rb +2 -2
  38. data/lib/new_relic/data_serialization.rb +10 -16
  39. data/lib/new_relic/delayed_job_injection.rb +6 -1
  40. data/lib/new_relic/language_support.rb +11 -7
  41. data/lib/new_relic/local_environment.rb +24 -10
  42. data/lib/new_relic/metric_spec.rb +7 -6
  43. data/lib/new_relic/noticed_error.rb +6 -1
  44. data/lib/new_relic/rack/browser_monitoring.rb +21 -13
  45. data/lib/new_relic/rack/developer_mode.rb +2 -2
  46. data/lib/new_relic/recipes.rb +8 -4
  47. data/lib/new_relic/stats.rb +0 -53
  48. data/lib/new_relic/transaction_sample/segment.rb +2 -0
  49. data/lib/new_relic/transaction_sample.rb +39 -23
  50. data/lib/new_relic/version.rb +3 -3
  51. data/test/active_record_fixtures.rb +3 -3
  52. data/test/fixtures/proc_cpuinfo.txt +575 -0
  53. data/test/new_relic/agent/agent/connect_test.rb +4 -11
  54. data/test/new_relic/agent/agent_test.rb +1 -0
  55. data/test/new_relic/agent/agent_test_controller_test.rb +20 -1
  56. data/test/new_relic/agent/beacon_configuration_test.rb +10 -7
  57. data/test/new_relic/agent/browser_monitoring_test.rb +90 -45
  58. data/test/new_relic/agent/database_test.rb +34 -47
  59. data/test/new_relic/agent/error_collector_test.rb +32 -15
  60. data/test/new_relic/agent/instrumentation/active_record_instrumentation_test.rb +56 -18
  61. data/test/new_relic/agent/instrumentation/controller_instrumentation_test.rb +0 -2
  62. data/test/new_relic/agent/instrumentation/metric_frame/pop_test.rb +0 -1
  63. data/test/new_relic/agent/instrumentation/net_instrumentation_test.rb +3 -3
  64. data/test/new_relic/agent/instrumentation/queue_time_test.rb +6 -11
  65. data/test/new_relic/agent/method_tracer/class_methods/add_method_tracer_test.rb +1 -1
  66. data/test/new_relic/agent/method_tracer_test.rb +2 -2
  67. data/test/new_relic/agent/rpm_agent_test.rb +1 -1
  68. data/test/new_relic/agent/sql_sampler_test.rb +48 -16
  69. data/test/new_relic/agent/stats_engine_test.rb +41 -6
  70. data/test/new_relic/agent/transaction_info_test.rb +13 -0
  71. data/test/new_relic/agent/transaction_sample_builder_test.rb +19 -3
  72. data/test/new_relic/agent/transaction_sampler_test.rb +49 -37
  73. data/test/new_relic/agent/worker_loop_test.rb +1 -1
  74. data/test/new_relic/agent_test.rb +12 -0
  75. data/test/new_relic/control/configuration_test.rb +12 -0
  76. data/test/new_relic/control_test.rb +2 -0
  77. data/test/new_relic/data_serialization_test.rb +12 -12
  78. data/test/new_relic/local_environment_test.rb +14 -1
  79. data/test/new_relic/metric_parser/metric_parser_test.rb +11 -0
  80. data/test/new_relic/rack/browser_monitoring_test.rb +81 -23
  81. data/test/new_relic/rack/developer_mode_test.rb +31 -0
  82. data/test/new_relic/stats_test.rb +0 -15
  83. data/test/new_relic/transaction_sample_test.rb +13 -0
  84. data/test/script/build_test_gem.sh +51 -0
  85. data/test/script/ci.sh +94 -0
  86. data/test/script/ci_bench.sh +52 -0
  87. data/test/test_helper.rb +1 -0
  88. data/vendor/gems/dependency_detection-0.0.1.build/lib/dependency_detection.rb +5 -0
  89. data/vendor/gems/metric_parser-0.1.0.pre1/lib/new_relic/metric_parser/metric_parser.rb +17 -4
  90. metadata +25 -19
  91. data/dolores_rpm-3.2.0.2.gem +0 -0
  92. data/dolores_rpm-3.2.0.5.gem +0 -0
  93. data/dolores_rpm-3.3.4.fork.gem +0 -0
  94. data/lib/new_relic/agent/instrumentation/rails/active_record_instrumentation.rb +0 -115
  95. 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
@@ -74,7 +74,7 @@ module Agent
74
74
  begin
75
75
  sampled_item.poll
76
76
  false # it's okay. don't delete it.
77
- rescue Exception => e
77
+ rescue => e
78
78
  log.error "Removing #{sampled_item} from list"
79
79
  log.error e
80
80
  log.debug e.backtrace.to_s
@@ -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
@@ -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,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
- segment = @sample.create_segment(time.to_f - @sample_start, metric_name)
32
- @current_segment.add_called_segment(segment)
33
- @current_segment = segment
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
- store_random_sample(sample)
180
- store_sample_for_developer_mode(sample)
181
- store_slowest_sample(sample)
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
- @slowest_sample = sample if slowest_sample?(@slowest_sample, sample)
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
- segment[key] = truncate_message(append_new_message(segment[key],
256
- message))
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 Exception => e
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")
@@ -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 "Plugin not initialized!" if @agent.nil?
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.