newrelic_rpm 4.6.0.338 → 4.7.0.339

Sign up to get free protection for your applications and to get access to all the features.
Files changed (53) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +61 -0
  3. data/lib/new_relic/agent/configuration/default_source.rb +7 -0
  4. data/lib/new_relic/agent/datastores.rb +9 -2
  5. data/lib/new_relic/agent/error_collector.rb +4 -2
  6. data/lib/new_relic/agent/external.rb +5 -1
  7. data/lib/new_relic/agent/http_clients/excon_wrappers.rb +3 -1
  8. data/lib/new_relic/agent/http_clients/net_http_wrappers.rb +4 -2
  9. data/lib/new_relic/agent/instrumentation/action_cable_subscriber.rb +1 -1
  10. data/lib/new_relic/agent/instrumentation/action_view_subscriber.rb +1 -1
  11. data/lib/new_relic/agent/instrumentation/active_record.rb +17 -3
  12. data/lib/new_relic/agent/instrumentation/active_record_4.rb +9 -1
  13. data/lib/new_relic/agent/instrumentation/active_record_5.rb +5 -61
  14. data/lib/new_relic/agent/instrumentation/active_record_prepend.rb +61 -0
  15. data/lib/new_relic/agent/instrumentation/bunny.rb +3 -1
  16. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +1 -0
  17. data/lib/new_relic/agent/instrumentation/curb.rb +4 -1
  18. data/lib/new_relic/agent/instrumentation/data_mapper.rb +5 -1
  19. data/lib/new_relic/agent/instrumentation/excon/connection.rb +4 -1
  20. data/lib/new_relic/agent/instrumentation/excon/middleware.rb +4 -1
  21. data/lib/new_relic/agent/instrumentation/http.rb +4 -1
  22. data/lib/new_relic/agent/instrumentation/httpclient.rb +4 -1
  23. data/lib/new_relic/agent/instrumentation/memcache.rb +4 -1
  24. data/lib/new_relic/agent/instrumentation/memcache/dalli.rb +5 -2
  25. data/lib/new_relic/agent/instrumentation/mongo.rb +6 -1
  26. data/lib/new_relic/agent/instrumentation/mongodb_command_subscriber.rb +6 -1
  27. data/lib/new_relic/agent/instrumentation/net.rb +4 -1
  28. data/lib/new_relic/agent/instrumentation/rails/action_controller.rb +1 -1
  29. data/lib/new_relic/agent/instrumentation/rails3/action_controller.rb +5 -3
  30. data/lib/new_relic/agent/instrumentation/rails4/action_controller.rb +1 -1
  31. data/lib/new_relic/agent/instrumentation/rails4/action_view.rb +1 -1
  32. data/lib/new_relic/agent/instrumentation/rails5/action_cable.rb +1 -1
  33. data/lib/new_relic/agent/instrumentation/rails5/action_controller.rb +5 -5
  34. data/lib/new_relic/agent/instrumentation/rails5/action_view.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/rails_middleware.rb +1 -1
  36. data/lib/new_relic/agent/instrumentation/redis.rb +21 -6
  37. data/lib/new_relic/agent/instrumentation/typhoeus.rb +48 -24
  38. data/lib/new_relic/agent/javascript_instrumentor.rb +2 -2
  39. data/lib/new_relic/agent/messaging.rb +7 -5
  40. data/lib/new_relic/agent/method_tracer_helpers.rb +5 -1
  41. data/lib/new_relic/agent/range_extensions.rb +47 -0
  42. data/lib/new_relic/agent/transaction.rb +8 -2
  43. data/lib/new_relic/agent/transaction/abstract_segment.rb +134 -25
  44. data/lib/new_relic/agent/transaction/datastore_segment.rb +5 -2
  45. data/lib/new_relic/agent/transaction/external_request_segment.rb +3 -2
  46. data/lib/new_relic/agent/transaction/segment.rb +0 -3
  47. data/lib/new_relic/agent/transaction/tracing.rb +77 -20
  48. data/lib/new_relic/agent/transaction_error_primitive.rb +2 -0
  49. data/lib/new_relic/noticed_error.rb +9 -6
  50. data/lib/new_relic/version.rb +1 -1
  51. data/lib/sequel/extensions/newrelic_instrumentation.rb +4 -1
  52. data/lib/sequel/plugins/newrelic_instrumentation.rb +5 -1
  53. metadata +4 -2
@@ -17,7 +17,11 @@ module NewRelic
17
17
  first_name = metric_names.shift
18
18
  return yield unless first_name
19
19
 
