newrelic_rpm 8.5.0 → 8.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +6 -3
  3. data/.yardopts +1 -0
  4. data/CHANGELOG.md +127 -1
  5. data/CONTRIBUTING.md +1 -1
  6. data/LICENSE +0 -6
  7. data/README.md +17 -19
  8. data/Rakefile +26 -21
  9. data/THIRD_PARTY_NOTICES.md +14 -199
  10. data/docker-compose.yml +1 -1
  11. data/lib/new_relic/agent/agent.rb +26 -2
  12. data/lib/new_relic/agent/agent_logger.rb +7 -0
  13. data/lib/new_relic/agent/audit_logger.rb +4 -0
  14. data/lib/new_relic/agent/autostart.rb +13 -10
  15. data/lib/new_relic/agent/configuration/default_source.rb +217 -50
  16. data/lib/new_relic/agent/configuration/environment_source.rb +2 -0
  17. data/lib/new_relic/agent/configuration/event_harvest_config.rb +4 -2
  18. data/lib/new_relic/agent/configuration/server_source.rb +1 -0
  19. data/lib/new_relic/agent/database.rb +5 -5
  20. data/lib/new_relic/agent/datastores/metric_helper.rb +3 -1
  21. data/lib/new_relic/agent/hostname.rb +16 -10
  22. data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +23 -20
  23. data/lib/new_relic/agent/instrumentation/active_job.rb +9 -2
  24. data/lib/new_relic/agent/instrumentation/active_merchant.rb +14 -0
  25. data/lib/new_relic/agent/instrumentation/active_record_helper.rb +22 -6
  26. data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +16 -7
  27. data/lib/new_relic/agent/instrumentation/active_support_logger/chain.rb +23 -0
  28. data/lib/new_relic/agent/instrumentation/active_support_logger/instrumentation.rb +20 -0
  29. data/lib/new_relic/agent/instrumentation/active_support_logger/prepend.rb +12 -0
  30. data/lib/new_relic/agent/instrumentation/active_support_logger.rb +24 -0
  31. data/lib/new_relic/agent/instrumentation/acts_as_solr.rb +10 -0
  32. data/lib/new_relic/agent/instrumentation/authlogic.rb +10 -0
  33. data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +9 -4
  34. data/lib/new_relic/agent/instrumentation/curb/chain.rb +1 -1
  35. data/lib/new_relic/agent/instrumentation/curb/prepend.rb +1 -1
  36. data/lib/new_relic/agent/instrumentation/data_mapper.rb +12 -0
  37. data/lib/new_relic/agent/instrumentation/delayed_job_instrumentation.rb +19 -0
  38. data/lib/new_relic/agent/instrumentation/logger/instrumentation.rb +18 -18
  39. data/lib/new_relic/agent/instrumentation/logger.rb +4 -3
  40. data/lib/new_relic/agent/instrumentation/rack/helpers.rb +2 -0
  41. data/lib/new_relic/agent/instrumentation/rainbows_instrumentation.rb +11 -0
  42. data/lib/new_relic/agent/instrumentation/sidekiq.rb +15 -0
  43. data/lib/new_relic/agent/instrumentation/sinatra.rb +21 -11
  44. data/lib/new_relic/agent/instrumentation/sunspot.rb +10 -0
  45. data/lib/new_relic/agent/instrumentation/thread/chain.rb +24 -0
  46. data/lib/new_relic/agent/instrumentation/thread/instrumentation.rb +27 -0
  47. data/lib/new_relic/agent/instrumentation/thread/prepend.rb +22 -0
  48. data/lib/new_relic/agent/instrumentation/thread.rb +20 -0
  49. data/lib/new_relic/agent/linking_metadata.rb +45 -0
  50. data/lib/new_relic/agent/local_log_decorator.rb +37 -0
  51. data/lib/new_relic/agent/log_event_aggregator.rb +234 -0
  52. data/lib/new_relic/agent/log_priority.rb +20 -0
  53. data/lib/new_relic/agent/method_tracer.rb +9 -4
  54. data/lib/new_relic/agent/method_tracer_helpers.rb +80 -0
  55. data/lib/new_relic/agent/new_relic_service.rb +27 -23
  56. data/lib/new_relic/agent/pipe_service.rb +5 -2
  57. data/lib/new_relic/agent/samplers/memory_sampler.rb +6 -1
  58. data/lib/new_relic/agent/span_event_primitive.rb +9 -6
  59. data/lib/new_relic/agent/stats.rb +48 -23
  60. data/lib/new_relic/agent/tracer.rb +14 -1
  61. data/lib/new_relic/agent/transaction/abstract_segment.rb +29 -1
  62. data/lib/new_relic/agent/transaction/tracing.rb +8 -3
  63. data/lib/new_relic/agent/transaction.rb +47 -11
  64. data/lib/new_relic/agent/transaction_error_primitive.rb +2 -0
  65. data/lib/new_relic/agent/transaction_metrics.rb +5 -4
  66. data/lib/new_relic/agent/vm/mri_vm.rb +13 -1
  67. data/lib/new_relic/agent.rb +6 -14
  68. data/lib/new_relic/control/instrumentation.rb +31 -0
  69. data/lib/new_relic/dependency_detection.rb +1 -1
  70. data/lib/new_relic/helper.rb +40 -0
  71. data/lib/new_relic/language_support.rb +17 -0
  72. data/lib/new_relic/local_environment.rb +2 -0
  73. data/lib/new_relic/supportability_helper.rb +1 -0
  74. data/lib/new_relic/traced_thread.rb +35 -0
  75. data/lib/new_relic/version.rb +1 -1
  76. data/lib/tasks/config.rake +13 -5
  77. data/newrelic.yml +34 -4
  78. data/newrelic_rpm.gemspec +2 -3
  79. data/test/agent_helper.rb +18 -4
  80. metadata +30 -17
  81. data/ROADMAP.md +0 -24
