Skip to content

Commit 97d31b6

Browse files
committed
Draft: Initial get_connection wrapper for redis metrics.
1 parent d75194a commit 97d31b6

File tree

3 files changed

+82
-2
lines changed

3 files changed

+82
-2
lines changed

instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/__init__.py

+47-2
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@ def response_hook(span, instance, response):
9191
"""
9292
import typing
9393
from typing import Any, Collection
94-
9594
import redis
9695
from wrapt import wrap_function_wrapper
9796

@@ -106,6 +105,7 @@ def response_hook(span, instance, response):
106105
from opentelemetry.instrumentation.utils import unwrap
107106
from opentelemetry.semconv.trace import SpanAttributes
108107
from opentelemetry.trace import Span
108+
from opentelemetry.metrics import UpDownCounter, get_meter
109109

110110
_DEFAULT_SERVICE = "redis"
111111

@@ -119,6 +119,7 @@ def response_hook(span, instance, response):
119119
]
120120

121121
_REDIS_ASYNCIO_VERSION = (4, 2, 0)
122+
122123
if redis.VERSION >= _REDIS_ASYNCIO_VERSION:
123124
import redis.asyncio
124125

@@ -137,6 +138,7 @@ def _set_connection_attributes(span, conn):
137138

138139
def _instrument(
139140
tracer,
141+
connections_usage: UpDownCounter,
140142
request_hook: _RequestHookT = None,
141143
response_hook: _ResponseHookT = None,
142144
):
@@ -147,6 +149,7 @@ def _traced_execute_command(func, instance, args, kwargs):
147149
name = args[0]
148150
else:
149151
name = instance.connection_pool.connection_kwargs.get("db", 0)
152+
150153
with tracer.start_as_current_span(
151154
name, kind=trace.SpanKind.CLIENT
152155
) as span:
@@ -156,7 +159,19 @@ def _traced_execute_command(func, instance, args, kwargs):
156159
span.set_attribute("db.redis.args_length", len(args))
157160
if callable(request_hook):
158161
request_hook(span, instance, args, kwargs)
162+
connections_usage.add(
163+
1,
164+
{
165+
"db.client.connection.usage.state": "used",
166+
"db.client.connection.usage.name": instance.connection_pool.pid,
167+
})
159168
response = func(*args, **kwargs)
169+
connections_usage.add(
170+
1,
171+
{
172+
"db.client.connections.usage.state": "idle",
173+
"db.client.connections.usage.name": instance.connection_pool.pid,
174+
})
160175
if callable(response_hook):
161176
response_hook(span, instance, response)
162177
return response
@@ -200,13 +215,26 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
200215
response_hook(span, instance, response)
201216
return response
202217

218+
def _traced_get_connection(func, connection_pool, command_name, *keys, **options):
219+
response = func(command_name, *keys, **options)
220+
connections_usage.add(
221+
1,
222+
{
223+
"db.client.connections.usage.state": "used",
224+
"db.client.connections.usage.name": connection_pool.pid,
225+
})
226+
return response
227+
228+
203229
pipeline_class = (
204230
"BasePipeline" if redis.VERSION < (3, 0, 0) else "Pipeline"
205231
)
206232
redis_class = "StrictRedis" if redis.VERSION < (3, 0, 0) else "Redis"
207233

208234
wrap_function_wrapper(
209-
"redis", f"{redis_class}.execute_command", _traced_execute_command
235+
"redis",
236+
f"{redis_class}.execute_command",
237+
_traced_execute_command
210238
)
211239
wrap_function_wrapper(
212240
"redis.client",
@@ -229,6 +257,11 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
229257
"ClusterPipeline.execute",
230258
_traced_execute_pipeline,
231259
)
260+
# wrap_function_wrapper(
261+
# "redis",
262+
# "ConnectionPool.get_connection",
263+
# _traced_get_connection
264+
# )
232265
if redis.VERSION >= _REDIS_ASYNCIO_VERSION:
233266
wrap_function_wrapper(
234267
"redis.asyncio",
@@ -278,8 +311,20 @@ def _instrument(self, **kwargs):
278311
tracer = trace.get_tracer(
279312
__name__, __version__, tracer_provider=tracer_provider
280313
)
314+
meter_provider = kwargs.get("meter_provider")
315+
meter = get_meter(
316+
__name__,
317+
__version__,
318+
meter_provider
319+
)
320+
connections_usage = meter.create_up_down_counter(
321+
name="db.client.connections.usage",
322+
unit="1",
323+
description="The number of connections that are currently in state described"
324+
)
281325
_instrument(
282326
tracer,
327+
connections_usage,
283328
request_hook=kwargs.get("request_hook"),
284329
response_hook=kwargs.get("response_hook"),
285330
)

instrumentation/opentelemetry-instrumentation-redis/src/opentelemetry/instrumentation/redis/package.py

+1
Original file line numberDiff line numberDiff line change
@@ -14,3 +14,4 @@
1414

1515

1616
_instruments = ("redis >= 2.6",)
17+
_supports_metrics = True

instrumentation/opentelemetry-instrumentation-redis/tests/test_redis.py

+34
Original file line numberDiff line numberDiff line change
@@ -146,3 +146,37 @@ def request_hook(span, conn, args, kwargs):
146146

147147
span = spans[0]
148148
self.assertEqual(span.attributes.get(custom_attribute_name), "GET")
149+
150+
151+
class TestRedisIntegrationMetric(TestBase):
152+
153+
def setUp(self):
154+
super().setUp()
155+
RedisInstrumentor().instrument(meter_provider=self.meter_provider)
156+
157+
def tearDown(self):
158+
super().tearDown()
159+
RedisInstrumentor().uninstrument()
160+
161+
@staticmethod
162+
def redis_get():
163+
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
164+
redis_client = redis.Redis(connection_pool=pool)
165+
redis_client.get('foo')
166+
return pool.pid
167+
168+
def test_multiple_connections_metric_success_redis(self):
169+
pid = self.redis_get()
170+
expected_metric_values = {
171+
"db.client.connection.usage.state": "used",
172+
"db.client.connection.usage.name": pid,
173+
}
174+
for (
175+
resource_metrics
176+
) in self.memory_metrics_reader.get_metrics_data().resource_metrics:
177+
for scope_metrics in resource_metrics.scope_metrics:
178+
for metric in scope_metrics.metrics:
179+
for data_point in metric.data.data_points:
180+
self.assertDictEqual(
181+
expected_metric_values, dict(data_point.attributes)
182+
)

0 commit comments

Comments
 (0)