newrelic_rpm 5.5.0.348 → 6.2.0.354

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +1 -0
  3. data/.travis.yml +36 -84
  4. data/.yardopts +1 -0
  5. data/CHANGELOG.md +149 -0
  6. data/README.md +1 -1
  7. data/config.dot +1 -0
  8. data/lib/new_relic/agent/agent.rb +15 -11
  9. data/lib/new_relic/agent/attribute_filter.rb +77 -17
  10. data/lib/new_relic/agent/configuration/default_source.rb +71 -1
  11. data/lib/new_relic/agent/configuration/security_policy_source.rb +14 -0
  12. data/lib/new_relic/agent/configuration/server_source.rb +2 -1
  13. data/lib/new_relic/agent/cross_app_monitor.rb +3 -3
  14. data/lib/new_relic/agent/datastores/metric_helper.rb +1 -2
  15. data/lib/new_relic/agent/datastores.rb +6 -8
  16. data/lib/new_relic/agent/distributed_trace_monitor.rb +1 -2
  17. data/lib/new_relic/agent/distributed_trace_payload.rb +7 -11
  18. data/lib/new_relic/agent/error_collector.rb +4 -6
  19. data/lib/new_relic/agent/external.rb +6 -4
  20. data/lib/new_relic/agent/hostname.rb +8 -0
  21. data/lib/new_relic/agent/instrumentation/action_cable_subscriber.rb +8 -5
  22. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +12 -8
  23. data/lib/new_relic/agent/instrumentation/action_view_subscriber.rb +1 -1
  24. data/lib/new_relic/agent/instrumentation/active_job.rb +6 -7
  25. data/lib/new_relic/agent/instrumentation/active_record.rb +2 -2
  26. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +3 -3
  27. data/lib/new_relic/agent/instrumentation/active_storage.rb +23 -0
  28. data/lib/new_relic/agent/instrumentation/active_storage_subscriber.rb +59 -0
  29. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +1 -1
  30. data/lib/new_relic/agent/instrumentation/bunny.rb +16 -12
  31. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +9 -3
  32. data/lib/new_relic/agent/instrumentation/curb.rb +18 -5
  33. data/lib/new_relic/agent/instrumentation/data_mapper.rb +1 -1
  34. data/lib/new_relic/agent/instrumentation/evented_subscriber.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/excon/connection.rb +1 -1
  36. data/lib/new_relic/agent/instrumentation/grape.rb +17 -4
  37. data/lib/new_relic/agent/instrumentation/middleware_proxy.rb +1 -1
  38. data/lib/new_relic/agent/instrumentation/middleware_tracing.rb +11 -4
  39. data/lib/new_relic/agent/instrumentation/net.rb +1 -1
  40. data/lib/new_relic/agent/instrumentation/rake.rb +2 -3
  41. data/lib/new_relic/agent/instrumentation/sequel.rb +1 -1
  42. data/lib/new_relic/agent/instrumentation/typhoeus.rb +3 -3
  43. data/lib/new_relic/agent/javascript_instrumentor.rb +1 -1
  44. data/lib/new_relic/agent/messaging.rb +9 -8
  45. data/lib/new_relic/agent/method_tracer_helpers.rb +2 -2
  46. data/lib/new_relic/agent/new_relic_service/json_marshaller.rb +0 -1
  47. data/lib/new_relic/agent/new_relic_service/marshaller.rb +5 -26
  48. data/lib/new_relic/agent/new_relic_service.rb +69 -25
  49. data/lib/new_relic/agent/span_event_primitive.rb +26 -16
  50. data/lib/new_relic/agent/sql_sampler.rb +3 -3
  51. data/lib/new_relic/agent/stats_engine.rb +2 -2
  52. data/lib/new_relic/agent/synthetics_monitor.rb +1 -2
  53. data/lib/new_relic/agent/system_info.rb +5 -0
  54. data/lib/new_relic/agent/threading/agent_thread.rb +1 -1
  55. data/lib/new_relic/agent/tracer.rb +462 -0
  56. data/lib/new_relic/agent/transaction/abstract_segment.rb +1 -1
  57. data/lib/new_relic/agent/transaction/datastore_segment.rb +0 -2
  58. data/lib/new_relic/agent/transaction/distributed_tracing.rb +1 -2
  59. data/lib/new_relic/agent/transaction/trace_node.rb +4 -2
  60. data/lib/new_relic/agent/transaction/tracing.rb +0 -99
  61. data/lib/new_relic/agent/transaction.rb +43 -88
  62. data/lib/new_relic/agent/transaction_time_aggregator.rb +49 -25
  63. data/lib/new_relic/agent/utilization_data.rb +36 -1
  64. data/lib/new_relic/agent.rb +8 -4
  65. data/lib/new_relic/build.rb +2 -2
  66. data/lib/new_relic/control/frameworks/rails6.rb +14 -0
  67. data/lib/new_relic/latest_changes.rb +2 -2
  68. data/lib/new_relic/rack/agent_middleware.rb +1 -1
  69. data/lib/new_relic/version.rb +2 -2
  70. data/newrelic_rpm.gemspec +2 -9
  71. data/test/agent_helper.rb +2 -2
  72. metadata +13 -39
  73. data/lib/new_relic/agent/transaction_state.rb +0 -186
