Skip to content

Commit c3127c8

Browse files
committed
add metrics tests
1 parent 97ee174 commit c3127c8

File tree

5 files changed

+139
-17
lines changed

5 files changed

+139
-17
lines changed

instrumentation/redis/Gemfile

+1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ gemspec
1010

1111
group :test do
1212
gem 'opentelemetry-instrumentation-base', path: '../base'
13+
gem 'opentelemetry-metrics-test-helpers', path: '../../helpers/metrics-test-helpers', require: false
1314
end
1415

1516
gem 'pry-byebug'

instrumentation/redis/lib/opentelemetry/instrumentation/redis/instrumentation.rb

+30-6
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ class Instrumentation < OpenTelemetry::Instrumentation::Base
2626

2727
# https://opentelemetry.io/docs/specs/semconv/database/database-metrics/#metric-dbclientoperationduration
2828
histogram 'db.client.operation.duration',
29-
attributes: { 'db.system.name' => 'redis' },
29+
attributes: { 'db.system' => 'redis' },
3030
unit: 's',
31-
boundaries: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10]
31+
explicit_bucket_boundaries: [0.001, 0.005, 0.01, 0.05, 0.1, 0.5, 1, 5, 10]
3232

3333
def client_operation_duration_histogram
3434
histogram('db.client.operation.duration')
@@ -37,18 +37,42 @@ def client_operation_duration_histogram
3737
private
3838

3939
def require_dependencies
40-
require_relative 'patches/redis_v4_client' if defined?(::Redis) && ::Redis::VERSION < '5'
40+
require_redis_client_dependencies
41+
require_redis_v4_dependencies
42+
end
43+
44+
def require_redis_v4_dependencies
45+
return unless defined?(::Redis) && Gem::Version.new(Redis::VERSION) < Gem::Version.new('5.0.0')
46+
47+
require_relative 'patches/redis_v4_client'
48+
require_relative 'patches/redis_v4_client_metrics'
49+
end
4150

51+
def require_redis_client_dependencies
4252
return unless defined?(::RedisClient)
4353

4454
require_relative 'middlewares/redis_client'
4555
require_relative 'middlewares/redis_client_metrics'
4656
end
4757

4858
def patch_client
49-
::RedisClient.register(Middlewares::RedisClientInstrumentation) if defined?(::RedisClient)
50-
::RedisClient.register(Middlewares::RedisClientMetrics) if defined?(::RedisClient)
51-
::Redis::Client.prepend(Patches::RedisV4Client) if defined?(::Redis) && ::Redis::VERSION < '5'
59+
patch_redis_v4_client
60+
patch_redis_client
61+
end
62+
63+
def patch_redis_v4_client
64+
return unless defined?(::Redis) && Gem::Version.new(Redis::VERSION) < Gem::Version.new('5.0.0')
65+
66+
::Redis::Client.prepend(Patches::RedisV4Client)
67+
::Redis::Client.prepend(Patches::RedisV4ClientMetrics) if metrics_defined?
68+
end
69+
70+
# Applies to redis-client or redis >= 5
71+
def patch_redis_client
72+
return unless defined?(::RedisClient)
73+
74+
::RedisClient.register(Middlewares::RedisClientInstrumentation)
75+
::RedisClient.register(Middlewares::RedisClientMetrics) if metrics_defined?
5276
end
5377
end
5478
end

instrumentation/redis/lib/opentelemetry/instrumentation/redis/middlewares/redis_client_metrics.rb

+12-10
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,32 @@ module RedisClientMetrics
1313
def call(command, redis_config)
1414
return super unless (histogram = instrumentation.client_operation_duration_histogram)
1515

16-
timed(histogram, command.first, redis_config) do
16+
attributes = metric_attributes(redis_config, command.first)
17+
otel_timed(histogram, attributes) do
1718
super
1819
end
1920
end
2021

2122
def call_pipelined(commands, redis_config)
2223
return super unless (histogram = instrumentation.client_operation_duration_histogram)
2324

24-
timed(histogram, 'PIPELINE', redis_config) do
25+
attributes = metric_attributes(redis_config, 'PIPELINED')
26+
otel_timed(histogram, attributes) do
2527
super
2628
end
2729
end
2830

2931
private
3032

31-
def timed(histogram, operation_name, redis_config)
33+
def otel_timed(histogram, attributes)
3234
t0 = monotonic_now
33-
34-
yield.tap do
35-
duration = monotonic_now - t0
36-
37-
histogram.record(duration, attributes: metric_attributes(redis_config, operation_name))
38-
end
35+
yield
36+
rescue StandardError => e
37+
attributes['error.type'] = e.class.to_s
38+
raise
39+
ensure
40+
duration = monotonic_now - t0
41+
histogram.record(duration, attributes: attributes)
3942
end
4043

4144
def monotonic_now
@@ -44,7 +47,6 @@ def monotonic_now
4447