20
- segment = NewRelic::Agent::Transaction.start_segment first_name, metric_names
20
+ segment = NewRelic::Agent::Transaction.start_segment(
21
+ name: first_name,
22
+ unscoped_metrics: metric_names
23
+ )
24
+
21
25
  if options[:metric] == false
22
26
  segment.record_metrics = false
23
27
  end
@@ -0,0 +1,47 @@
1
+ # encoding: utf-8
2
+ # This file is distributed under New Relic's license terms.
3
+ # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
+
5
+ module NewRelic
6
+ module Agent
7
+ module RangeExtensions
8
+ module_function
9
+
10
+ def intersects? r1, r2
11
+ r1.include?(r2.begin) || r2.include?(r1.begin)
12
+ end
13
+
14
+ def merge r1, r2
15
+ return unless intersects? r1, r2
16
+ range_min = r1.begin < r2.begin ? r1.begin : r2.begin
17
+ range_max = r1.end > r2.end ? r1.end : r2.end
18
+ range_min..range_max
19
+ end
20
+
21
+ # Takes an array of ranges and a range which it will
22
+ # merge into an existing range if they intersect, otherwise
23
+ # it will append this range to the end the array.
24
+ def merge_or_append range, ranges
25
+ ranges.each_with_index do |r, i|
26
+ if merged = merge(r, range)
27
+ ranges[i] = merged
28
+ return ranges
29
+ end
30
+ end
31
+ ranges.push range
32
+ end
33
+
34
+ # Computes the amount of overlap between range and an array of ranges.
35
+ # For efficiency, it assumes that range intersects with each of the
36
+ # ranges in the ranges array.
37
+ def compute_overlap range, ranges
38
+ ranges.inject(0) do |memo, other|
39
+ next memo unless intersects? range, other
40
+ lower_bound = range.begin > other.begin ? range.begin : other.begin
41
+ upper_bound = range.end < other.end ? range.end : other.end
42
+ memo += upper_bound - lower_bound
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -459,7 +459,12 @@ module NewRelic
459
459
  end
460
460
 
461
461
  @nesting_max_depth += 1
462
- segment = self.class.start_segment name, summary_metrics
462
+
463
+ segment = self.class.start_segment(
464
+ name: name,
465
+ unscoped_metrics: summary_metrics
466
+ )
467
+
463
468
  frame_stack.push segment
464
469
  segment
465
470
  end
@@ -546,12 +551,13 @@ module NewRelic
546
551
  assign_agent_attributes
547
552
  assign_intrinsics(state)
548
553
 
549
- segments.each { |s| s.finalize }
554
+ finalize_segments
550
555
 
551
556
  @transaction_trace = transaction_sampler.on_finishing_transaction(state, self, end_time)
552
557
  sql_sampler.on_finishing_transaction(state, @frozen_name)
553
558
 
554
559
  record_summary_metrics(outermost_node_name, end_time)
560
+ record_total_time_metrics
555
561
  record_apdex(state, end_time) unless ignore_apdex?
556
562
  record_queue_time
557
563
  record_client_application_metric state
@@ -2,40 +2,58 @@
2
2
  # This file is distributed under New Relic's license terms.
3
3
  # See https://github.com/newrelic/rpm/blob/master/LICENSE for complete details.
4
4
 
5
+ require 'new_relic/agent/range_extensions'
6
+
5
7
  module NewRelic
6
8
  module Agent
7
9
  class Transaction
8
10
  class AbstractSegment
11
+ # This class is the base class for all segments. It is reponsible for
12
+ # timing, naming, and defining lifecycle callbacks. One of the more
13
+ # complex responsibilites of this class is computing exclusive duration.
14
+ # One of the reasons for this complexity is that exclusive time will be
15
+ # computed using time ranges or by recording an aggregate value for
16
+ # a segments children time. The reason for this is that computing
17
+ # exclusive duration using time ranges is expensive and it's only
18
+ # necessary if a segment's children run concurrently, or a segment ends
19
+ # after it's parent. We will use the optimized exclusive duration
20
+ # calculation in all other cases.
21
+ #
9
22
  attr_reader :start_time, :end_time, :duration, :exclusive_duration
10
23
  attr_accessor :name, :parent, :children_time, :transaction
11
24
  attr_writer :record_metrics, :record_scoped_metric, :record_on_finish
12
25
 
13
26
  def initialize name=nil, start_time=nil
14
27
  @name = name
15
- @children_time = 0.0
16
- @record_metrics = true
17
- @record_scoped_metric = true
18
28
  @transaction = nil
19
29
  @parent = nil