@@ -228,7 +228,7 @@ module NewRelic
228
228
  @transaction_state ||= if @transaction
229
229
  transaction.state
230
230
  else
231
- TransactionState.tl_get
231
+ Tracer.state
232
232
  end
233
233
  end
234
234
  end
@@ -109,13 +109,11 @@ module NewRelic
109
109
  end
110
110
 
111
111
  def add_instance_parameters
112
- return unless NewRelic::Agent.config[:'datastore_tracer.instance_reporting.enabled']
113
112
  params[:host] = host if host
114
113
  params[:port_path_or_id] = port_path_or_id if port_path_or_id
115
114
  end
116
115
 
117
116
  def add_database_name_parameter
118
- return unless NewRelic::Agent.config[:'datastore_tracer.database_name_reporting.enabled']
119
117
  params[:database_name] = database_name if database_name
120
118
  end
121
119
 
@@ -156,8 +156,7 @@ module NewRelic
156
156
  !payload.parent_type.nil? &&
157
157
  (!payload.transaction_id.nil? || !payload.id.nil?) &&
158
158
  !payload.trace_id.nil? &&
159
- !payload.timestamp.nil? &&
160
- !payload.parent_account_id.nil?
159
+ !payload.timestamp.nil?
161
160
 
162
161
  true
163
162
  else
@@ -21,8 +21,10 @@ module NewRelic
21
21
  @entry_timestamp = relative_start
22
22
  @metric_name = metric_name || UNKNOWN_NODE_NAME
23
23
  @exit_timestamp = relative_end
24
- @children = nil
25
- @params = params
24
+ @children = nil
25
+ @params = params.select do |p|
26
+ NewRelic::Agent.instance.attribute_filter.allows_key? p, AttributeFilter::DST_TRANSACTION_SEGMENTS
27
+ end if params
26
28
  @parent_node = parent
27
29
  end
28
30
 
@@ -2,109 +2,10 @@
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/transaction/segment'
6
- require 'new_relic/agent/transaction/datastore_segment'
7
- require 'new_relic/agent/transaction/external_request_segment'
8
- require 'new_relic/agent/transaction/message_broker_segment'
9
-
10
5
  module NewRelic
11
6
  module Agent
12
7
  class Transaction
13
8
  module Tracing
