Skip to content

Commit ec35a24

Browse files
committed
Draft: Initial get_connection wrapper for redis metrics.
1 parent 9e2dbec commit ec35a24

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

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

Lines changed: 40 additions & 2 deletions
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:
@@ -200,13 +203,31 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
200203
response_hook(span, instance, response)
201204
return response
202205

206+
def _traced_get_connection(func, connection_pool, command_name, *keys, **options):
207+
span_name = "random-test"
208+
with tracer.start_as_current_span(
209+
span_name, kind=trace.SpanKind.CLIENT
210+
) as span:
211+
response = func(command_name, *keys, **options)
212+
metric_labels = {
213+
SpanAttributes.DB_CLIENT_CONNECTIONS_USAGE: connection_pool._created_connections,
214+
}
215+
connections_usage.add(1, metric_labels)
216+
metric_labels[
217+
SpanAttributes.DB_CLIENT_CONNECTIONS_USAGE
218+
] = connection_pool._created_connections
219+
return response
220+
221+
203222
pipeline_class = (
204223
"BasePipeline" if redis.VERSION < (3, 0, 0) else "Pipeline"
205224
)
206225
redis_class = "StrictRedis" if redis.VERSION < (3, 0, 0) else "Redis"
207226

208227
wrap_function_wrapper(
209-
"redis", f"{redis_class}.execute_command", _traced_execute_command
228+
"redis",
229+
f"{redis_class}.execute_command",
230+
_traced_execute_command
210231
)
211232
wrap_function_wrapper(
212233
"redis.client",
@@ -229,6 +250,11 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
229250
"ClusterPipeline.execute",
230251
_traced_execute_pipeline,
231252
)
253+
wrap_function_wrapper(
254+
"redis",
255+
"ConnectionPool.get_connection",
256+
_traced_get_connection
257+
)
232258
if redis.VERSION >= _REDIS_ASYNCIO_VERSION:
233259
wrap_function_wrapper(
234260
"redis.asyncio",
@@ -278,8 +304,20 @@ def _instrument(self, **kwargs):
278304
tracer = trace.get_tracer(
279305
__name__, __version__, tracer_provider=tracer_provider
280306
)
307+
meter_provider = kwargs.get("meter_provider")
308+
meter = get_meter(
309+
__name__,
310+
__version__,
311+
meter_provider
312+
)
313+
connections_usage = meter.create_up_down_counter(
314+
name="db.client.connections.usage",
315+
unit="connections",
316+
description="The number of connections that are currently in state described"
317+
)
281318
_instrument(
282319
tracer,
320+
connections_usage,
283321
request_hook=kwargs.get("request_hook"),
284322
response_hook=kwargs.get("response_hook"),
285323
)

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

Lines changed: 1 addition & 0 deletions
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

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,60 @@ def request_hook(span, conn, args, kwargs):
141141

142142
span = spans[0]
143143
self.assertEqual(span.attributes.get(custom_attribute_name), "GET")
144+
145+
146+
class TestRedisIntegrationMetric(TestBase):
147+
148+
def setUp(self):
149+
super().setUp()
150+
RedisInstrumentor().instrument(meter_provider=self.meter_provider)
151+
152+
def tearDown(self):
153+
super().tearDown()
154+
RedisInstrumentor().uninstrument()
155+
156+
157+
@staticmethod
158+
def redis_get_multiple_connections():
159+
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
160+
c1 = pool.get_connection('_')
161+
c2 = pool.get_connection('_')
162+
c3 = pool.get_connection('_')
163+
164+
@staticmethod
165+
def redis_get():
166+
pool = redis.ConnectionPool(host='localhost', port=6379, db=0)
167+
redis_client = redis.Redis(connection_pool=pool)
168+
redis_client.get('foo')
169+
170+
171+
def test_basic_metric_success_redis(self):
172+
self.redis_get()
173+
expected_attributes = {
174+
"db.client.connections.usage": 1,
175+
}
176+
for (
177+
resource_metrics
178+
) in self.memory_metrics_reader.get_metrics_data().resource_metrics:
179+
for scope_metrics in resource_metrics.scope_metrics:
180+
for metric in scope_metrics.metrics:
181+
for data_point in metric.data.data_points:
182+
self.assertDictEqual(
183+
expected_attributes, dict(data_point.attributes)
184+
)
185+
186+
def test_multiple_connections_metric_success_redis(self):
187+
self.redis_get_multiple_connections()
188+
189+
expected_attributes = {
190+
"db.client.connections.usage": 3,
191+
}
192+
for (
193+
resource_metrics
194+
) in self.memory_metrics_reader.get_metrics_data().resource_metrics:
195+
for scope_metrics in resource_metrics.scope_metrics:
196+
for metric in scope_metrics.metrics:
197+
for data_point in metric.data.data_points:
198+
self.assertDictEqual(
199+
expected_attributes, dict(data_point.attributes)
200+
)

0 commit comments

Comments
 (0)