@@ -15,23 +15,23 @@ module NewRelic
15
15
  class NewRelicService
16
16
  # Specifies the version of the agent's communication protocol with
17
17
  # the NewRelic hosted site.
18
-
19
18
  PROTOCOL_VERSION = 17
20
19
 
21
- # 1f147a42: v10 (tag 3.5.3.17)
22
- # cf0d1ff1: v9 (tag 3.5.0)
23
- # 14105: v8 (tag 2.10.3)
24
- # (no v7)
25
- # 10379: v6 (not tagged)
26
- # 4078: v5 (tag 2.5.4)
27
- # 2292: v4 (tag 2.3.6)
28
- # 1754: v3 (tag 2.3.0)
29
- # 534: v2 (shows up in 2.1.0, our first tag)
30
-
31
20
  # These include Errno connection errors, and all indicate that the
32
21
  # underlying TCP connection may be in a bad state.
33
22
  CONNECTION_ERRORS = [Timeout::Error, EOFError, SystemCallError, SocketError].freeze
34
23
 
24
+ # Don't perform compression on the payload unless its uncompressed size is
25
+ # greater than or equal to this number of bytes. In testing with
26
+ # Ruby 2.2 - 3.1, we determined an absolute minimum value for ASCII to be
27
+ # 535 bytes to obtain at least a 10% savings in size. It is recommended
28
+ # that this value be kept above that 535 number. It is also important to
29
+ # consider the CPU cost involved with performing compression and to find
30
+ # a balance between CPU cycles spent and bandwidth saved. A good
31
+ # reasonable default here is 2048 bytes, which is a tried and true Apache
32
+ # Tomcat default (as of v8.5.78)
33
+ MIN_BYTE_SIZE_TO_COMPRESS = 2048
34
+
35
35
  attr_accessor :request_timeout
36
36
  attr_reader :collector, :marshaller, :agent_id
37
37
 
@@ -177,26 +177,30 @@ module NewRelic
177
177
  :item_count => items.size)
178
178
  end
179
179
 
180
+ def log_event_data(data)
181
+ payload, size = LogEventAggregator.payload_to_melt_format(data)
182
+ invoke_remote(:log_event_data, payload, :item_count => size)
183
+ end
184
+
180
185
  def error_event_data(data)
181
186
  metadata, items = data
182
- invoke_remote(:error_event_data, [@agent_id, *data], :item_count => items.size)
187
+ response = invoke_remote(:error_event_data, [@agent_id, *data], :item_count => items.size)
183
188
  NewRelic::Agent.record_metric("Supportability/Events/TransactionError/Sent", :count => items.size)