14
- module ClassMethods
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
26
- end
27
-
28
- UNKNOWN_PRODUCT = "Unknown".freeze
29
- UNKNOWN_OPERATION = "other".freeze
30
-
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
-
40
- product ||= UNKNOWN_PRODUCT
41
- operation ||= UNKNOWN_OPERATION
42
-
43
- segment = DatastoreSegment.new product, operation, collection, host, port_path_or_id, database_name
44
- start_and_add_segment segment, parent
45
- end
46
-
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
60
- end
61
-
62
- # @api private
63
- #
64
- def start_message_broker_segment(action: nil,
65
- library: nil,
66
- destination_type: nil,
67
- destination_name: nil,
68
- headers: nil,
69
- parameters: nil,
70
- start_time: nil,
71
- parent: nil)
72
-
73
- # ruby 2.0.0 does not support required kwargs
74
- raise ArgumentError, 'missing required argument: action' if action.nil?
75
- raise ArgumentError, 'missing required argument: library' if library.nil?
76
- raise ArgumentError, 'missing required argument: destination_type' if destination_type.nil?
77
- raise ArgumentError, 'missing required argument: destination_name' if destination_name.nil?
78
-
79
- segment = MessageBrokerSegment.new(
80
- action: action,
81
- library: library,
82
- destination_type: destination_type,
83
- destination_name: destination_name,
84
- headers: headers,
85
- parameters: parameters,
86
- start_time: start_time
87
- )
88
- start_and_add_segment segment, parent
89
- end
90
-
91
- private
92
-
93
- def start_and_add_segment segment, parent = nil
94
- state = NewRelic::Agent::TransactionState.tl_get
95
- if (txn = state.current_transaction) && state.is_execution_traced?
96
- txn.add_segment segment, parent
97
- else
98
- segment.record_metrics = false
99
- end
100
- segment.start
101
- segment
102
- end
103
- end
104
-
105
- def self.included base
106
- base.extend ClassMethods
107
- end
108
9
 
109
10
  attr_reader :current_segment
110
11
 
@@ -11,6 +11,7 @@ require 'new_relic/agent/transaction/tracing'
11
11
  require 'new_relic/agent/transaction/distributed_tracing'
12
12
  require 'new_relic/agent/cross_app_tracing'
13
13
  require 'new_relic/agent/transaction_time_aggregator'
14
+ require 'new_relic/agent/deprecator'
14
15
 
15
16
  module NewRelic
16
17
  module Agent
@@ -36,7 +37,7 @@ module NewRelic
36
37
  ACTION_CABLE_PREFIX = 'Controller/ActionCable/'.freeze
37
38
  OTHER_TRANSACTION_PREFIX = 'OtherTransaction/'.freeze
38
39
 
39
- WEB_TRANSACTION_CATEGORIES = [:controller, :uri, :rack, :sinatra, :grape, :middleware, :action_cable].freeze
40
+ WEB_TRANSACTION_CATEGORIES = [:web, :controller, :uri, :rack, :sinatra, :grape, :middleware, :action_cable].freeze
40
41
  TRANSACTION_NAMING_SOURCES = [:child, :api].freeze
41
42
 
42
43
  MIDDLEWARE_SUMMARY_METRICS = ['Middleware/all'.freeze].freeze
@@ -64,7 +65,6 @@ module NewRelic
64
65
  :metrics,
65
66
  :gc_start_snapshot,
66
67
  :category,
67
- :frame_stack,
68
68
  :attributes,
69
69
  :payload,
70
70
  :nesting_max_depth,
@@ -83,28 +83,37 @@ module NewRelic
83
83
 
84
84
  # Return the currently active transaction, or nil.
85
85
  def self.tl_current
86
- TransactionState.tl_get.current_transaction
86
+ Tracer.current_transaction
87
87
  end
88
88
 
89
- def self.set_default_transaction_name(name, category = nil, node_name = nil) #THREAD_LOCAL_ACCESS
89
+ def self.set_default_transaction_name(partial_name, category = nil) #THREAD_LOCAL_ACCESS
90
90
  txn = tl_current
91
- name = txn.make_transaction_name(name, category)
92
- txn.name_last_frame(node_name || name)
91
+ name = name_from_partial(partial_name, category || txn.category)
93
92
  txn.set_default_transaction_name(name, category)
94
93
  end
95
94
 