20
- @record_on_finish = false
21
30
  @params = nil
22
31
  @start_time = start_time if start_time
32
+ @end_time = nil
33
+ @duration = 0.0
34
+ @exclusive_duration = 0.0
35
+ @children_time = 0.0
36
+ @children_time_ranges = nil
37
+ @active_children = 0
38
+ @range_recorded = false
39
+ @concurrent_children = false
40
+ @record_metrics = true
41
+ @record_scoped_metric = true
42
+ @record_on_finish = false
23
43
  end
24
44
 
25
45
  def start
26
46
  @start_time ||= Time.now
47
+ return unless transaction
48
+ parent.child_start self if parent
27
49
  end
28
50
 
29
51
  def finish
30
52
  @end_time = Time.now
31
53
  @duration = end_time.to_f - start_time.to_f
32
- @exclusive_duration = duration - children_time
33
- if transaction
34
- record_metrics if record_metrics? && record_on_finish?
35
- segment_complete
36
- parent.child_complete self if parent
37
- transaction.segment_complete self
38
- end
54
+ return unless transaction
55
+ run_complete_callbacks
56
+ finalize if record_on_finish?
39
57
  rescue => e
40
58
  NewRelic::Agent.logger.error "Exception finishing segment: #{name}", e
41
59
  end
@@ -57,13 +75,8 @@ module NewRelic
57
75
  end
58
76
 
59
77
  def finalize
60
- unless finished?
61
- finish
62
- NewRelic::Agent.logger.warn "Segment: #{name} was unfinished at " \
63
- "the end of transaction. Timing information for " \
64
- "#{transaction.best_name} may be inaccurate."
65
- # @todo: we should record a supportability metric here
66
- end
78
+ force_finish unless finished?
79
+ record_exclusive_duration
67
80
  record_metrics if record_metrics?
68
81
  end
69
82
 
@@ -75,6 +88,22 @@ module NewRelic
75
88
  !!@params
76
89
  end
77
90
 
91
+ def time_range
92
+ @start_time.to_f .. @end_time.to_f
93
+ end
94
+
95
+ def children_time_ranges
96
+ @children_time_ranges ||= []
97
+ end
98
+
99
+ def children_time_ranges?
100
+ !!@children_time_ranges
101
+ end
102
+
103
+ def concurrent_children?
104
+ @concurrent_children
105
+ end
106
+
78
107
  INSPECT_IGNORE = [:@transaction, :@transaction_state].freeze
79
108
 
80
109
  def inspect
@@ -86,23 +115,103 @@ module NewRelic
86
115
 
87
116
  protected
88
117
 
89
- def record_metrics
90
- raise NotImplementedError, "Subclasses must implement record_metrics"
118
+ attr_writer :range_recorded
119
+
120
+ def range_recorded?
121
+ @range_recorded
122
+ end
123
+
124
+ def child_start segment
125
+ @active_children += 1
126
+ @concurrent_children = @concurrent_children || @active_children > 1
127
+
128
+ transaction.async = true if @concurrent_children
91
129
  end
92
130
 
93
131
  def child_complete segment
94
- if segment.record_metrics?
95
- self.children_time += segment.duration
96
- else
97
- self.children_time += segment.children_time
132
+ @active_children -= 1
133
+ record_child_time segment
134
+
135
+ if finished?
136
+ transaction.async = true
137
+ parent.descendant_complete self, segment
138
+ end
139
+ end
140
+
141
+ # When a child segment completes after its parent, we need to propagate
142
+ # the information about the descendant further up the tree so that
143
+ # ancestors can properly account for exclusive time. Once we've reached
144
+ # an ancestor whose end time is greater than or equal to the descendant's
145
+ # we can stop the propagation. We pass along the direct child so we can
146
+ # make any corrections needed for exclusive time calculation.
147
+
148
+ def descendant_complete child, descendant
149
+ RangeExtensions.merge_or_append descendant.time_range,
150
+ children_time_ranges
151
+ # If this child's time was previously added to this segment's
152
+ # aggregate children time, we need to re-record it using a time range
153
+ # for proper exclusive time calculation
154
+ unless child.range_recorded?
155
+ self.children_time -= child.duration
156
+ record_child_time_as_range child
157
+ end
158
+
159
+ if parent && finished? && descendant.end_time >= end_time
160
+ parent.descendant_complete self, descendant
98
161
  end
99
162
  end
100
163
 
101
164
  private
102
165
 