184
189
  NewRelic::Agent.record_metric("Supportability/Events/TransactionError/Seen", :count => metadata[:events_seen])
190
+ response
185
191
  end
186
192
 
187
193
  def span_event_data(data)
188
194
  metadata, items = data
189
- invoke_remote(:span_event_data, [@agent_id, *data], :item_count => items.size)
195
+ response = invoke_remote(:span_event_data, [@agent_id, *data], :item_count => items.size)
190
196
  NewRelic::Agent.record_metric("Supportability/Events/SpanEvents/Sent", :count => items.size)
191
197
  NewRelic::Agent.record_metric("Supportability/Events/SpanEvents/Seen", :count => metadata[:events_seen])
198
+ response
192
199
  end
193
200
 
194
- # We do not compress if content is smaller than 64kb. There are
195
- # problems with bugs in Ruby in some versions that expose us
196
- # to a risk of segfaults if we compress aggressively.
197
201
  def compress_request_if_needed(data, endpoint)
198
202
  encoding = 'identity'
199
- if data.size > 64 * 1024
203
+ if data.size >= MIN_BYTE_SIZE_TO_COMPRESS
200
204
  encoding = Agent.config[:compressed_content_encoding]
201
205
  data = if encoding == 'deflate'
202
206
  Encoders::Compressed::Deflate.encode(data)
@@ -422,8 +426,8 @@ module NewRelic
422
426
  end
423
427
  serialize_finish_ts = Process.clock_gettime(Process::CLOCK_MONOTONIC)
424
428
 
429
+ size = data.size # only the uncompressed size is reported
425
430
  data, encoding = compress_request_if_needed(data, method)
426
- size = data.size
427
431
 
428
432
  # Preconnect needs to always use the configured collector host, not the redirect host
429
433
  # We reset it here so we are always using the configured collector during our creation of the new connection
@@ -454,7 +458,7 @@ module NewRelic
454
458
  def handle_serialization_error(method, e)
455
459
  NewRelic::Agent.increment_metric("Supportability/serialization_failure")
456
460
  NewRelic::Agent.increment_metric("Supportability/serialization_failure/#{method}")
457
- msg = "Failed to serialize #{method} data using #{@marshaller.class.to_s}: #{e.inspect}"
461
+ msg = "Failed to serialize #{method} data using #{@marshaller.class}: #{e.inspect}"
458
462
  error = SerializationError.new(msg)
459
463
  error.set_backtrace(e.backtrace)
460
464
  raise error
@@ -464,11 +468,11 @@ module NewRelic
464
468
  serialize_time = serialize_finish_ts && (serialize_finish_ts - start_ts)
465
469
  request_duration = response_check_ts && (response_check_ts - request_send_ts)
466
470
  if request_duration
467
- NewRelic::Agent.record_metric("Supportability/Agent/Collector/#{method.to_s}/Duration", request_duration)
471
+ NewRelic::Agent.record_metric("Supportability/Agent/Collector/#{method}/Duration", request_duration)
468
472
  end
469
473
  if serialize_time
470
474
  NewRelic::Agent.record_metric("Supportability/invoke_remote_serialize", serialize_time)
471
- NewRelic::Agent.record_metric("Supportability/invoke_remote_serialize/#{method.to_s}", serialize_time)
475
+ NewRelic::Agent.record_metric("Supportability/invoke_remote_serialize/#{method}", serialize_time)
472
476
  end
473
477
  end
474
478
 
@@ -482,8 +486,8 @@ module NewRelic
482
486
  # of items as arguments.
483
487
  def record_size_supportability_metrics(method, size_bytes, item_count)
484
488
  metrics = [
485
- "Supportability/invoke_remote_size",
486
- "Supportability/invoke_remote_size/#{method.to_s}"
489
+ "Supportability/Ruby/Collector/Output/Bytes",
490
+ "Supportability/Ruby/Collector/#{method}/Output/Bytes"
487
491
  ]
488
492
  # we may not have an item count, in which case, just record 0 for the exclusive time
489
493
  item_count ||= 0
@@ -10,8 +10,7 @@ module NewRelic
10
10
 
11
11
  def initialize(channel_id)
12
12
  @channel_id = channel_id
13
- @collector = NewRelic::Control::Server.new(:name => 'parent',
14
- :port => 0)
13
+ @collector = NewRelic::Control::Server.new({name: 'parent', port: 0})
15
14
  @pipe = NewRelic::Agent::PipeChannelManager.channels[@channel_id]