96
- def self.set_overriding_transaction_name(name, category = nil) #THREAD_LOCAL_ACCESS
95
+ def self.set_overriding_transaction_name(partial_name, category = nil) #THREAD_LOCAL_ACCESS
97
96
  txn = tl_current
98
97
  return unless txn
99
98
 
100
- name = txn.make_transaction_name(name, category)
101
-
102
- txn.name_last_frame(name)
99
+ name = name_from_partial(partial_name, category || txn.category)
103
100
  txn.set_overriding_transaction_name(name, category)
104
101
  end
105
102
 
103
+ def self.name_from_partial(partial_name, category)
104
+ namer = Instrumentation::ControllerInstrumentation::TransactionNamer
105
+ "#{namer.prefix_for_category(self, category)}#{partial_name}"
106
+ end
107
+
106
108
  def self.wrap(state, name, category, options = {})
107
- Transaction.start(state, category, options.merge(:transaction_name => name))
109
+ Deprecator.deprecate 'Transaction.wrap',
110
+ 'Tracer#in_transaction'
111
+
112
+ finishable = Tracer.start_transaction_or_segment(
113
+ name: name,
114
+ category: category,
115
+ options: options
116
+ )
108
117
 
109
118
  begin
110
119
  # We shouldn't raise from Transaction.start, but only wrap the yield
@@ -114,24 +123,8 @@ module NewRelic
114
123
  Transaction.notice_error(e)
115
124
  raise e
116
125
  ensure
117
- Transaction.stop(state)
118
- end
119
- end
120
-
121
- def self.start(state, category, options)
122
- category ||= :controller
123
- txn = state.current_transaction
124
-
125
- if txn
126
- txn.create_nested_frame(category, options)
127
- else
128
- txn = start_new_transaction(state, category, options)
126
+ finishable.finish if finishable
129
127
  end
130
-
131
- txn
132
- rescue => e
133
- NewRelic::Agent.logger.error("Exception during Transaction.start", e)
134
- nil
135
128
  end
136
129
 
137
130
  def self.start_new_transaction(state, category, options)
@@ -142,32 +135,6 @@ module NewRelic
142
135
  txn
143
136
  end
144
137
 
145
- FAILED_TO_STOP_MESSAGE = "Failed during Transaction.stop because there is no current transaction"
146
-
147
- def self.stop(state)
148
- txn = state.current_transaction
149
-
150
- if txn.nil?
151
- NewRelic::Agent.logger.error(FAILED_TO_STOP_MESSAGE)
152
- return
153
- end
154
-
155
- nested_frame = txn.frame_stack.pop
156
-
157
- if txn.frame_stack.empty?
158
- txn.stop(nested_frame) if nested_frame
159
- state.reset
160
- else
161
- nested_frame.finish
162
- end
163
-
164
- :transaction_stopped
165
- rescue => e
166
- state.reset
167
- NewRelic::Agent.logger.error("Exception during Transaction.stop", e)
168
- nil
169
- end
170
-
171
138
  def self.nested_transaction_name(name)
172
139
  if name.start_with?(CONTROLLER_PREFIX) || name.start_with?(OTHER_TRANSACTION_PREFIX)
173
140
  "#{SUBTRANSACTION_PREFIX}#{name}"
@@ -179,15 +146,13 @@ module NewRelic
179
146
  # Indicate that you don't want to keep the currently saved transaction
180
147
  # information
181
148
  def self.abort_transaction! #THREAD_LOCAL_ACCESS
182
- state = NewRelic::Agent::TransactionState.tl_get
183
- txn = state.current_transaction
149
+ txn = Tracer.current_transaction
184
150
  txn.abort_transaction! if txn
185
151
  end
186
152
 
187
153
  # See NewRelic::Agent.notice_error for options and commentary
188
154
  def self.notice_error(e, options={}) #THREAD_LOCAL_ACCESS
189
- state = NewRelic::Agent::TransactionState.tl_get
190
- txn = state.current_transaction
155
+ txn = Tracer.current_transaction
191
156
  if txn