4548
def metric_attributes(redis_config, operation_name)
4649
attributes = {
47-
'db.system' => 'redis',
4850
'db.operation.name' => operation_name,
4951
'net.peer.name' => redis_config.host,
5052
'net.peer.port' => redis_config.port

instrumentation/redis/test/opentelemetry/instrumentation/redis/patches/client_test.rb

+94-1
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,10 @@ def redis_gte_5?
4141
redis_version_major&.>=(5)
4242
end
4343

44+
let(:config) { { db_statement: :include } }
45+
4446
before do
4547
# ensure obfuscation is off if it was previously set in a different test
46-
config = { db_statement: :include }
4748
instrumentation.install(config)
4849
exporter.reset
4950
end
@@ -389,4 +390,96 @@ def redis_gte_5?
389390
end
390391
end
391392
end
393+
394+
if defined?(OpenTelemetry::Metrics)
395+
describe 'metrics not enabled' do
396+
it 'will not be enabled' do
397+
assert(instrumentation.metrics_defined?)
398+
refute(instrumentation.metrics_enabled?)
399+
end
400+
end
401+
402+
describe 'metrics enabled' do
403+
let(:config) { { db_statement: :include, metrics: true } }
404+
let(:metric_snapshot) do
405+
metrics_exporter.pull
406+
metrics_exporter.metric_snapshots.last
407+
end
408+
409+
it 'will be enabled' do
410+
assert(instrumentation.metrics_defined?)
411+
assert(instrumentation.metrics_enabled?)
412+
end
413+
414+
it 'works', with_metrics_sdk: true do
415+
skip if redis_gte_5?
416+
417+
redis = redis_with_auth
418+
key = SecureRandom.hex
419+
10.times { redis.incr(key) }
420+
redis.expire(key, 1)
421+
422+
_(metric_snapshot.data_points.length).must_equal(3)
423+
424+
metric_snapshot.data_points.each do |data_point|
425+
_(data_point.attributes['db.system']).must_equal('redis')
426+
end
427+
428+
by_operation_name = metric_snapshot.data_points.each_with_object({}) { |d, res| res[d.attributes['db.operation.name']] = d }
429+
_(by_operation_name.keys.sort).must_equal(%w[auth expire incr])
430+
431+
_(by_operation_name['auth'].count).must_equal(1)
432+
_(by_operation_name['incr'].count).must_equal(10)
433+
_(by_operation_name['expire'].count).must_equal(1)
434+
end
435+
436+
it 'works v5', with_metrics_sdk: true do
437+
skip unless redis_gte_5?
438+
439+
redis = redis_with_auth
440+
key = SecureRandom.hex
441+
10.times { redis.incr(key) }
442+
redis.expire(key, 1)
443+
444+
_(metric_snapshot.data_points.length).must_equal(3)
445+
446+
metric_snapshot.data_points.each do |data_point|
447+
_(data_point.attributes['db.system']).must_equal('redis')
448+
end
449+
450+
by_operation_name = metric_snapshot.data_points.each_with_object({}) { |d, res| res[d.attributes['db.operation.name']] = d }
451+
_(by_operation_name.keys.sort).must_equal(%w[PIPELINED expire incr])
452+
453+
_(by_operation_name['PIPELINED'].count).must_equal(1)
454+
_(by_operation_name['incr'].count).must_equal(10)
455+
_(by_operation_name['expire'].count).must_equal(1)
456+
end
457+
458+
it 'adds errors', with_metrics_sdk: true do
459+
skip if redis_gte_5?
460+
461+
redis = redis_with_auth
462+
key = SecureRandom.hex
463+
redis.setex(key, 100, 'string_value')
464+
expect { redis.incr(key) }.must_raise(Redis::CommandError)
465+
466+
last_data_point = metric_snapshot.data_points.last
467+
_(last_data_point.attributes['db.operation.name']).must_equal('incr')
468+
_(last_data_point.attributes['error.type']).must_equal('RedisClient::CommandError')
469+
end
470+
471+
it 'adds errors v5', with_metrics_sdk: true do
472+
skip unless redis_gte_5?
473+
474+
redis = redis_with_auth
475+
key = SecureRandom.hex
476+
redis.setex(key, 100, 'string_value')
477+
expect { redis.incr(key) }.must_raise(Redis::CommandError)
478+
479+
last_data_point = metric_snapshot.data_points.last
480+
_(last_data_point.attributes['db.operation.name']).must_equal('incr')
481+
_(last_data_point.attributes['error.type']).must_equal('RedisClient::CommandError')
482+
end
483+
end
484+
end
392485
end unless ENV['OMIT_SERVICES']

instrumentation/redis/test/test_helper.rb

+2
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,5 @@
2121
c.logger = Logger.new($stderr, level: ENV.fetch('OTEL_LOG_LEVEL', 'fatal').to_sym)
2222
c.add_span_processor span_processor
2323
end
24+
25+
require 'opentelemetry-metrics-test-helpers'

0 commit comments

Comments
 (0)