16
15
  if @pipe && @pipe.parent_pid != $$
17
16
  @pipe.after_fork_in_child
@@ -61,6 +60,10 @@ module NewRelic
61
60
  write_to_pipe(:sql_trace_data, sql) if sql
62
61
  end
63
62
 
63
+ def log_event_data(logs)
64
+ write_to_pipe(:log_event_data, logs) if logs
65
+ end
66
+
64
67
  def shutdown
65
68
  @pipe.close if @pipe
66
69
  end
@@ -3,6 +3,7 @@
3
3
  # See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
4
4
 
5
5
  require 'new_relic/agent/sampler'
6
+ require 'new_relic/helper'
6
7
 
7
8
  module NewRelic
8
9
  module Agent
@@ -46,7 +47,11 @@ module NewRelic
46
47
 
47
48
  def self.platform
48
49
  if RUBY_PLATFORM =~ /java/
49
- %x(uname -s).downcase
50
+ begin
51
+ NewRelic::Helper.run_command('uname -s').downcase
52
+ rescue NewRelic::CommandRunFailedError, NewRelic::CommandExecutableNotFoundError
53
+ 'unknown'
54
+ end
50
55
  else
51
56
  RUBY_PLATFORM.downcase
52
57
  end
@@ -165,18 +165,21 @@ module NewRelic
165
165
  end
166
166
  end
167
167
 
168
- def merge_and_freeze_attributes agent_attributes, error_attributes
169
- return agent_attributes.freeze unless error_attributes
170
- return error_attributes.freeze if agent_attributes.equal?(NewRelic::EMPTY_HASH)
171
- agent_attributes.merge!(error_attributes).freeze
168
+ def merge_hashes(hash1, hash2)
169
+ return hash1 if hash2.nil? || hash2.empty?
170
+ return hash2 if hash1.nil? || hash1.empty?
171
+
172
+ hash1.merge!(hash2)
172
173
  end
173
174
 
174
175
  def agent_attributes segment
175
176
  agent_attributes = segment.attributes
176
177
  .agent_attributes_for(NewRelic::Agent::AttributeFilter::DST_SPAN_EVENTS)
177
178
  error_attributes = error_attributes(segment)
178
- return NewRelic::EMPTY_HASH unless agent_attributes || error_attributes
179
- merge_and_freeze_attributes(agent_attributes, error_attributes)
179
+ code_attributes = segment.code_attributes
180
+ agent_attributes = merge_hashes(agent_attributes, error_attributes)
181
+ agent_attributes = merge_hashes(agent_attributes, code_attributes)
182
+ agent_attributes.freeze
180
183
  end
181
184
 
182
185
  def parent_guid segment
@@ -4,6 +4,8 @@
4
4
  module NewRelic
5
5
  module Agent
6
6
  class Stats
7
+ SKIP_MARSHALLING = [:@lock]
8
+
7
9
  attr_accessor :call_count
8
10
  attr_accessor :min_call_time
9
11
  attr_accessor :max_call_time
@@ -12,6 +14,7 @@ module NewRelic
12
14
  attr_accessor :sum_of_squares
13
15
 
14
16
  def initialize
17
+ @lock = Mutex.new
15
18
  reset
16
19
  end
17
20
 
@@ -34,12 +37,14 @@ module NewRelic
34
37
  end
35
38
 
36
39
  def merge!(other)
37
- @min_call_time = other.min_call_time if min_time_less?(other)
38
- @max_call_time = other.max_call_time if other.max_call_time > max_call_time
39
- @total_call_time += other.total_call_time
40
- @total_exclusive_time += other.total_exclusive_time
41
- @sum_of_squares += other.sum_of_squares
42
- @call_count += other.call_count
40
+ @lock.synchronize do
41
+ @min_call_time = other.min_call_time if min_time_less?(other)
42
+ @max_call_time = other.max_call_time if other.max_call_time > max_call_time
43
+ @total_call_time += other.total_call_time
44
+ @total_exclusive_time += other.total_exclusive_time
45
+ @sum_of_squares += other.sum_of_squares
46
+ @call_count += other.call_count
47
+ end
43
48
  self
44
49
  end
45
50
 