192
157
  txn.notice_error(e, options)
193
158
  elsif NewRelic::Agent.instance
@@ -260,7 +225,6 @@ module NewRelic
260
225
  @nesting_max_depth = 0
261
226
  @current_segment = nil
262
227
  @segments = []
263
- @frame_stack = []
264
228
 
265
229
  self.default_name = options[:transaction_name]
266
230
  @overridden_name = nil
@@ -288,6 +252,8 @@ module NewRelic
288
252
  @sampled = nil
289
253
  @priority = nil
290
254
 
255
+ @starting_thread_id = Thread.current.object_id
256
+
291
257
  @attributes = Attributes.new(NewRelic::Agent.instance.attribute_filter)
292
258
 
293
259
  merge_request_parameters(@filtered_params)
@@ -357,11 +323,6 @@ module NewRelic
357
323
  merge_untrusted_agent_attributes(params, :'request.parameters', AttributeFilter::DST_NONE)
358
324
  end
359
325
 
360
- def make_transaction_name(name, category=nil)
361
- namer = Instrumentation::ControllerInstrumentation::TransactionNamer
362
- "#{namer.prefix_for_category(self, category)}#{name}"
363
- end
364
-
365
326
  def set_default_transaction_name(name, category)
366
327
  return log_frozen_name(name) if name_frozen?
367
328
  if influences_transaction_name?(category)
@@ -378,18 +339,13 @@ module NewRelic
378
339
  end
379
340
  end
380
341
 
381
- def name_last_frame(name)
382
- name = self.class.nested_transaction_name(name) if nesting_max_depth > 1
383
- frame_stack.last.name = name
384
- end
385
-
386
342
  def log_frozen_name(name)
387
343
  NewRelic::Agent.logger.warn("Attempted to rename transaction to '#{name}' after transaction name was already frozen as '#{@frozen_name}'.")
388
344
  nil
389
345
  end
390
346
 
391
347
  def influences_transaction_name?(category)
392
- !category || frame_stack.size == 1 || similar_category?(category)
348
+ !category || nesting_max_depth == 1 || similar_category?(category)
393
349
  end
394
350
 
395
351
  def best_name
@@ -403,10 +359,6 @@ module NewRelic
403
359
  attr_accessor :xray_session_id
404
360
  # End common interface
405
361
 
406
- def name_set?
407
- (@overridden_name || @default_name) ? true : false
408
- end
409
-
410
362
  def promoted_transaction_name(name)
411
363
  if name.start_with?(MIDDLEWARE_PREFIX)
412
364
  "#{CONTROLLER_PREFIX}#{name}"
@@ -447,14 +399,14 @@ module NewRelic
447
399
 
448
400
  ignore! if user_defined_rules_ignore?
449
401
 
450
- create_initial_segment @default_name
402
+ create_initial_segment
451
403
  end
452
404
 
453
405
  def initial_segment
454
406
  segments.first
455
407
  end
456
408
 
457
- def create_initial_segment name
409
+ def create_initial_segment
458
410
  segment = create_segment @default_name
459
411
  segment.record_scoped_metric = false
460
412
  end
@@ -468,16 +420,15 @@ module NewRelic
468
420
 
469
421
  @nesting_max_depth += 1
470
422
 