166
+ def force_finish
167
+ finish
168
+ NewRelic::Agent.logger.warn "Segment: #{name} was unfinished at " \
169
+ "the end of transaction. Timing information for this segment's" \
170
+ "parent #{parent.name} in #{transaction.best_name} may be inaccurate."
171
+ end
172
+
173
+ def run_complete_callbacks
174
+ segment_complete
175
+ parent.child_complete self if parent
176
+ transaction.segment_complete self
177
+ end
178
+
179
+ def record_metrics
180
+ raise NotImplementedError, "Subclasses must implement record_metrics"
181
+ end
182
+
103
183
  # callback for subclasses to override
104
184
  def segment_complete
105
- raise NotImplementedError
185
+ end
186
+
187
+ def record_child_time child
188
+ if concurrent_children? || finished? && end_time < child.end_time
189
+ record_child_time_as_range child
190
+ else
191
+ record_child_time_as_number child
192
+ end
193
+ end
194
+
195
+ def record_child_time_as_range child
196
+ RangeExtensions.merge_or_append child.time_range,
197
+ children_time_ranges
198
+ child.range_recorded = true
199
+ end
200
+
201
+ def record_child_time_as_number child
202
+ self.children_time += child.duration
203
+ end
204
+
205
+ def record_exclusive_duration
206
+ overlapping_duration = if children_time_ranges?
207
+ RangeExtensions.compute_overlap time_range, children_time_ranges
208
+ else
209
+ 0.0
210
+ end
211
+
212
+ @exclusive_duration = duration - children_time - overlapping_duration
213
+ transaction.total_time += @exclusive_duration
214
+ params[:exclusive_duration_millis] = @exclusive_duration * 1000 if transaction.async?
106
215
  end
107
216
 
108
217
  def metric_cache
@@ -17,7 +17,8 @@ module NewRelic
17
17
  attr_reader :product, :operation, :collection, :sql_statement, :nosql_statement, :host, :port_path_or_id
18
18
  attr_accessor :database_name
19
19
 
20
- def initialize product, operation, collection = nil, host = nil, port_path_or_id = nil, database_name = nil
20
+
21
+ def initialize product, operation, collection = nil, host = nil, port_path_or_id = nil, database_name = nil, start_time = nil
21
22
  @product = product
22
23
  @operation = operation
23
24
  @collection = collection
@@ -25,7 +26,9 @@ module NewRelic
25
26
  @nosql_statement = nil
26
27
  set_instance_info host, port_path_or_id
27
28
  @database_name = database_name ? database_name.to_s : nil
28
- super Datastores::MetricHelper.scoped_metric_for(product, operation, collection)
29
+ super Datastores::MetricHelper.scoped_metric_for(product, operation, collection),
30
+ nil,
31
+ start_time
29
32
  end
30
33
 
31
34
  def set_instance_info host = nil, port_path_or_id = nil
@@ -18,13 +18,14 @@ module NewRelic
18
18
 
19
19
  NR_SYNTHETICS_HEADER = 'X-NewRelic-Synthetics'.freeze
20
20
 
21
- def initialize library, uri, procedure # :nodoc:
21
+
22
+ def initialize library, uri, procedure, start_time = nil # :nodoc:
22
23
  @library = library
23
24
  @uri = HTTPClients::URIUtil.parse_and_normalize_url(uri)
24
25
  @procedure = procedure
25
26
  @host_header = nil
26
27
  @app_data = nil
27
- super()
28
+ super(nil, nil, start_time)
28
29
  end
29
30
 
30
31
  def name # :nodoc:
@@ -46,9 +46,6 @@ module NewRelic
46
46
  @unscoped_metrics = metric
47
47
  end
48
48
  end
49
-
50
- def segment_complete
51
- end
52
49
  end
53
50
  end
54
51
  end
@@ -12,24 +12,51 @@ module NewRelic
12
12
  class Transaction
13
13
  module Tracing
14
14
  module ClassMethods
15
- def start_segment name, unscoped_metrics=nil
16
- segment = Segment.new name, unscoped_metrics
17
- start_and_add_segment segment
15
+ def start_segment(name:nil,
16
+ unscoped_metrics:nil,
17
+ start_time: nil,
18
+ parent: nil)
19
+
20
+ # ruby 2.0.0 does not support required kwargs
21
+ raise ArgumentError, 'missing required argument: name' if name.nil?
22
+
23
+ segment = Segment.new name, unscoped_metrics, start_time
24
+
25
+ start_and_add_segment segment, parent
18
26
  end
19
27
 
20
28
  UNKNOWN_PRODUCT = "Unknown".freeze
21
29
  UNKNOWN_OPERATION = "other".freeze
22
30
 