@@ -78,13 +83,15 @@ module NewRelic
78
83
  # will aggregate all data points collected over a specified period and upload
79
84
  # its data to the NewRelic server
80
85
  def record_data_point(value, exclusive_time = value)
81
- @call_count += 1
82
- @total_call_time += value
83
- @min_call_time = value if value < @min_call_time || @call_count == 1
84
- @max_call_time = value if value > @max_call_time
85
- @total_exclusive_time += exclusive_time
86
-
87
- @sum_of_squares += (value * value)
86
+ @lock.synchronize do
87
+ @call_count += 1
88
+ @total_call_time += value
89
+ @min_call_time = value if value < @min_call_time || @call_count == 1
90
+ @max_call_time = value if value > @max_call_time
91
+ @total_exclusive_time += exclusive_time
92
+
93
+ @sum_of_squares += (value * value)
94
+ end
88
95
  self
89
96
  end
90
97
 
@@ -92,7 +99,7 @@ module NewRelic
92
99
 
93
100
  # increments the call_count by one
94
101
  def increment_count(value = 1)
95
- @call_count += value
102
+ @lock.synchronize { @call_count += value }
96
103
  end
97
104
 
98
105
  # Concerned about implicit usage of inspect relying on stats format, so
@@ -122,17 +129,35 @@ module NewRelic
122
129
  alias_method :apdex_f, :total_exclusive_time
123
130
 
124
131
  def record_apdex(bucket, apdex_t)
125
- case bucket
126
- when :apdex_s then @call_count += 1
127
- when :apdex_t then @total_call_time += 1
128
- when :apdex_f then @total_exclusive_time += 1
132
+ @lock.synchronize do
133
+ case bucket
134
+ when :apdex_s then @call_count += 1
135
+ when :apdex_t then @total_call_time += 1
136
+ when :apdex_f then @total_exclusive_time += 1
137
+ end
138
+ if apdex_t
139
+ @min_call_time = apdex_t
140
+ @max_call_time = apdex_t
141
+ else
142
+ ::NewRelic::Agent.logger.warn("Attempted to set apdex_t to #{apdex_t.inspect}, backtrace = #{caller.join("\n")}")
143
+ end
129
144
  end
130
- if apdex_t
131
- @min_call_time = apdex_t
132
- @max_call_time = apdex_t
133
- else
134
- ::NewRelic::Agent.logger.warn("Attempted to set apdex_t to #{apdex_t.inspect}, backtrace = #{caller.join("\n")}")
145
+ end
146
+
147
+ # Override marshalling methods to exclude @lock from being included in marshalled data
148
+ def marshal_dump
149
+ instance_variables.each_with_object({}) do |name, instance_copy|
150
+ next if SKIP_MARSHALLING.include?(name)
151
+ instance_copy[name] = instance_variable_get(name)
152
+ end
153
+ end
154
+
155
+ def marshal_load(marshalled_data)
156
+ marshalled_data.each do |name, value|
157
+ instance_variable_set(name, value) unless SKIP_MARSHALLING.include?(name)
135
158
  end
159
+ # since the lock is excluded when marshalling, create a new lock when loading marshalled data
160
+ @lock = Mutex.new
136
161
  end
137
162
 
138
163
  protected
@@ -405,6 +405,19 @@ module NewRelic
405
405
 
406
406
  alias_method :tl_clear, :clear_state
407
407
 
408
+ def thread_block_with_current_transaction(*args, &block)
409
+ current_txn = ::Thread.current[:newrelic_tracer_state].current_transaction if ::Thread.current[:newrelic_tracer_state]
410
+ Proc.new do
411
+ begin
412
+ NewRelic::Agent::Tracer.state.current_transaction = current_txn
413
+ segment = NewRelic::Agent::Tracer.start_segment(name: "Ruby/Thread/#{::Thread.current.object_id}")
414
+ block.call(*args) if block.respond_to?(:call)
415
+ ensure
416
+ segment.finish if segment
417
+ end
418
+ end
419
+ end
420
+
408
421
  private
409
422
 
410
423
  def start_and_add_segment segment, parent = nil
@@ -445,7 +458,7 @@ module NewRelic
445
458
  end
446
459
 
447
460
  # Current transaction stack
448
- attr_reader :current_transaction
461
+ attr_accessor :current_transaction
449
462
 
