newrelic_rpm 8.7.0 → 8.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +6 -3
- data/.yardopts +1 -0
- data/CHANGELOG.md +19 -0
- data/CONTRIBUTING.md +1 -1
- data/Rakefile +25 -20
- data/lib/new_relic/agent/configuration/default_source.rb +7 -0
- data/lib/new_relic/agent/database.rb +5 -5
- data/lib/new_relic/agent/datastores/metric_helper.rb +3 -1
- data/lib/new_relic/agent/instrumentation/action_controller_subscriber.rb +21 -18
- data/lib/new_relic/agent/instrumentation/active_job.rb +9 -2
- data/lib/new_relic/agent/instrumentation/active_record_helper.rb +22 -6
- data/lib/new_relic/agent/instrumentation/active_record_subscriber.rb +16 -7
- data/lib/new_relic/agent/instrumentation/controller_instrumentation.rb +9 -4
- data/lib/new_relic/agent/log_event_aggregator.rb +1 -1
- data/lib/new_relic/agent/method_tracer.rb +9 -4
- data/lib/new_relic/agent/method_tracer_helpers.rb +80 -0
- data/lib/new_relic/agent/new_relic_service.rb +13 -16
- data/lib/new_relic/agent/span_event_primitive.rb +9 -6
- data/lib/new_relic/agent/transaction/abstract_segment.rb +27 -0
- data/lib/new_relic/agent/transaction.rb +12 -7
- data/lib/new_relic/agent.rb +2 -0
- data/lib/new_relic/traced_thread.rb +5 -6
- data/lib/new_relic/version.rb +1 -1
- data/newrelic.yml +3 -0
- data/newrelic_rpm.gemspec +0 -5
- metadata +3 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 0eb3871a3ff66d8707dd01f40025d812d34904c02631d3a83af046bdbaeacdeb
|
4
|
+
data.tar.gz: 4e73dfe935bcd268ad0969a2c076e83313e75c3f3f0b711a7cba8c3af1327677
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 07566af3aff20a1fe5951fb36e34086f665e8788ecb7edfd2664213e73877e7a1c58d4447f2dd85efb75beeda0ce2f484c513d4a7a09c01bcbe4c20e8a5cba1a
|
7
|
+
data.tar.gz: 577944d3712cdd9908d4a564346e7f4bd6fd5772fa00069d73acdb3d8a033621042c72346ddd4ca22585910f035116701f0f904dd8f40c8f0b1e9934bd787705
|
data/.rubocop.yml
CHANGED
@@ -819,9 +819,6 @@ Lint/UselessAccessModifier:
|
|
819
819
|
Lint/UselessAssignment:
|
820
820
|
Enabled: false
|
821
821
|
|
822
|
-
Lint/UselessElseWithoutRescue:
|
823
|
-
Enabled: true
|
824
|
-
|
825
822
|
Lint/UselessMethodDefinition:
|
826
823
|
Enabled: false
|
827
824
|
|
@@ -967,6 +964,9 @@ Performance/CollectionLiteralInLoop:
|
|
967
964
|
Performance/CompareWithBlock:
|
968
965
|
Enabled: true
|
969
966
|
|
967
|
+
Performance/ConcurrentMonotonicTime:
|
968
|
+
Enabled: true
|
969
|
+
|
970
970
|
# Disabling for now
|
971
971
|
Performance/ConstantRegexp:
|
972
972
|
Enabled: false
|
@@ -1072,6 +1072,9 @@ Performance/Squeeze:
|
|
1072
1072
|
Performance/StartWith:
|
1073
1073
|
Enabled: false
|
1074
1074
|
|
1075
|
+
Performance/StringIdentifierArgument:
|
1076
|
+
Enabled: true
|
1077
|
+
|
1075
1078
|
Performance/StringInclude:
|
1076
1079
|
Enabled: false
|
1077
1080
|
|
data/.yardopts
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,24 @@
|
|
1
1
|
# New Relic Ruby Agent Release Notes #
|
2
2
|
|
3
|
+
## v8.8.0
|
4
|
+
|
5
|
+
* **Support Makara database adapters with ActiveRecord**
|
6
|
+
|
7
|
+
Thanks to a community submission from @lucasklaassen with [PR #1177](https://github.com/newrelic/newrelic-ruby-agent/pull/1177), the Ruby agent will now correctly work well with the [Makara gem](https://github.com/instacart/makara). Functionality such as SQL obfuscation should now work when Makara database adapters are used with Active Record.
|
8
|
+
|
9
|
+
* **Lowered the minimum payload size to compress**
|
10
|
+
|
11
|
+
Previously the Ruby agent used a particularly large payload size threshold of 64KiB that would need to be met before the agent would compress data en route to New Relic's collector. The original value stems from segfault issues that very old Rubies (< 2.2) used to encounter when compressing smaller payloads. This value has been lowered to 2KiB (2048 bytes), which should provide a more optimal balance between the CPU cycles spent on compression and the bandwidth savings gained from it.
|
12
|
+
|
13
|
+
* **Provide Code Level Metrics for New Relic CodeStream**
|
14
|
+
|
15
|
+
For Ruby on Rails applications and/or those with manually traced methods, the agent is now capable of reporting metrics with Ruby method-level granularity. When the new `code_level_metrics.enabled` configuration parameter is set to a `true` value, the agent will associate source-code-related metadata with the metrics for things such as Rails controller methods. Then, when the corresponding Ruby class file that defines the methods is loaded up in a [New Relic CodeStream](https://www.codestream.com/)-powered IDE, [the four golden signals](https://sre.google/sre-book/monitoring-distributed-systems/) for each method will be presented to the developer directly.
|
16
|
+
|
17
|
+
* **Supportability Metrics will always report uncompressed payload size**
|
18
|
+
|
19
|
+
New Relic's agent specifications call for Supportability Metrics to always reference the uncompressed payload byte size. Previously, the Ruby agent was calculating the byte size after compression. Furthermore, compression is only performed on payloads of a certain size. This means that sometimes the value could have represented a compressed size and sometimes an uncompressed one. Now the uncompressed value is always used, bringing consistency for comparing two instances of the same metric and alignment with the New Relic agent specifications.
|
20
|
+
|
21
|
+
|
3
22
|
## v8.7.0
|
4
23
|
|
5
24
|
* **APM logs-in-context log forwarding on by default**
|
data/CONTRIBUTING.md
CHANGED
@@ -128,7 +128,7 @@ This will run the unit tests in standalone mode. You can run against a specific
|
|
128
128
|
by passing the version name (which should match the name of a subdirectory in test/environments)
|
129
129
|
as an argument to the test:env rake task, like this:
|
130
130
|
|
131
|
-
bundle exec rake test:env[rails60]
|
131
|
+
bundle exec rake 'test:env[rails60]'
|
132
132
|
|
133
133
|
These tests are setup to run automatically in
|
134
134
|
[GitHub Actions](https://github.com/newrelic/newrelic-ruby-agent/actions) under several
|
data/Rakefile
CHANGED
@@ -11,15 +11,6 @@ task :test => ['test:newrelic']
|
|
11
11
|
namespace :test do
|
12
12
|
desc "Run all tests"
|
13
13
|
task :all => %w[newrelic multiverse all_compatible_envs]
|
14
|
-
|
15
|
-
begin
|
16
|
-
require 'test_bisect'
|
17
|
-
TestBisect::BisectTask.new do |t|
|
18
|
-
t.test_task_name = 'test:newrelic'
|
19
|
-
end
|
20
|
-
rescue LoadError
|
21
|
-
end
|
22
|
-
|
23
14
|
agent_home = File.expand_path(File.dirname(__FILE__))
|
24
15
|
|
25
16
|
desc "Run agent performance tests"
|
@@ -116,24 +107,38 @@ task :update_ca_bundle do |t|
|
|
116
107
|
end
|
117
108
|
|
118
109
|
namespace :cross_agent_tests do
|
119
|
-
|
120
|
-
|
110
|
+
CROSS_AGENT_TESTS_UPSTREAM_PATH = File.expand_path(File.join('..', 'cross_agent_tests')).freeze
|
111
|
+
CROSS_AGENT_TESTS_LOCAL_PATH = File.expand_path(File.join('test', 'fixtures', 'cross_agent_tests')).freeze
|
112
|
+
|
113
|
+
def prompt_to_continue(command, destination = 'local')
|
114
|
+
puts "The following rsync command will be executed to update the #{destination} copy of the specs:"
|
115
|
+
puts
|
116
|
+
puts command
|
117
|
+
puts
|
118
|
+
puts "CAUTION: Any unsaved changes that exist within the #{destination} content will be OVERWRITTEN!"
|
119
|
+
if destination.eql?('local')
|
120
|
+
puts 'CAUTION 2: Before continuing, make sure your local repo is on the correct, synced branch!'
|
121
|
+
end
|
122
|
+
puts
|
123
|
+
print "Do you wish to continue? ('y' to continue, return to cancel) [n] "
|
124
|
+
continue = STDIN.gets.chomp
|
125
|
+
if continue.downcase.eql?('y')
|
126
|
+
system(command)
|
127
|
+
else
|
128
|
+
puts 'Cancelled'
|
129
|
+
end
|
130
|
+
end
|
121
131
|
|
122
|
-
# Note: before you pull, make sure your local repo is on the correct, synced branch!
|
123
132
|
desc 'Pull latest changes from cross_agent_tests repo'
|
124
133
|
task :pull do
|
125
|
-
|
126
|
-
|
127
|
-
puts cmd
|
128
|
-
system(cmd)
|
134
|
+
command = " rsync -av --exclude .git #{CROSS_AGENT_TESTS_UPSTREAM_PATH}/ #{CROSS_AGENT_TESTS_LOCAL_PATH}/"
|
135
|
+
prompt_to_continue(command)
|
129
136
|
end
|
130
137
|
|
131
138
|
desc 'Copy changes from embedded cross_agent_tests to official repo working copy'
|
132
139
|
task :push do
|
133
|
-
|
134
|
-
|
135
|
-
puts cmd
|
136
|
-
system(cmd)
|
140
|
+
command = "rsync -av #{CROSS_AGENT_TESTS_LOCAL_PATH}/ #{CROSS_AGENT_TESTS_UPSTREAM_PATH}/"
|
141
|
+
prompt_to_continue(command, 'remote (agent spec repo)')
|
137
142
|
end
|
138
143
|
end
|
139
144
|
|
@@ -2010,6 +2010,13 @@ A map of error classes to a list of messages. When an error of one of the classe
|
|
2010
2010
|
:allowed_from_server => false,
|
2011
2011
|
:description => 'If `true`, the agent decorates logs with metadata to link to entities, hosts, traces, and spans.'
|
2012
2012
|
},
|
2013
|
+
:'code_level_metrics.enabled' => {
|
2014
|
+
:default => false,
|
2015
|
+
:public => true,
|
2016
|
+
:type => Boolean,
|
2017
|
+
:allowed_from_server => true,
|
2018
|
+
:description => 'If `true`, the agent will report source code level metrics for traced methods.'
|
2019
|
+
},
|
2013
2020
|
:'instrumentation.active_support_logger' => {
|
2014
2021
|
:default => instrumentation_value_from_boolean(:'application_logging.enabled'),
|
2015
2022
|
:documentation_default => 'auto',
|
@@ -131,15 +131,15 @@ module NewRelic
|
|
131
131
|
'execute',
|
132
132
|
'call'
|
133
133
|
]
|
134
|
-
|
134
|
+
OTHER_OPERATION = 'other'.freeze
|
135
135
|
SQL_COMMENT_REGEX = Regexp.new('/\*.*?\*/', Regexp::MULTILINE).freeze
|
136
136
|
|
137
137
|
def parse_operation_from_query(sql)
|
138
138
|
sql = Helper.correctly_encoded(sql).gsub(SQL_COMMENT_REGEX, NewRelic::EMPTY_STR)
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
139
|
+
return unless sql =~ /(\w+)/
|
140
|
+
|
141
|
+
op = Regexp.last_match(1).downcase
|
142
|
+
KNOWN_OPERATIONS.include?(op) ? op : OTHER_OPERATION
|
143
143
|
end
|
144
144
|
|
145
145
|
class ConnectionManager
|
@@ -101,7 +101,9 @@ module NewRelic
|
|
101
101
|
end
|
102
102
|
|
103
103
|
def self.operation_from_sql(sql)
|
104
|
-
NewRelic::Agent::Database.parse_operation_from_query(sql)
|
104
|
+
operation = NewRelic::Agent::Database.parse_operation_from_query(sql)
|
105
|
+
operation = OTHER if operation.eql?(NewRelic::Agent::Database::OTHER_OPERATION)
|
106
|
+
operation
|
105
107
|
end
|
106
108
|
|
107
109
|
# Allow Transaction#with_database_metric_name to override our
|
@@ -50,27 +50,30 @@ module NewRelic
|
|
50
50
|
Tracer.start_transaction_or_segment(
|
51
51
|
name: format_metric_name(payload[:action], controller_class),
|
52
52
|
category: :controller,
|
53
|
-
options:
|
54
|
-
request: request,
|
55
|
-
filtered_params: NewRelic::Agent::ParameterFiltering.filter_using_rails(
|
56
|
-
payload[:params],
|
57
|
-
Rails.application.config.filter_parameters
|
58
|
-
),
|
59
|
-
apdex_start_time: queue_start(request),
|
60
|
-
ignore_apdex: NewRelic::Agent::Instrumentation::IgnoreActions.is_filtered?(
|
61
|
-
ControllerInstrumentation::NR_IGNORE_APDEX_KEY,
|
62
|
-
controller_class,
|
63
|
-
payload[:action]
|
64
|
-
),
|
65
|
-
ignore_enduser: NewRelic::Agent::Instrumentation::IgnoreActions.is_filtered?(
|
66
|
-
ControllerInstrumentation::NR_IGNORE_ENDUSER_KEY,
|
67
|
-
controller_class,
|
68
|
-
payload[:action]
|
69
|
-
)
|
70
|
-
}
|
53
|
+
options: tracer_options(payload, request, controller_class)
|
71
54
|
)
|
72
55
|
end
|
73
56
|
|
57
|
+
def tracer_options(payload, request, controller_class)
|
58
|
+
{
|
59
|
+
request: request,
|
60
|
+
filtered_params: filtered_params(payload[:params]),
|
61
|
+
apdex_start_time: queue_start(request),
|
62
|
+
ignore_apdex: ignore?(payload[:action], ControllerInstrumentation::NR_IGNORE_APDEX_KEY, controller_class),
|
63
|
+
ignore_enduser: ignore?(payload[:action],
|
64
|
+
ControllerInstrumentation::NR_IGNORE_ENDUSER_KEY,
|
65
|
+
controller_class)
|
66
|
+
}.merge(NewRelic::Agent::MethodTracerHelpers.code_information(controller_class, payload[:action]))
|
67
|
+
end
|
68
|
+
|
69
|
+
def filtered_params(params)
|
70
|
+
NewRelic::Agent::ParameterFiltering.filter_using_rails(params, Rails.application.config.filter_parameters)
|
71
|
+
end
|
72
|
+
|
73
|
+
def ignore?(action, key, controller_class)
|
74
|
+
NewRelic::Agent::Instrumentation::IgnoreActions.is_filtered?(key, controller_class, action)
|
75
|
+
end
|
76
|
+
|
74
77
|
def format_metric_name(metric_action, controller)
|
75
78
|
controller_class = controller.is_a?(Class) ? controller : Object.const_get(controller)
|
76
79
|
"Controller/#{controller_class.controller_path}/#{metric_action}"
|
@@ -58,14 +58,21 @@ module NewRelic
|
|
58
58
|
end
|
59
59
|
|
60
60
|
def self.run_in_trace(job, block, event)
|
61
|
-
trace_execution_scoped("MessageBroker/#{adapter}/Queue/#{event}/Named/#{job.queue_name}"
|
61
|
+
trace_execution_scoped("MessageBroker/#{adapter}/Queue/#{event}/Named/#{job.queue_name}",
|
62
|
+
code_information: code_information_for_job(job)) do
|
62
63
|
block.call
|
63
64
|
end
|
64
65
|
end
|
65
66
|
|
66
67
|
def self.run_in_transaction(job, block)
|
68
|
+
options = code_information_for_job(job)
|
69
|
+
options = {} if options.frozen? # the hash will be added to later
|
67
70
|
::NewRelic::Agent::Tracer.in_transaction(name: transaction_name_for_job(job),
|
68
|
-
category: :other, &block)
|
71
|
+
category: :other, options: options, &block)
|
72
|
+
end
|
73
|
+
|
74
|
+
def self.code_information_for_job(job)
|
75
|
+
NewRelic::Agent::MethodTracerHelpers.code_information(job.class, :perform)
|
69
76
|
end
|
70
77
|
|
71
78
|
def self.transaction_category
|
@@ -93,9 +93,21 @@ module NewRelic
|
|
93
93
|
|
94
94
|
ACTIVE_RECORD = "ActiveRecord".freeze
|
95
95
|
OTHER = "other".freeze
|
96
|
+
MAKARA_SUFFIX = "_makara".freeze
|
97
|
+
|
98
|
+
# convert vendor (makara, etc.) wrapper names to their bare names
|
99
|
+
# ex: postgresql_makara -> postgresql
|
100
|
+
def bare_adapter_name(adapter_name)
|
101
|
+
# TODO: OLD RUBIES - RUBY_VERSION < 2.5
|
102
|
+
# With Ruby 2.5+ we could use #delete_suffix instead of #chomp for a
|
103
|
+
# potential speed boost
|
104
|
+
return adapter_name.chomp(MAKARA_SUFFIX) if adapter_name && adapter_name.end_with?(MAKARA_SUFFIX)
|
105
|
+
|
106
|
+
adapter_name
|
107
|
+
end
|
96
108
|
|
97
109
|
def product_operation_collection_for name, sql, adapter_name
|
98
|
-
product = map_product(adapter_name)
|
110
|
+
product = map_product(bare_adapter_name(adapter_name))
|
99
111
|
splits = split_name(name)
|
100
112
|
model = model_from_splits(splits)
|
101
113
|
operation = operation_from_splits(splits, sql)
|
@@ -194,8 +206,7 @@ module NewRelic
|
|
194
206
|
ACTIVE_RECORD_DEFAULT_PRODUCT_NAME = "ActiveRecord".freeze
|
195
207
|
|
196
208
|
def map_product(adapter_name)
|
197
|
-
PRODUCT_NAMES.fetch(adapter_name,
|
198
|
-
ACTIVE_RECORD_DEFAULT_PRODUCT_NAME)
|
209
|
+
PRODUCT_NAMES.fetch(adapter_name, ACTIVE_RECORD_DEFAULT_PRODUCT_NAME)
|
199
210
|
end
|
200
211
|
|
201
212
|
module InstanceIdentification
|
@@ -221,11 +232,16 @@ module NewRelic
|
|
221
232
|
SLASH = "/".freeze
|
222
233
|
LOCALHOST = "localhost".freeze
|
223
234
|
|
235
|
+
def adapter_from_config(config)
|
236
|
+
bare_name = NewRelic::Agent::Instrumentation::ActiveRecordHelper.bare_adapter_name(config[:adapter])
|
237
|
+
PRODUCT_SYMBOLS[bare_name]
|
238
|
+
end
|
239
|
+
|
224
240
|
def host(config)
|
225
241
|
return UNKNOWN unless config
|
226
242
|
|
227
243
|
configured_value = config[:host]
|
228
|
-
adapter =
|
244
|
+
adapter = adapter_from_config(config)
|
229
245
|
if configured_value.nil? ||
|
230
246
|
postgres_unix_domain_socket_case?(configured_value, adapter)
|
231
247
|
|
@@ -243,7 +259,7 @@ module NewRelic
|
|
243
259
|
def port_path_or_id(config)
|
244
260
|
return UNKNOWN unless config
|
245
261
|
|
246
|
-
adapter =
|
262
|
+
adapter = adapter_from_config(config)
|
247
263
|
if config[:socket]
|
248
264
|
config[:socket].empty? ? UNKNOWN : config[:socket]
|
249
265
|
elsif postgres_unix_domain_socket_case?(config[:host], adapter) || mysql_default_case?(config, adapter)
|
@@ -263,7 +279,7 @@ module NewRelic
|
|
263
279
|
SUPPORTED_ADAPTERS = [:mysql, :postgres].freeze
|
264
280
|
|
265
281
|
def supported_adapter? config
|
266
|
-
config && SUPPORTED_ADAPTERS.include?(
|
282
|
+
config && SUPPORTED_ADAPTERS.include?(adapter_from_config(config))
|
267
283
|
end
|
268
284
|
|
269
285
|
private
|
@@ -91,17 +91,26 @@ module NewRelic
|
|
91
91
|
end
|
92
92
|
|
93
93
|
return unless connection_id = payload[:connection_id]
|
94
|
-
connection = nil
|
95
94
|
|
96
95
|
::ActiveRecord::Base.connection_handler.connection_pool_list.each do |handler|
|
97
|
-
connection = handler.connections.detect
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
96
|
+
connection = handler.connections.detect { |conn| conn.object_id == connection_id }
|
97
|
+
return connection.instance_variable_get(:@config) if connection
|
98
|
+
|
99
|
+
# when using makara, handler.connections will be empty, so use the
|
100
|
+
# spec config instead.
|
101
|
+
# https://github.com/newrelic/newrelic-ruby-agent/issues/507
|
102
|
+
# thank you @lucasklaassen
|
103
|
+
return handler.spec.config if use_spec_config?(handler)
|
102
104
|
end
|
103
105
|
|
104
|
-
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
109
|
+
def use_spec_config?(handler)
|
110
|
+
handler.respond_to?(:spec) &&
|
111
|
+
handler.spec &&
|
112
|
+
handler.spec.config &&
|
113
|
+
handler.spec.config[:adapter].end_with?('makara')
|
105
114
|
end
|
106
115
|
|
107
116
|
def start_segment(config, payload)
|
@@ -165,10 +165,6 @@ module NewRelic
|
|
165
165
|
def add_transaction_tracer(method, options = {})
|
166
166
|
NewRelic::Agent.record_api_supportability_metric(:add_transaction_tracer)
|
167
167
|
|
168
|
-
# The metric path:
|
169
|
-
options[:name] ||= method.to_s
|
170
|
-
|
171
|
-
argument_list = generate_argument_list(options)
|
172
168
|
traced_method, punctuation = parse_punctuation(method)
|
173
169
|
with_method_name, without_method_name = build_method_names(traced_method, punctuation)
|
174
170
|
|
@@ -177,6 +173,12 @@ module NewRelic
|
|
177
173
|
return
|
178
174
|
end
|
179
175
|
|
176
|
+
# The metric path:
|
177
|
+
options[:name] ||= method.to_s
|
178
|
+
|
179
|
+
code_info = NewRelic::Agent::MethodTracerHelpers.code_information(self, method)
|
180
|
+
argument_list = generate_argument_list(options.merge(code_info))
|
181
|
+
|
180
182
|
class_eval <<-EOC
|
181
183
|
def #{with_method_name}(*args, &block)
|
182
184
|
perform_action_with_newrelic_trace(#{argument_list.join(',')}) do
|
@@ -441,6 +443,9 @@ module NewRelic
|
|
441
443
|
txn_options[:apdex_start_time] = queue_start_time
|
442
444
|
txn_options[:ignore_apdex] = ignore_apdex?
|
443
445
|
txn_options[:ignore_enduser] = ignore_enduser?
|
446
|
+
NewRelic::Agent::MethodTracerHelpers::SOURCE_CODE_INFORMATION_PARAMETERS.each do |parameter|
|
447
|
+
txn_options[parameter] = trace_options[parameter]
|
448
|
+
end
|
444
449
|
txn_options
|
445
450
|
end
|
446
451
|
|
@@ -9,7 +9,7 @@ module NewRelic
|
|
9
9
|
module Agent
|
10
10
|
class LogEventAggregator < EventAggregator
|
11
11
|
# Per-message keys
|
12
|
-
LEVEL_KEY = "
|
12
|
+
LEVEL_KEY = "level".freeze
|
13
13
|
MESSAGE_KEY = "message".freeze
|
14
14
|
TIMESTAMP_KEY = "timestamp".freeze
|
15
15
|
PRIORITY_KEY = "priority".freeze
|
@@ -100,7 +100,7 @@ module NewRelic
|
|
100
100
|
module ClassMethods
|
101
101
|
# contains methods refactored out of the #add_method_tracer method
|
102
102
|
module AddMethodTracer
|
103
|
-
ALLOWED_KEYS = [:metric, :push_scope, :code_header, :code_footer].freeze
|
103
|
+
ALLOWED_KEYS = [:metric, :push_scope, :code_header, :code_information, :code_footer].freeze
|
104
104
|
|
105
105
|
DEFAULT_SETTINGS = {:push_scope => true, :metric => true, :code_header => "", :code_footer => ""}.freeze
|
106
106
|
|
@@ -274,7 +274,8 @@ module NewRelic
|
|
274
274
|
|
275
275
|
_nr_define_traced_method(method_name, scoped_metric: scoped_metric, unscoped_metrics: unscoped_metrics,
|
276
276
|
code_header: options[:code_header], code_footer: options[:code_footer],
|
277
|
-
record_metrics: options[:metric], visibility: visibility
|
277
|
+
record_metrics: options[:metric], visibility: visibility,
|
278
|
+
code_information: options[:code_information])
|
278
279
|
|
279
280
|
prepend(_nr_traced_method_module)
|
280
281
|
|
@@ -298,7 +299,8 @@ module NewRelic
|
|
298
299
|
|
299
300
|
def _nr_define_traced_method(method_name, scoped_metric: nil, unscoped_metrics: [],
|
300
301
|
code_header: nil, code_footer: nil, record_metrics: true,
|
301
|
-
visibility: :public)
|
302
|
+
visibility: :public, code_information: {})
|
303
|
+
|
302
304
|
_nr_traced_method_module.module_eval do
|
303
305
|
define_method(method_name) do |*args, &block|
|
304
306
|
return super(*args, &block) unless NewRelic::Agent.tl_is_execution_traced?
|
@@ -325,7 +327,10 @@ module NewRelic
|
|
325
327
|
# If tracing multiple metrics on this method, nest one unscoped trace inside the scoped trace.
|
326
328
|
begin
|
327
329
|
if scoped_metric_eval
|
328
|
-
::NewRelic::Agent::MethodTracer.trace_execution_scoped(scoped_metric_eval,
|
330
|
+
::NewRelic::Agent::MethodTracer.trace_execution_scoped(scoped_metric_eval,
|
331
|
+
metric: record_metrics,
|
332
|
+
internal: true,
|
333
|
+
code_information: code_information) do
|
329
334
|
if unscoped_metrics_eval.empty?
|
330
335
|
super(*args, &block)
|
331
336
|
else
|
@@ -5,6 +5,13 @@
|
|
5
5
|
module NewRelic
|
6
6
|
module Agent
|
7
7
|
module MethodTracerHelpers
|
8
|
+
# These are code level metrics (CLM) attributes. For Ruby, they map like so:
|
9
|
+
# filepath: full path to an .rb file on disk
|
10
|
+
# lineno: the line number a Ruby method is defined on within a given .rb file
|
11
|
+
# function: the name of the Ruby method
|
12
|
+
# namespace: the Ruby class' namespace as a string, ex: 'MyModule::MyClass'
|
13
|
+
SOURCE_CODE_INFORMATION_PARAMETERS = %i[filepath lineno function namespace].freeze
|
14
|
+
SOURCE_CODE_INFORMATION_FAILURE_METRIC = "Supportabiltiy/CodeLevelMetrics/Ruby/Failure".freeze
|
8
15
|
MAX_ALLOWED_METRIC_DURATION = 1_000_000_000 # roughly 31 years
|
9
16
|
|
10
17
|
extend self
|
@@ -26,12 +33,85 @@ module NewRelic
|
|
26
33
|
segment.record_metrics = false
|
27
34
|
end
|
28
35
|
|
36
|
+
unless !options.key?(:code_information) || options[:code_information].nil? || options[:code_information].empty?
|
37
|
+
segment.code_information = options[:code_information]
|
38
|
+
end
|
39
|
+
|
29
40
|
begin
|
30
41
|
Tracer.capture_segment_error(segment) { yield }
|
31
42
|
ensure
|
32
43
|
segment.finish if segment
|
33
44
|
end
|
34
45
|
end
|
46
|
+
|
47
|
+
def code_information(object, method_name)
|
48
|
+
unless NewRelic::Agent.config[:'code_level_metrics.enabled'] && object && method_name
|
49
|
+
return NewRelic::EMPTY_HASH
|
50
|
+
end
|
51
|
+
|
52
|
+
@code_information ||= {}
|
53
|
+
cache_key = "#{object.object_id}#{method_name}"
|
54
|
+
return @code_information[cache_key] if @code_information.key?(cache_key)
|
55
|
+
|
56
|
+
namespace, location, is_class_method = namespace_and_location(object, method_name.to_sym)
|
57
|
+
|
58
|
+
@code_information[cache_key] = {filepath: location.first,
|
59
|
+
lineno: location.last,
|
60
|
+
function: "#{'self.' if is_class_method}#{method_name}",
|
61
|
+
namespace: namespace}
|
62
|
+
rescue => e
|
63
|
+
::NewRelic::Agent.logger.warn("Unable to determine source code info for '#{object}', " \
|
64
|
+
"method '#{method_name}' - #{e.class}: #{e.message}")
|
65
|
+
::NewRelic::Agent.increment_metric(SOURCE_CODE_INFORMATION_FAILURE_METRIC, 1)
|
66
|
+
::NewRelic::EMPTY_HASH
|
67
|
+
end
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# The string representation of a singleton class looks like
|
72
|
+
# '#<Class:MyModule::MyClass>'. Return the 'MyModule::MyClass' part of
|
73
|
+
# that string
|
74
|
+
def klass_name(object)
|
75
|
+
name = Regexp.last_match(1) if object.to_s =~ /^#<Class:(.*)>$/
|
76
|
+
return name if name
|
77
|
+
|
78
|
+
raise "Unable to glean a class name from string '#{object}'" unless name
|
79
|
+
end
|
80
|
+
|
81
|
+
# get at the underlying class from the singleton class
|
82
|
+
#
|
83
|
+
# note: even with the regex hit from klass_name(), `Object.const_get`
|
84
|
+
# is more performant than iterating through `ObjectSpace`
|
85
|
+
def klassify_singleton(object)
|
86
|
+
Object.const_get(klass_name(object))
|
87
|
+
end
|
88
|
+
|
89
|
+
# determine the namespace (class name including all module names in scope)
|
90
|
+
# and source code location (file path and line number) for the given
|
91
|
+
# object and method name
|
92
|
+
#
|
93
|
+
# traced class methods:
|
94
|
+
# * object is a singleton class, `#<Class::MyClass>`
|
95
|
+
# * get at the underlying non-singleton class
|
96
|
+
#
|
97
|
+
# traced instance methods and Rails controller methods:
|
98
|
+
# * object is a class, `MyClass`
|
99
|
+
#
|
100
|
+
# anonymous class based methods (`c = Class.new { def method; end; }`:
|
101
|
+
# * `#name` returns `nil`, so use '(Anonymous)' instead
|
102
|
+
#
|
103
|
+
def namespace_and_location(object, method_name)
|
104
|
+
klass = object.singleton_class? ? klassify_singleton(object) : object
|
105
|
+
name = klass.name || '(Anonymous)'
|
106
|
+
is_class_method = false
|
107
|
+
method = if klass.instance_methods.include?(method_name)
|
108
|
+
klass.instance_method(method_name)
|
109
|
+
else
|
110
|
+
is_class_method = true
|
111
|
+
klass.method(method_name)
|
112
|
+
end
|
113
|
+
[name, method.source_location, is_class_method]
|
114
|
+
end
|
35
115
|
end
|
36
116
|
end
|
37
117
|
end
|
@@ -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
|
|
@@ -198,12 +198,9 @@ module NewRelic
|
|
198
198
|
response
|
199
199
|
end
|
200
200
|
|
201
|
-
# We do not compress if content is smaller than 64kb. There are
|
202
|
-
# problems with bugs in Ruby in some versions that expose us
|
203
|
-
# to a risk of segfaults if we compress aggressively.
|
204
201
|
def compress_request_if_needed(data, endpoint)
|
205
202
|
encoding = 'identity'
|
206
|
-
if data.size
|
203
|
+
if data.size >= MIN_BYTE_SIZE_TO_COMPRESS
|
207
204
|
encoding = Agent.config[:compressed_content_encoding]
|
208
205
|
data = if encoding == 'deflate'
|
209
206
|
Encoders::Compressed::Deflate.encode(data)
|
@@ -429,8 +426,8 @@ module NewRelic
|
|
429
426
|
end
|
430
427
|
serialize_finish_ts = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
431
428
|
|
429
|
+
size = data.size # only the uncompressed size is reported
|
432
430
|
data, encoding = compress_request_if_needed(data, method)
|
433
|
-
size = data.size
|
434
431
|
|
435
432
|
# Preconnect needs to always use the configured collector host, not the redirect host
|
436
433
|
# We reset it here so we are always using the configured collector during our creation of the new connection
|
@@ -165,18 +165,21 @@ module NewRelic
|
|
165
165
|
end
|
166
166
|
end
|
167
167
|
|
168
|
-
def
|
169
|
-
return
|
170
|
-
return
|
171
|
-
|
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
|
-
|
179
|
-
|
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
|
@@ -46,6 +46,10 @@ module NewRelic
|
|
46
46
|
@record_scoped_metric = true
|
47
47
|
@record_on_finish = false
|
48
48
|
@noticed_error = nil
|
49
|
+
@code_filepath = nil
|
50
|
+
@code_function = nil
|
51
|
+
@code_lineno = nil
|
52
|
+
@code_namespace = nil
|
49
53
|
end
|
50
54
|
|
51
55
|
def start
|
@@ -57,6 +61,7 @@ module NewRelic
|
|
57
61
|
def finish
|
58
62
|
@end_time = Process.clock_gettime(Process::CLOCK_REALTIME)
|
59
63
|
@duration = end_time - start_time
|
64
|
+
|
60
65
|
return unless transaction
|
61
66
|
run_complete_callbacks
|
62
67
|
finalize if record_on_finish?
|
@@ -110,6 +115,28 @@ module NewRelic
|
|
110
115
|
@concurrent_children
|
111
116
|
end
|
112
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
|
+
|
113
140
|
INSPECT_IGNORE = [:@transaction, :@transaction_state].freeze
|
114
141
|
|
115
142
|
def inspect
|
@@ -124,7 +124,7 @@ module NewRelic
|
|
124
124
|
txn = Transaction.new(category, options)
|
125
125
|
state.reset(txn)
|
126
126
|
txn.state = state
|
127
|
-
txn.start
|
127
|
+
txn.start(options)
|
128
128
|
txn
|
129
129
|
end
|
130
130
|
|
@@ -414,7 +414,7 @@ module NewRelic
|
|
414
414
|
@frozen_name ? true : false
|
415
415
|
end
|
416
416
|
|
417
|
-
def start
|
417
|
+
def start(options = {})
|
418
418
|
return if !state.is_execution_traced?
|
419
419
|
|
420
420
|
sql_sampler.on_start_transaction(state, request_path)
|
@@ -423,7 +423,7 @@ module NewRelic
|
|
423
423
|
|
424
424
|
ignore! if user_defined_rules_ignore?
|
425
425
|
|
426
|
-
create_initial_segment
|
426
|
+
create_initial_segment(options)
|
427
427
|
Segment.merge_untrusted_agent_attributes \
|
428
428
|
@filtered_params,
|
429
429
|
:'request.parameters',
|
@@ -434,12 +434,12 @@ module NewRelic
|
|
434
434
|
segments.first
|
435
435
|
end
|
436
436
|
|
437
|
-
def create_initial_segment
|
438
|
-
segment = create_segment @default_name
|
437
|
+
def create_initial_segment(options = {})
|
438
|
+
segment = create_segment @default_name, options
|
439
439
|
segment.record_scoped_metric = false
|
440
440
|
end
|
441
441
|
|
442
|
-
def create_segment(name)
|
442
|
+
def create_segment(name, options = {})
|
443
443
|
summary_metrics = nil
|
444
444
|
|
445
445
|
if name.start_with?(MIDDLEWARE_PREFIX)
|
@@ -453,6 +453,10 @@ module NewRelic
|
|
453
453
|
unscoped_metrics: summary_metrics
|
454
454
|
)
|
455
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
|
+
|
456
460
|
segment
|
457
461
|
end
|
458
462
|
|
@@ -467,7 +471,8 @@ module NewRelic
|
|
467
471
|
|
468
472
|
nest_initial_segment if segments.length == 1
|
469
473
|
nested_name = self.class.nested_transaction_name options[:transaction_name]
|
470
|
-
|
474
|
+
|
475
|
+
segment = create_segment nested_name, options
|
471
476
|
set_default_transaction_name(options[:transaction_name], category)
|
472
477
|
segment
|
473
478
|
end
|
data/lib/new_relic/agent.rb
CHANGED
@@ -143,6 +143,8 @@ module NewRelic
|
|
143
143
|
#
|
144
144
|
def add_or_defer_method_tracer(receiver, method_name, metric_name, options)
|
145
145
|
@tracer_lock.synchronize do
|
146
|
+
options[:code_information] = NewRelic::Agent::MethodTracerHelpers.code_information(receiver, method_name)
|
147
|
+
|
146
148
|
if @agent
|
147
149
|
receiver.send(:_nr_add_method_tracer_now, method_name, metric_name, options)
|
148
150
|
else
|
@@ -7,19 +7,18 @@ module NewRelic
|
|
7
7
|
#
|
8
8
|
# This class allows the current transaction to be passed to a Thread so that nested segments can be created from the operations performed within the Thread's block.
|
9
9
|
# To have the New Relic Ruby agent automatically trace all of your applications threads,
|
10
|
-
# enable the
|
10
|
+
# enable the +instrumentation.thread.tracing+ configuration option in your newrelic.yml.
|
11
11
|
#
|
12
|
-
# Note: disabling the configuration option
|
12
|
+
# Note: disabling the configuration option +instrumentation.thread+ while using this class can cause incorrectly nested spans.
|
13
13
|
#
|
14
14
|
# @api public
|
15
15
|
class TracedThread < Thread
|
16
16
|
#
|
17
17
|
# Creates a new Thread whose work will be traced by New Relic.
|
18
18
|
# Use this class as a replacement for the native Thread class.
|
19
|
-
# Example: Instead of using
|
20
|
-
#
|
21
|
-
#
|
22
|
-
# ```
|
19
|
+
# Example: Instead of using +Thread.new+, use:
|
20
|
+
#
|
21
|
+
# NewRelic::TracedThread.new { execute_some_code }
|
23
22
|
#
|
24
23
|
# @api public
|
25
24
|
def initialize(*args, &block)
|
data/lib/new_relic/version.rb
CHANGED
data/newrelic.yml
CHANGED
@@ -42,6 +42,9 @@ common: &default_settings
|
|
42
42
|
# If `true`, the agent decorates logs with metadata to link to entities, hosts, traces, and spans.
|
43
43
|
# application_logging.local_decorating.enabled: false
|
44
44
|
|
45
|
+
# If `true`, the agent will report source code level metrics for traced methods
|
46
|
+
# code_level_metrics.enabled: false
|
47
|
+
|
45
48
|
# If true, enables transaction event sampling.
|
46
49
|
# transaction_events.enabled: true
|
47
50
|
|
data/newrelic_rpm.gemspec
CHANGED
@@ -47,10 +47,6 @@ https://github.com/newrelic/newrelic-ruby-agent/
|
|
47
47
|
s.require_paths = ["lib"]
|
48
48
|
s.rubygems_version = Gem::VERSION
|
49
49
|
s.summary = "New Relic Ruby Agent"
|
50
|
-
s.post_install_message = 'Thanks for installing the latest version of newrelic_rpm. '\
|
51
|
-
'This version turns on application log forwarding by default. If you want to turn ' \
|
52
|
-
'off this feature, set `application_logging.forwarding.enabled: false`. ' \
|
53
|
-
'For more information, visit: https://docs.newrelic.com/docs/logs/logs-context/configure-logs-context-ruby'
|
54
50
|
s.add_development_dependency 'rake', '12.3.3'
|
55
51
|
s.add_development_dependency 'rb-inotify', '0.9.10' # locked to support < Ruby 2.3 (and listen 3.0.8)
|
56
52
|
s.add_development_dependency 'listen', '3.0.8' # locked to support < Ruby 2.3
|
@@ -62,7 +58,6 @@ https://github.com/newrelic/newrelic-ruby-agent/
|
|
62
58
|
s.add_development_dependency 'pry-stack_explorer', '~> 0.4.9'
|
63
59
|
s.add_development_dependency 'guard', '~> 2.16.0'
|
64
60
|
s.add_development_dependency 'guard-minitest', '~> 2.4.0'
|
65
|
-
s.add_development_dependency 'hometown', '~> 0.2.5'
|
66
61
|
s.add_development_dependency 'bundler'
|
67
62
|
s.add_development_dependency 'rubocop'
|
68
63
|
s.add_development_dependency 'rubocop-performance'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: newrelic_rpm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 8.
|
4
|
+
version: 8.8.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tanna McClure
|
@@ -11,7 +11,7 @@ authors:
|
|
11
11
|
autorequire:
|
12
12
|
bindir: bin
|
13
13
|
cert_chain: []
|
14
|
-
date: 2022-
|
14
|
+
date: 2022-06-02 00:00:00.000000000 Z
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
17
17
|
name: rake
|
@@ -167,20 +167,6 @@ dependencies:
|
|
167
167
|
- - "~>"
|
168
168
|
- !ruby/object:Gem::Version
|
169
169
|
version: 2.4.0
|
170
|
-
- !ruby/object:Gem::Dependency
|
171
|
-
name: hometown
|
172
|
-
requirement: !ruby/object:Gem::Requirement
|
173
|
-
requirements:
|
174
|
-
- - "~>"
|
175
|
-
- !ruby/object:Gem::Version
|
176
|
-
version: 0.2.5
|
177
|
-
type: :development
|
178
|
-
prerelease: false
|
179
|
-
version_requirements: !ruby/object:Gem::Requirement
|
180
|
-
requirements:
|
181
|
-
- - "~>"
|
182
|
-
- !ruby/object:Gem::Version
|
183
|
-
version: 0.2.5
|
184
170
|
- !ruby/object:Gem::Dependency
|
185
171
|
name: bundler
|
186
172
|
requirement: !ruby/object:Gem::Requirement
|
@@ -618,10 +604,7 @@ metadata:
|
|
618
604
|
documentation_uri: https://docs.newrelic.com/docs/agents/ruby-agent
|
619
605
|
source_code_uri: https://github.com/newrelic/newrelic-ruby-agent
|
620
606
|
homepage_uri: https://newrelic.com/ruby
|
621
|
-
post_install_message:
|
622
|
-
version turns on application log forwarding by default. If you want to turn off
|
623
|
-
this feature, set `application_logging.forwarding.enabled: false`. For more information,
|
624
|
-
visit: https://docs.newrelic.com/docs/logs/logs-context/configure-logs-context-ruby'
|
607
|
+
post_install_message:
|
625
608
|
rdoc_options: []
|
626
609
|
require_paths:
|
627
610
|
- lib
|