Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update Redis instrumentation to support v5 #1611

Merged
merged 7 commits into from
Nov 11, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions lib/new_relic/agent/instrumentation/redis.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative 'redis/instrumentation'
require_relative 'redis/chain'
require_relative 'redis/prepend'
require_relative 'redis/middleware'

DependencyDetection.defer do
# Why not :redis? newrelic-redis used that name, so avoid conflicting
Expand All @@ -29,6 +30,10 @@

executes do
NewRelic::Agent.logger.info('Installing Redis Instrumentation')
if NewRelic::Agent::Instrumentation::Redis::HAS_REDIS_CLIENT
::RedisClient.register(NewRelic::Agent::Instrumentation::RedisClient::Middleware)
end

if use_prepend?
prepend_instrument ::Redis::Client, NewRelic::Agent::Instrumentation::Redis::Prepend
else
Expand Down
24 changes: 18 additions & 6 deletions lib/new_relic/agent/instrumentation/redis/chain.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,28 @@ def self.instrument!
::Redis::Client.class_eval do
kaylareopelle marked this conversation as resolved.
Show resolved Hide resolved
include NewRelic::Agent::Instrumentation::Redis

alias_method(:call_without_new_relic, :call)
if method_defined?(:call_v)
alias_method(:call_v_without_new_relic, :call_v)

def call(*args, &block)
call_with_tracing(args[0]) { call_without_new_relic(*args, &block) }
def call_v(*args, &block)
call_with_tracing(args[0]) { call_v_without_new_relic(*args, &block) }
end
end

alias_method(:call_pipeline_without_new_relic, :call_pipeline)
if method_defined?(:call)
alias_method(:call_without_new_relic, :call)

def call_pipeline(*args, &block)
call_pipeline_with_tracing(args[0]) { call_pipeline_without_new_relic(*args, &block) }
def call(*args, &block)
call_with_tracing(args[0]) { call_without_new_relic(*args, &block) }
end
end

if method_defined?(:call_pipeline)
alias_method(:call_pipeline_without_new_relic, :call_pipeline)

def call_pipeline(*args, &block)
call_pipeline_with_tracing(args[0]) { call_pipeline_without_new_relic(*args, &block) }
end
end

alias_method(:connect_without_new_relic, :connect)
Expand Down
36 changes: 26 additions & 10 deletions lib/new_relic/agent/instrumentation/redis/instrumentation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,38 +6,50 @@ module NewRelic::Agent::Instrumentation
module Redis
PRODUCT_NAME = 'Redis'
CONNECT = 'connect'
UNKNOWN = "unknown"
LOCALHOST = "localhost"
UNKNOWN = 'unknown'
LOCALHOST = 'localhost'
MULTI_OPERATION = 'multi'
PIPELINE_OPERATION = 'pipeline'
HAS_REDIS_CLIENT = Gem::Version.new(::Redis::VERSION) >= Gem::Version.new('5.0.0') && !!defined?(::RedisClient)

def connect_with_tracing
with_tracing(CONNECT, database: db) { yield }
fallwith marked this conversation as resolved.
Show resolved Hide resolved
end

def call_with_tracing(command, &block)
operation = command[0]
statement = ::NewRelic::Agent::Datastores::Redis.format_command(command)

with_tracing(operation, statement) { yield }
with_tracing(operation, statement: statement, database: db) { yield }
end

# Used for Redis 4.x and 3.x
def call_pipeline_with_tracing(pipeline)
operation = pipeline.is_a?(::Redis::Pipeline::Multi) ? MULTI_OPERATION : PIPELINE_OPERATION
statement = ::NewRelic::Agent::Datastores::Redis.format_pipeline_commands(pipeline.commands)

with_tracing(operation, statement) { yield }
with_tracing(operation, statement: statement, database: db) { yield }
end

def connect_with_tracing
with_tracing(CONNECT) { yield }
# Used for Redis 5.x+
def call_pipelined_with_tracing(pipeline)
operation = pipeline.flatten.include?('MULTI') ? MULTI_OPERATION : PIPELINE_OPERATION
statement = ::NewRelic::Agent::Datastores::Redis.format_pipeline_commands(pipeline)

# call_pipelined isn't invoked on the client object, so use client.db to
# access the client instance var on self
with_tracing(operation, statement: statement, database: client.db) { yield }
end

private

def with_tracing(operation, statement = nil)
def with_tracing(operation, statement: nil, database: nil)
segment = NewRelic::Agent::Tracer.start_datastore_segment(
product: PRODUCT_NAME,
operation: operation,
host: _nr_hostname,
port_path_or_id: _nr_port_path_or_id,
database_name: db
database_name: database
)
begin
segment.notice_nosql_statement(statement) if statement
Expand All @@ -48,17 +60,21 @@ def with_tracing(operation, statement = nil)
end

def _nr_hostname
self.path ? LOCALHOST : self.host
_nr_client.path ? LOCALHOST : _nr_client.host
rescue => e
NewRelic::Agent.logger.debug("Failed to retrieve Redis host: #{e}")
UNKNOWN
end

def _nr_port_path_or_id
self.path || self.port
_nr_client.path || _nr_client.port
rescue => e
NewRelic::Agent.logger.debug("Failed to retrieve Redis port_path_or_id: #{e}")
UNKNOWN
end

def _nr_client
@nr_client ||= self.is_a?(::Redis::Client) ? self : client
end
end
end
16 changes: 16 additions & 0 deletions lib/new_relic/agent/instrumentation/redis/middleware.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# This file is distributed under New Relic's license terms.
# See https://github.com/newrelic/newrelic-ruby-agent/blob/main/LICENSE for complete details.
# frozen_string_literal: true

module NewRelic::Agent::Instrumentation
module RedisClient
module Middleware
# This module is used to instrument Redis 5.x+
fallwith marked this conversation as resolved.
Show resolved Hide resolved
include NewRelic::Agent::Instrumentation::Redis

def call_pipelined(*args, &block)
call_pipelined_with_tracing(args[0]) { super }
end
end
end
end
6 changes: 6 additions & 0 deletions lib/new_relic/agent/instrumentation/redis/prepend.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ module Redis
module Prepend
include NewRelic::Agent::Instrumentation::Redis

# Defined in version 5.x+
def call_v(*args, &block)
call_with_tracing(args[0]) { super }
end

# Defined in version 4.x, 3.x
def call(*args, &block)
call_with_tracing(args[0]) { super }
end
Expand Down
6 changes: 2 additions & 4 deletions test/multiverse/suites/redis/Envfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,8 @@
instrumentation_methods :chain, :prepend

REDIS_VERSIONS = [
# TODO: add support for redis v5+, re-enable nil
# https://github.com/newrelic/newrelic-ruby-agent/issues/1361
# [nil, 2.4],
['4.7.1', 2.4],
[nil, 2.5],
['4.8.0', 2.4],
['3.3.0']
]

Expand Down
Loading