450
463
  # Execution tracing on current thread
451
464
  attr_accessor :untraced
@@ -20,13 +20,14 @@ module NewRelic
20
20
  # after its parent. We will use the optimized exclusive duration
21
21
  # calculation in all other cases.
22
22
  #
23
- attr_reader :start_time, :end_time, :duration, :exclusive_duration, :guid
23
+ attr_reader :start_time, :end_time, :duration, :exclusive_duration, :guid, :starting_thread_id
24
24
  attr_accessor :name, :parent, :children_time, :transaction, :transaction_name
25
25
  attr_writer :record_metrics, :record_scoped_metric, :record_on_finish
26
26
  attr_reader :noticed_error
27
27
 
28
28
  def initialize name = nil, start_time = nil
29
29
  @name = name
30
+ @starting_thread_id = ::Thread.current.object_id
30
31
  @transaction_name = nil
31
32
  @transaction = nil
32
33
  @guid = NewRelic::Agent::GuidGenerator.generate_guid
@@ -45,6 +46,10 @@ module NewRelic
45
46
  @record_scoped_metric = true
46
47
  @record_on_finish = false
47
48
  @noticed_error = nil
49
+ @code_filepath = nil
50
+ @code_function = nil
51
+ @code_lineno = nil
52
+ @code_namespace = nil
48
53
  end
49
54
 
50
55
  def start
@@ -56,6 +61,7 @@ module NewRelic
56
61
  def finish
57
62
  @end_time = Process.clock_gettime(Process::CLOCK_REALTIME)
58
63
  @duration = end_time - start_time
64
+
59
65
  return unless transaction
60
66
  run_complete_callbacks
61
67
  finalize if record_on_finish?
@@ -109,6 +115,28 @@ module NewRelic
109
115
  @concurrent_children
110
116
  end
111
117
 
118
+ def code_information=(info = {})
119
+ return unless info[:filepath]
120
+
121
+ @code_filepath = info[:filepath]
122
+ @code_function = info[:function]
123
+ @code_lineno = info[:lineno]
124
+ @code_namespace = info[:namespace]
125
+ end
126
+
127
+ def all_code_information_present?
128
+ @code_filepath && @code_function && @code_lineno && @code_namespace
129
+ end
130
+
131
+ def code_attributes
132
+ return ::NewRelic::EMPTY_HASH unless all_code_information_present?
133
+
134
+ @code_attributes ||= {'code.filepath' => @code_filepath,
135
+ 'code.function' => @code_function,
136
+ 'code.lineno' => @code_lineno,
137
+ 'code.namespace' => @code_namespace}
138
+ end
139
+
112
140
  INSPECT_IGNORE = [:@transaction, :@transaction_state].freeze
113
141
 
114
142
  def inspect
@@ -6,7 +6,7 @@ module NewRelic
6
6
  module Agent
7
7
  class Transaction
8
8
  module Tracing
9
- attr_reader :current_segment
9
+ attr_reader :current_segment_by_thread
10
10
 
11
11
  def async?
12
12
  @async ||= false
@@ -23,7 +23,7 @@ module NewRelic
23
23
  def add_segment segment, parent = nil
24
24
  segment.transaction = self
25
25
  segment.parent = parent || current_segment
26
- @current_segment = segment
26
+ set_current_segment segment
27
27
  if @segments.length < segment_limit
28
28
  @segments << segment
29
29
  else
@@ -34,7 +34,12 @@ module NewRelic
34
34
  end
35
35
 
36
36
  def segment_complete segment
37
- @current_segment = segment.parent
37
+ # if parent was in another thread, remove the current_segment entry for this thread
38
+ if segment.parent && segment.parent.starting_thread_id != ::Thread.current.object_id
39
+ remove_current_segment_by_thread_id(::Thread.current.object_id)
40
+ else
41
+ set_current_segment segment.parent
42
+ end
38
43
  end
39
44
 
40
45
  def segment_limit
@@ -77,6 +77,7 @@ module NewRelic
77
77
 
78
78
  attr_reader :guid,
79
79
  :metrics,
80
+ :logs,
80
81
  :gc_start_snapshot,
81
82
  :category,
82
83
  :attributes,
@@ -123,7 +124,7 @@ module NewRelic
123
124
  txn = Transaction.new(category, options)