23
- def start_datastore_segment product=nil, operation=nil, collection=nil, host=nil, port_path_or_id=nil, database_name=nil
31
+ def start_datastore_segment(product: nil,
32
+ operation: nil,
33
+ collection: nil,
34
+ host: nil,
35
+ port_path_or_id: nil,
36
+ database_name: nil,
37
+ start_time: nil,
38
+ parent: nil)
39
+
24
40
  product ||= UNKNOWN_PRODUCT
25
41
  operation ||= UNKNOWN_OPERATION
42
+
26
43
  segment = DatastoreSegment.new product, operation, collection, host, port_path_or_id, database_name
27
- start_and_add_segment segment
44
+ start_and_add_segment segment, parent
28
45
  end
29
46
 
30
- def start_external_request_segment library, uri, procedure
31
- segment = ExternalRequestSegment.new library, uri, procedure
32
- start_and_add_segment segment
47
+ def start_external_request_segment(library: nil,
48
+ uri: nil,
49
+ procedure: nil,
50
+ start_time: nil,
51
+ parent: nil)
52
+
53
+ # ruby 2.0.0 does not support required kwargs
54
+ raise ArgumentError, 'missing required argument: library' if library.nil?
55
+ raise ArgumentError, 'missing required argument: uri' if uri.nil?
56
+ raise ArgumentError, 'missing required argument: procedure' if procedure.nil?
57
+
58
+ segment = ExternalRequestSegment.new library, uri, procedure, start_time
59
+ start_and_add_segment segment, parent
33
60
  end
34
61
 
35
62
  # @api private
@@ -40,7 +67,8 @@ module NewRelic
40
67
  destination_name: nil,
41
68
  headers: nil,
42
69
  parameters: nil,
43
- start_time: nil)
70
+ start_time: nil,
71
+ parent: nil)
44
72
 
45
73
  # ruby 2.0.0 does not support required kwargs
46
74
  raise ArgumentError, 'missing required argument: action' if action.nil?
@@ -57,24 +85,20 @@ module NewRelic
57
85
  parameters: parameters,
58
86
  start_time: start_time
59
87
  )
60
- start_and_add_segment segment
88
+ start_and_add_segment segment, parent
61
89
  end
62
90
 
63
91
  private
64
92
 
65
- def start_and_add_segment segment
66
- segment.start
67
- add_segment segment
68
- segment
69
- end
70
-
71
- def add_segment segment
93
+ def start_and_add_segment segment, parent = nil
72
94
  state = NewRelic::Agent::TransactionState.tl_get
73
95
  if (txn = state.current_transaction) && state.is_execution_traced?
74
- txn.add_segment segment
96
+ txn.add_segment segment, parent
75
97
  else
76
98
  segment.record_metrics = false
77
99
  end
100
+ segment.start
101
+ segment
78
102
  end
79
103
  end
80
104
 
@@ -84,9 +108,21 @@ module NewRelic
84
108
 
85
109
  attr_reader :current_segment
86
110
 
87
- def add_segment segment
111
+ def async?
112
+ @async ||= false
113
+ end
114
+
115
+ attr_writer :async
116
+
117
+ def total_time
118
+ @total_time ||= 0.0
119
+ end
120
+
121
+ attr_writer :total_time
122
+
123
+ def add_segment segment, parent = nil
88
124
  segment.transaction = self
89
- segment.parent = current_segment
125
+ segment.parent = parent || current_segment
90
126
  @current_segment = segment
91
127
  if @segments.length < segment_limit
92
128
  @segments << segment
@@ -103,6 +139,27 @@ module NewRelic
103
139
  def segment_limit
104
140
  Agent.config[:'transaction_tracer.limit_segments']
105
141
  end
142
+
143
+ private
144
+
145
+ def finalize_segments
146
+ segments.each { |s| s.finalize }
147
+ end
148
+
149
+
150
+ WEB_TRANSACTION_TOTAL_TIME = "WebTransactionTotalTime".freeze
151
+ OTHER_TRANSACTION_TOTAL_TIME = "OtherTransactionTotalTime".freeze
152
+
153
+ def record_total_time_metrics
154
+ total_time_metric = if recording_web_transaction?
155
+ WEB_TRANSACTION_TOTAL_TIME
156
+ else
157
+ OTHER_TRANSACTION_TOTAL_TIME
158
+ end
159
+
160
+ @metrics.record_unscoped total_time_metric, total_time
161
+ @metrics.record_unscoped "#{total_time_metric}/#{@frozen_name}", total_time
162
+ end
106
163
  end
107
164
  end
108
165
  end