Skip to content

Commit b6b7a08

Browse files
committed
Merge remote-tracking branch 'origin/develop'
# Conflicts: # Gemfile.lock
2 parents d5a0625 + 4b26187 commit b6b7a08

4 files changed

Lines changed: 119 additions & 0 deletions

File tree

app.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,9 @@
183183
# Initialize the app
184184
require_relative 'init'
185185

186+
# Custom method tracers (must load after init so traced methods exist)
187+
require_relative 'config/newrelic_method_tracers'
188+
186189
# Enter console mode
187190
if settings.environment == :console
188191
require 'rack/test'

config/newrelic_method_tracers.rb

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Custom New Relic method tracers for hot request paths.
2+
#
3+
# High-traffic transactions (GET /search, GET .../classes/{cls}/tree)
4+
# spend most of their time in application code that the agent reports as
5+
# a single opaque "Sinatra::Application#GET (unknown)" segment. These
6+
# tracers split that block into named Custom/* child segments so
7+
# transaction traces and the breakdown table show where the time goes.
8+
#
9+
# To instrument another endpoint, add its module/class and methods to the
10+
# registry below; test/helpers/test_newrelic_method_tracers.rb consumes
11+
# the same registry, so new entries are covered automatically.
12+
#
13+
# Must be loaded after init.rb so all traced methods are already defined.
14+
# When the agent is disabled (development/test), the tracers are no-ops.
15+
#
16+
# Kill switch: set NEWRELIC_CUSTOM_TRACERS=off (and restart) to skip
17+
# registration entirely, e.g. to rule instrumentation out while
18+
# debugging. Requires a process restart to take effect either way.
19+
module NewRelicMethodTracers
20+
# Instance methods, keyed by module/class name. Methods inherited from
21+
# concerns or mixins (e.g. the tree methods, defined in an
22+
# ontologies_linked_data concern) can be traced here without touching
23+
# the defining repo.
24+
INSTANCE_METHODS = {
25+
# GET /search pipeline
26+
'Sinatra::Helpers::SearchHelper' => %i[process_search get_term_search_query
27+
add_matched_fields filter_attrs_by_language],
28+
# App-wide: ontology access list load + response serialization entry
29+
'Sinatra::Helpers::ApplicationHelper' => %i[restricted_ontologies reply],
30+
# GET /ontologies/{ontology}/classes/{cls}/tree pipeline
31+
'LinkedData::Models::Class' => %i[tree tree_sorted paths_to_root]
32+
}.freeze
33+
34+
# Class (singleton) methods, keyed by class name.
35+
CLASS_METHODS = {
36+
# Hypermedia links + JSON generation for every API response
37+
'LinkedData::Serializer' => %i[build_response serialize],
38+
# Solr query execution incl. Ruby-side response parsing (the HTTP
39+
# call itself already appears as an External segment)
40+
'LinkedData::Models::Class' => %i[search]
41+
}.freeze
42+
43+
def self.register
44+
INSTANCE_METHODS.each do |const_name, methods|
45+
install(Object.const_get(const_name), const_name, methods)
46+
end
47+
48+
CLASS_METHODS.each do |const_name, methods|
49+
install(Object.const_get(const_name).singleton_class, const_name, methods)
50+
end
51+
end
52+
53+
def self.install(target, const_name, methods)
54+
target.include(::NewRelic::Agent::MethodTracer)
55+
56+
methods.each do |method_name|
57+
target.send(:add_method_tracer, method_name, "Custom/#{const_name}/#{method_name}")
58+
end
59+
end
60+
end
61+
62+
unless ENV['NEWRELIC_CUSTOM_TRACERS'] == 'off'
63+
require 'new_relic/agent/method_tracer'
64+
NewRelicMethodTracers.register
65+
end

helpers/search_helper.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -560,6 +560,12 @@ def process_search(params = nil)
560560
params.delete('query')
561561
text = params["q"]
562562

563+
# The agent strips query strings from request.uri, so record whether
564+
# this search is ontology-scoped as a queryable transaction attribute
565+
if defined?(::NewRelic::Agent)
566+
::NewRelic::Agent.add_custom_attributes(search_scoped: !params[ONTOLOGIES_PARAM].to_s.empty?)
567+
end
568+
563569
query = get_term_search_query(text, params)
564570
# puts "Edismax query: #{query}, params: #{params}"
565571
set_page_params(params)
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
require_relative '../test_case_helpers'
2+
3+
# Guards the custom New Relic instrumentation registered by
4+
# config/newrelic_method_tracers.rb. If a traced method is renamed or
5+
# moved, add_method_tracer logs a warning and silently no-ops, so without
6+
# these assertions the instrumentation could vanish unnoticed while the
7+
# rest of the suite stays green.
8+
#
9+
# The tests iterate the registry itself, so entries added to
10+
# NewRelicMethodTracers::INSTANCE_METHODS / CLASS_METHODS are covered
11+
# automatically.
12+
class TestNewRelicMethodTracers < TestCaseHelpers
13+
14+
def test_instance_method_tracers_registered
15+
NewRelicMethodTracers::INSTANCE_METHODS.each do |const_name, methods|
16+
mod = Object.const_get(const_name)
17+
methods.each do |method_name|
18+
assert_wrapped_by_newrelic(mod.instance_method(method_name),
19+
"#{const_name}##{method_name}")
20+
end
21+
end
22+
end
23+
24+
def test_class_method_tracers_registered
25+
NewRelicMethodTracers::CLASS_METHODS.each do |const_name, methods|
26+
singleton = Object.const_get(const_name).singleton_class
27+
methods.each do |method_name|
28+
assert_wrapped_by_newrelic(singleton.instance_method(method_name),
29+
"#{const_name}.#{method_name}")
30+
end
31+
end
32+
end
33+
34+
private
35+
36+
# The tracer wrapper is defined inside the newrelic_rpm gem, so the
37+
# wrapped method's source_location must point there. This distinguishes
38+
# a genuine tracer from the untraced original (defined in this repo or
39+
# its gems) and from any other module that happens to prepend.
40+
def assert_wrapped_by_newrelic(unbound_method, label)
41+
source_file = unbound_method.source_location&.first.to_s
42+
assert_match(/newrelic/, source_file,
43+
"#{label} is not wrapped by a newrelic_rpm method tracer (source: #{source_file})")
44+
end
45+
end

0 commit comments

Comments
 (0)