471
- segment = self.class.start_segment(
423
+ segment = Tracer.start_segment(
472
424
  name: name,
473
425
  unscoped_metrics: summary_metrics
474
426
  )
475
427
 
476
- frame_stack.push segment
477
428
  segment
478
429
  end
479
430
 
480
- def create_nested_frame(category, options)
431
+ def create_nested_segment(category, options)
481
432
  if options[:filtered_params] && !options[:filtered_params].empty?
482
433
  @filtered_params = options[:filtered_params]
483
434
  merge_request_parameters(options[:filtered_params])
@@ -488,8 +439,9 @@ module NewRelic
488
439
 
489
440
  nest_initial_segment if nesting_max_depth == 1
490
441
  nested_name = self.class.nested_transaction_name options[:transaction_name]
491
- create_segment nested_name
442
+ result = create_segment nested_name
492
443
  set_default_transaction_name(options[:transaction_name], category)
444
+ result
493
445
  end
494
446
 
495
447
  def nest_initial_segment
@@ -527,23 +479,26 @@ module NewRelic
527
479
  name.start_with?(MIDDLEWARE_PREFIX)
528
480
  end
529
481
 
530
- def stop(outermost_frame = nil)
531
- return if !state.is_execution_traced?
532
- return self.class.stop(state) unless outermost_frame
533
-
482
+ def finish
483
+ return unless state.is_execution_traced?
534
484
  @end_time = Time.now
535
485
  @duration = @end_time.to_f - @start_time.to_f
536
486
  freeze_name_and_execute_if_not_ignored
537
487
 
538
488
  if nesting_max_depth == 1
539
- outermost_frame.name = @frozen_name
489
+ initial_segment.name = @frozen_name
540
490
  end
541
491
 
542
- outermost_frame.finish
492
+ initial_segment.finish
543
493
 
544
- NewRelic::Agent::TransactionTimeAggregator.transaction_stop(@end_time)
494
+ NewRelic::Agent::TransactionTimeAggregator.transaction_stop(@end_time, @starting_thread_id)
545
495
 
546
- commit!(outermost_frame.name) unless @ignore_this_transaction
496
+ commit!(initial_segment.name) unless @ignore_this_transaction
497
+ rescue => e
498
+ NewRelic::Agent.logger.error("Exception during Transaction#finish", e)
499
+ nil
500
+ ensure
501
+ state.reset
547
502
  end
548
503
 
549
504
  def user_defined_rules_ignore?
@@ -2,8 +2,6 @@
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 'objspace'
6
-
7
5
  # This module powers the Busy calculation for the Capacity report in
8
6
  # APM (https://rpm.newrelic.com/accounts/.../applications/.../optimize/capacity_analysis).
9
7
  #
@@ -23,37 +21,37 @@ module NewRelic
23
21
  h[k] = TransactionStats.new nil, 0.0
24
22
  end
25
23
 
26
- def reset!(now = Time.now)
27
- @harvest_cycle_started_at = now
24
+ def reset!(timestamp = Time.now)
25
+ @harvest_cycle_started_at = timestamp
28
26
  @stats.clear
29
27
  end
30
28
 
31
- def transaction_start(now = Time.now)
29
+ def transaction_start(timestamp = Time.now)
32
30
  @lock.synchronize do
33
- set_transaction_start_time now
31
+ set_transaction_start_time timestamp
34
32
  end
35
33
  end
36
34
 
37
- def transaction_stop(now = Time.now)
35
+ def transaction_stop(timestamp = Time.now, starting_thread_id = current_thread)
38
36
  @lock.synchronize do
39
- record_elapsed_transaction_time_until now
40
- set_transaction_start_time nil
37
+ record_elapsed_transaction_time_until timestamp, starting_thread_id
38
+ set_transaction_start_time nil, starting_thread_id
41
39
  end
42
40
  end
43
41
 
44
42
  INSTANCE_BUSY_METRIC = 'Instance/Busy'.freeze
45
43
 
46
- def harvest!(now = Time.now)
44
+ def harvest!(timestamp = Time.now)
47
45
  active_threads = 0
48
46
  result = @lock.synchronize do
49
47
  # Sum up the transaction times spent in each thread
50
48
  elapsed_transaction_time = @stats.inject(0.0) do |total, (thread_id, entry)|
51
- total + transaction_time_in_thread(thread_id, entry, now)
49
+ total + transaction_time_in_thread(timestamp, thread_id, entry)
52
50
  end
53
51
 
54
52
  active_threads = @stats.size
55
- elapsed_harvest_time = (now - @harvest_cycle_started_at) * active_threads
56
- @harvest_cycle_started_at = now
53
+ elapsed_harvest_time = (timestamp - @harvest_cycle_started_at) * active_threads
54
+ @harvest_cycle_started_at = timestamp
57
55
 
58
56
  # Clear out the stats for all threads, _except_ the live ones
59
57
  # that have transactions still open (we'll count the rest of
@@ -81,12 +79,16 @@ module NewRelic
81
79
  :transaction_stop,
82
80
  :harvest!
83
81
 
84
- class <<self
82
+ class << self
85
83
  private
86
84
 
87
- def record_elapsed_transaction_time_until(timestamp, thread_id: current_thread)
88
- @stats[thread_id].elapsed_transaction_time +=
89
- (timestamp - (@stats[thread_id].transaction_started_at || 0.0))
85
+ def record_elapsed_transaction_time_until(timestamp, thread_id = current_thread)
86
+ if @stats[thread_id].transaction_started_at
87
+ @stats[thread_id].elapsed_transaction_time +=
88
+ (timestamp - (@stats[thread_id].transaction_started_at || 0.0))
89
+ else
90
+ log_missing_elapsed_transaction_time
91
+ end
90
92
  end
91
93
 
92
94
  def in_transaction?(thread_id = current_thread)
@@ -98,33 +100,55 @@ module NewRelic
98
100
  end
99
101
 
100
102
  def thread_is_alive?(thread_id)
101
- thread = ObjectSpace._id2ref(thread_id)
103
+ thread = thread_by_id thread_id
102
104
  thread && thread.alive?
103
105
  rescue StandardError
104
106
  false
105
107
  end
106
108
 
107
- def set_transaction_start_time(timestamp)
108
- @stats[current_thread].transaction_started_at = timestamp
109
+ # ObjectSpace is faster on MRI, but disabled by default on JRuby for
110
+ # perfomance reasons. We have two implmentations of `thread_by_id`
111
+ # based on ruby implementation.
112
+ if RUBY_ENGINE == 'jruby'
113
+ def thread_by_id thread_id
114
+ Thread.list.detect { |t| t.object_id == thread_id }
115
+ end
116
+ else
117
+ require 'objspace'
118
+
119
+ def thread_by_id thread_id
120
+ ObjectSpace._id2ref(thread_id)
121
+ end
122
+ end
123
+
124
+ def set_transaction_start_time(timestamp, thread_id = current_thread)
125
+ @stats[thread_id].transaction_started_at = timestamp
109
126
  end
110
127
 
111
- def split_transaction_at_harvest(now, thread_id: nil)
128
+ def split_transaction_at_harvest(timestamp, thread_id = nil)
112
129
  raise ArgumentError, 'thread_id required' unless thread_id
113
- @stats[thread_id].transaction_started_at = now
130
+ @stats[thread_id].transaction_started_at = timestamp
114
131
  @stats[thread_id].elapsed_transaction_time = 0.0
115
132
  end
116
133
 
117
- def transaction_time_in_thread thread_id, entry, now
134
+ def transaction_time_in_thread timestamp, thread_id, entry
118
135
  return entry.elapsed_transaction_time unless in_transaction? thread_id
119
136
 
120
137
  # Count the portion of the transaction that's elapsed so far,...
121
- elapsed = record_elapsed_transaction_time_until now, thread_id: thread_id
138
+ elapsed = record_elapsed_transaction_time_until timestamp, thread_id
122
139
 
123
140
  # ...then readjust the transaction start time to the next harvest
124
- split_transaction_at_harvest now, thread_id: thread_id
141
+ split_transaction_at_harvest timestamp, thread_id
125
142
 
126
143
  elapsed
127
144
  end
145
+
146
+ def log_missing_elapsed_transaction_time
147
+ transaction_name = Tracer.current_transaction &&
148
+ Tracer.current_transaction.best_name ||
149
+ "unknown"
150
+ NewRelic::Agent.logger.warn("Unable to calculate elapsed transaction time for #{transaction_name}")
151
+ end
128
152
  end
129
153
  end
130
154
  end