124
125
  state.reset(txn)
125
126
  txn.state = state
126
- txn.start
127
+ txn.start(options)
127
128
  txn
128
129
  end
129
130
 
@@ -216,7 +217,8 @@ module NewRelic
216
217
 
217
218
  def initialize(category, options)
218
219
  @nesting_max_depth = 0
219
- @current_segment = nil
220
+ @current_segment_by_thread = {}
221
+ @current_segment_lock = Mutex.new
220
222
  @segments = []
221
223
 
222
224
  self.default_name = options[:transaction_name]
@@ -236,6 +238,7 @@ module NewRelic
236
238
 
237
239
  @exceptions = {}
238
240
  @metrics = TransactionMetrics.new
241
+ @logs = PrioritySampledBuffer.new(NewRelic::Agent.instance.log_event_aggregator.capacity)
239
242
  @guid = NewRelic::Agent::GuidGenerator.generate_guid
240
243
 
241
244
  @ignore_this_transaction = false
@@ -259,6 +262,25 @@ module NewRelic
259
262
  end
260
263
  end
261
264
 
265
+ def parent_thread_id
266
+ ::Thread.current.nr_parent_thread_id if ::Thread.current.respond_to?(:nr_parent_thread_id)
267
+ end
268
+
269
+ def current_segment
270
+ current_thread_id = ::Thread.current.object_id
271
+ return current_segment_by_thread[current_thread_id] if current_segment_by_thread[current_thread_id]
272
+ return current_segment_by_thread[parent_thread_id] if current_segment_by_thread[parent_thread_id]
273
+ current_segment_by_thread[@starting_thread_id]
274
+ end
275
+
276
+ def set_current_segment(new_segment)
277
+ @current_segment_lock.synchronize { current_segment_by_thread[::Thread.current.object_id] = new_segment }
278
+ end
279
+
280
+ def remove_current_segment_by_thread_id(id)
281
+ @current_segment_lock.synchronize { current_segment_by_thread.delete(id) }
282
+ end
283
+
262
284
  def distributed_tracer
263
285
  @distributed_tracer ||= DistributedTracer.new(self)
264
286
  end
@@ -392,7 +414,7 @@ module NewRelic
392
414
  @frozen_name ? true : false
393
415
  end
394
416
 
395
- def start
417
+ def start(options = {})
396
418
  return if !state.is_execution_traced?
397
419
 
398
420
  sql_sampler.on_start_transaction(state, request_path)
@@ -401,7 +423,7 @@ module NewRelic
401
423
 
402
424
  ignore! if user_defined_rules_ignore?
403
425
 
404
- create_initial_segment
426
+ create_initial_segment(options)
405
427
  Segment.merge_untrusted_agent_attributes \
406
428
  @filtered_params,
407
429
  :'request.parameters',
@@ -412,12 +434,12 @@ module NewRelic
412
434
  segments.first
413
435
  end
414
436
 
415
- def create_initial_segment
416
- segment = create_segment @default_name
437
+ def create_initial_segment(options = {})
438
+ segment = create_segment @default_name, options
417
439
  segment.record_scoped_metric = false
418
440
  end
419
441
 
420
- def create_segment(name)
442
+ def create_segment(name, options = {})
421
443
  summary_metrics = nil
422
444
 
423
445
  if name.start_with?(MIDDLEWARE_PREFIX)
@@ -431,6 +453,10 @@ module NewRelic
431
453
  unscoped_metrics: summary_metrics
432
454
  )
433
455
 
456
+ # #code_information will glean the code info out of the options hash
457
+ # if it exists or noop otherwise
458
+ segment.code_information = options
459
+
434
460
  segment
435
461
  end
436
462
 
@@ -445,7 +471,8 @@ module NewRelic
445
471
 
446
472
  nest_initial_segment if segments.length == 1
447
473
  nested_name = self.class.nested_transaction_name options[:transaction_name]
448
- segment = create_segment nested_name
474
+
475
+ segment = create_segment nested_name, options
449
476
  set_default_transaction_name(options[:transaction_name], category)
450
477
  segment
451
478
  end
@@ -533,6 +560,7 @@ module NewRelic
533
560
 
534
561
  record_exceptions
535
562
  record_transaction_event
563
+ record_log_events
536
564
  merge_metrics
