Skip to content

Commit 3639dc4

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

File tree

3 files changed

+101
-2
lines changed

3 files changed

+101
-2
lines changed

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

+43-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:
@@ -200,13 +203,34 @@ 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+
with open('/Users/diego_canizales/Documents/EPAM/Disney/Nucleus/playground/redis/log.txt', 'a') as f:
220+
for k, v in metric_labels.items():
221+
f.write(f'k:{k}, v:{v}\n')
222+
return response
223+
224+
203225
pipeline_class = (
204226
"BasePipeline" if redis.VERSION < (3, 0, 0) else "Pipeline"
205227
)
206228
redis_class = "StrictRedis" if redis.VERSION < (3, 0, 0) else "Redis"
207229

208230
wrap_function_wrapper(
209-
"redis", f"{redis_class}.execute_command", _traced_execute_command
231+
"redis",
232+
f"{redis_class}.execute_command",
233+
_traced_execute_command
210234
)
211235
wrap_function_wrapper(
212236
"redis.client",
@@ -229,6 +253,11 @@ def _traced_execute_pipeline(func, instance, args, kwargs):
229253
"ClusterPipeline.execute",
230254
_traced_execute_pipeline,
231255
)
256+
wrap_function_wrapper(
257+
"redis",
258+
"ConnectionPool.get_connection",
259+
_traced_get_connection
260+
)
232261
if redis.VERSION >= _REDIS_ASYNCIO_VERSION:
233262
wrap_function_wrapper(
234263
"redis.asyncio",
@@ -278,8 +307,20 @@ def _instrument(self, **kwargs):
278307
tracer = trace.get_tracer(
279308
__name__, __version__, tracer_provider=tracer_provider
280309
)
310+
meter_provider = kwargs.get("meter_provider")
311+
meter = get_meter(
312+
__name__,
313+
__version__,
314+
meter_provider
315+
)
316+
connections_usage = meter.create_up_down_counter(
317+
name="db.client.connections.usage",
318+
unit="connections",
319+
description="The number of connections that are currently in state described"
320+
)
281321
_instrument(
282322
tracer,
323+
connections_usage,
283324
request_hook=kwargs.get("request_hook"),
284325
response_hook=kwargs.get("response_hook"),
285326
)

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

+57
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)