537
565
  send_transaction_finished_event
538
566
  end
@@ -716,9 +744,9 @@ module NewRelic
716
744
  # Do not call this. Invoke the class method instead.
717
745
  def notice_error(error, options = {}) # :nodoc:
718
746
  # Only the last error is kept
719
- if @current_segment
720
- @current_segment.notice_error error, expected: options[:expected]
721
- options[:span_id] = @current_segment.guid
747
+ if current_segment
748
+ current_segment.notice_error(error, expected: options[:expected])
749
+ options[:span_id] = current_segment.guid
722
750
  end
723
751
 
724
752
  if @exceptions[error]
@@ -732,6 +760,10 @@ module NewRelic
732
760
  agent.transaction_event_recorder.record payload
733
761
  end
734
762
 
763
+ def record_log_events
764
+ agent.log_event_aggregator.record_batch self, @logs.to_a
765
+ end
766
+
735
767
  def queue_time
736
768
  @apdex_start ? @start_time - @apdex_start : 0
737
769
  end
@@ -826,6 +858,10 @@ module NewRelic
826
858
  attributes.merge_custom_attributes(p)
827
859
  end
828
860
 
861
+ def add_log_event(event)
862
+ logs.append(event: event)
863
+ end
864
+
829
865
  def recording_web_transaction?
830
866
  web_category?(@category)
831
867
  end
@@ -63,6 +63,8 @@ module NewRelic
63
63
  append_cat payload, attrs
64
64
  DistributedTraceAttributes.copy_to_hash payload, attrs
65
65
  PayloadMetricMapping.append_mapped_metrics payload[:metrics], attrs
66
+ else
67
+ attrs[PRIORITY_KEY] = rand.round(NewRelic::PRIORITY_PRECISION)
66
68
  end
67
69
 
68
70
  attrs
@@ -13,6 +13,7 @@ module NewRelic
13
13
  DEFAULT_PROC = Proc.new { |hash, name| hash[name] = NewRelic::Agent::Stats.new }
14
14
 
15
15
  def initialize
16
+ @lock = Mutex.new
16
17
  @unscoped = Hash.new(&DEFAULT_PROC)
17
18
  @scoped = Hash.new(&DEFAULT_PROC)
18
19
  end
@@ -42,11 +43,11 @@ module NewRelic
42
43
  end
43
44
 
44
45
  def each_unscoped
45
- @unscoped.each { |name, stats| yield name, stats }
46
+ @lock.synchronize { @unscoped.each { |name, stats| yield name, stats } }
46
47
  end
47
48
 
48
49
  def each_scoped
49
- @scoped.each { |name, stats| yield name, stats }
50
+ @lock.synchronize { @scoped.each { |name, stats| yield name, stats } }
50
51
  end
51
52
 
52
53
  def _record_metrics(names, value, aux, target, &blk)
@@ -54,10 +55,10 @@ module NewRelic
54
55
  case names
55
56
  when Array
56
57
  names.each do |name|
57
- target[name].record(value, aux, &blk)
58
+ @lock.synchronize { target[name].record(value, aux, &blk) }
58
59
  end
59
60
  else
60
- target[names].record(value, aux, &blk)
61
+ @lock.synchronize { target[names].record(value, aux, &blk) }
61
62
  end
62
63
  end
63
64
  end
@@ -49,7 +49,19 @@ module NewRelic
49
49
  end
50
50
 
51
51
  if supports?(:constant_cache_invalidations)
52
- snap.constant_cache_invalidations = RubyVM.stat[:global_constant_state]
52
+ snap.constant_cache_invalidations = gather_constant_cache_invalidations
53
+ end
54
+ end
55
+
56
+ def gather_constant_cache_invalidations
57
+ # Ruby >= 3.2 uses :constant_cache
58
+ # see: https://github.com/ruby/ruby/pull/5433 and https://bugs.ruby-lang.org/issues/18589
59
+ # TODO: now that 3.2+ provides more granual cache invalidation data, should we report it instead of summing?
60
+ if RUBY_VERSION >= '3.2.0'
61
+ RubyVM.stat[:constant_cache].values.sum
62
+ # Ruby < 3.2 uses :global_constant_state
63
+ else
64
+ RubyVM.stat[:global_constant_state]
53
65
  end
54
66
  end
55
67