diff --git a/.github/COMMIT_TEMPLATE.txt b/.github/COMMIT_TEMPLATE.txt index f6e418cc2e8..55dc46d5378 100644 --- a/.github/COMMIT_TEMPLATE.txt +++ b/.github/COMMIT_TEMPLATE.txt @@ -29,5 +29,5 @@ feat/fix/docs/refactor/ci(xxx): commit title here # mysqlpython, openai, opentelemetry, opentracer, profile, psycopg, pylibmc, pymemcache, # pymongo, pymysql, pynamodb, pyodbc, pyramid, pytest, redis, rediscluster, requests, rq, # sanic, snowflake, sourcecode, sqlalchemy, starlette, stdlib, structlog, subprocess, -# telemetry, test_logging, tornado, tracer, unittest, urllib3, vendor, vertica, wsgi, +# telemetry, test_logging, tornado, tracer, unittest, urllib3, valkey, vendor, vertica, wsgi, # yaaredis diff --git a/.riot/requirements/11ac941.txt b/.riot/requirements/11ac941.txt new file mode 100644 index 00000000000..92df617ba6e --- /dev/null +++ b/.riot/requirements/11ac941.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.8 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/11ac941.in +# +async-timeout==5.0.1 +attrs==24.3.0 +coverage[toml]==7.6.1 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.23.7 +pytest-cov==5.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.15.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +valkey==6.0.2 +zipp==3.20.2 diff --git a/.riot/requirements/1e98e9b.txt b/.riot/requirements/1e98e9b.txt new file mode 100644 index 00000000000..6e2d11413c3 --- /dev/null +++ b/.riot/requirements/1e98e9b.txt @@ -0,0 +1,26 @@ +# +# This file is autogenerated by pip-compile with Python 3.9 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/1e98e9b.in +# +async-timeout==5.0.1 +attrs==24.3.0 +coverage[toml]==7.6.10 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +importlib-metadata==8.5.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.23.7 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +valkey==6.0.2 +zipp==3.21.0 diff --git a/.riot/requirements/4aa2a2a.txt b/.riot/requirements/4aa2a2a.txt new file mode 100644 index 00000000000..6bc72515b3f --- /dev/null +++ b/.riot/requirements/4aa2a2a.txt @@ -0,0 +1,22 @@ +# +# This file is autogenerated by pip-compile with Python 3.11 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/4aa2a2a.in +# +async-timeout==5.0.1 +attrs==24.3.0 +coverage[toml]==7.6.10 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.23.7 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +valkey==6.0.2 diff --git a/.riot/requirements/7219cf4.txt b/.riot/requirements/7219cf4.txt new file mode 100644 index 00000000000..ffb631b7bcb --- /dev/null +++ b/.riot/requirements/7219cf4.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.13 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/7219cf4.in +# +attrs==24.3.0 +coverage[toml]==7.6.10 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.23.7 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +valkey==6.0.2 diff --git a/.riot/requirements/b96b665.txt b/.riot/requirements/b96b665.txt new file mode 100644 index 00000000000..8b14d5cb8ec --- /dev/null +++ b/.riot/requirements/b96b665.txt @@ -0,0 +1,21 @@ +# +# This file is autogenerated by pip-compile with Python 3.12 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/b96b665.in +# +attrs==24.3.0 +coverage[toml]==7.6.10 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.23.7 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +valkey==6.0.2 diff --git a/.riot/requirements/dd68acc.txt b/.riot/requirements/dd68acc.txt new file mode 100644 index 00000000000..8eda9971324 --- /dev/null +++ b/.riot/requirements/dd68acc.txt @@ -0,0 +1,24 @@ +# +# This file is autogenerated by pip-compile with Python 3.10 +# by the following command: +# +# pip-compile --allow-unsafe --no-annotate .riot/requirements/dd68acc.in +# +async-timeout==5.0.1 +attrs==24.3.0 +coverage[toml]==7.6.10 +exceptiongroup==1.2.2 +hypothesis==6.45.0 +iniconfig==2.0.0 +mock==5.1.0 +opentracing==2.4.0 +packaging==24.2 +pluggy==1.5.0 +pytest==8.3.4 +pytest-asyncio==0.23.7 +pytest-cov==6.0.0 +pytest-mock==3.14.0 +pytest-randomly==3.16.0 +sortedcontainers==2.4.0 +tomli==2.2.1 +valkey==6.0.2 diff --git a/ddtrace/_monkey.py b/ddtrace/_monkey.py index 75c70114ef2..f5890eb7bff 100644 --- a/ddtrace/_monkey.py +++ b/ddtrace/_monkey.py @@ -105,6 +105,7 @@ "unittest": True, "coverage": False, "selenium": True, + "valkey": True, } diff --git a/ddtrace/_trace/trace_handlers.py b/ddtrace/_trace/trace_handlers.py index 96c33da8c6b..b57e28cd0a3 100644 --- a/ddtrace/_trace/trace_handlers.py +++ b/ddtrace/_trace/trace_handlers.py @@ -649,6 +649,11 @@ def _on_redis_command_post(ctx: core.ExecutionContext, rowcount): ctx.span.set_metric(db.ROWCOUNT, rowcount) +def _on_valkey_command_post(ctx: core.ExecutionContext, rowcount): + if rowcount is not None: + ctx.span.set_metric(db.ROWCOUNT, rowcount) + + def _on_test_visibility_enable(config) -> None: from ddtrace.internal.ci_visibility import CIVisibility @@ -758,6 +763,8 @@ def listen(): core.on("botocore.kinesis.GetRecords.post", _on_botocore_kinesis_getrecords_post) core.on("redis.async_command.post", _on_redis_command_post) core.on("redis.command.post", _on_redis_command_post) + core.on("valkey.async_command.post", _on_valkey_command_post) + core.on("valkey.command.post", _on_valkey_command_post) core.on("azure.functions.request_call_modifier", _on_azure_functions_request_span_modifier) core.on("azure.functions.start_response", _on_azure_functions_start_response) @@ -786,6 +793,7 @@ def listen(): "botocore.patched_stepfunctions_api_call", "botocore.patched_bedrock_api_call", "redis.command", + "valkey.command", "rq.queue.enqueue_job", "rq.traced_queue_fetch_job", "rq.worker.perform_job", diff --git a/ddtrace/_trace/utils_valkey.py b/ddtrace/_trace/utils_valkey.py new file mode 100644 index 00000000000..ac90070ca9d --- /dev/null +++ b/ddtrace/_trace/utils_valkey.py @@ -0,0 +1,96 @@ +""" +Some utils used by the dogtrace valkey integration +""" + +from contextlib import contextmanager +from typing import List +from typing import Optional + +from ddtrace.constants import _ANALYTICS_SAMPLE_RATE_KEY +from ddtrace.constants import SPAN_KIND +from ddtrace.constants import SPAN_MEASURED_KEY +from ddtrace.contrib import trace_utils +from ddtrace.contrib.valkey_utils import _extract_conn_tags +from ddtrace.ext import SpanKind +from ddtrace.ext import SpanTypes +from ddtrace.ext import db +from ddtrace.ext import valkey as valkeyx +from ddtrace.internal import core +from ddtrace.internal.constants import COMPONENT +from ddtrace.internal.schema import schematize_cache_operation +from ddtrace.internal.utils.formats import stringify_cache_args + + +format_command_args = stringify_cache_args + + +def _set_span_tags( + span, pin, config_integration, args: Optional[List], instance, query: Optional[List], is_cluster: bool = False +): + span.set_tag_str(SPAN_KIND, SpanKind.CLIENT) + span.set_tag_str(COMPONENT, config_integration.integration_name) + span.set_tag_str(db.SYSTEM, valkeyx.APP) + span.set_tag(SPAN_MEASURED_KEY) + if query is not None: + span_name = schematize_cache_operation(valkeyx.RAWCMD, cache_provider=valkeyx.APP) # type: ignore[operator] + span.set_tag_str(span_name, query) + if pin.tags: + span.set_tags(pin.tags) + # some valkey clients do not have a connection_pool attribute (ex. aiovalkey v1.3) + if not is_cluster and hasattr(instance, "connection_pool"): + span.set_tags(_extract_conn_tags(instance.connection_pool.connection_kwargs)) + if args is not None: + span.set_metric(valkeyx.ARGS_LEN, len(args)) + else: + for attr in ("command_stack", "_command_stack"): + if hasattr(instance, attr): + span.set_metric(valkeyx.PIPELINE_LEN, len(getattr(instance, attr))) + # set analytics sample rate if enabled + span.set_tag(_ANALYTICS_SAMPLE_RATE_KEY, config_integration.get_analytics_sample_rate()) + + +@contextmanager +def _instrument_valkey_cmd(pin, config_integration, instance, args): + query = stringify_cache_args(args, cmd_max_len=config_integration.cmd_max_length) + with core.context_with_data( + "valkey.command", + span_name=schematize_cache_operation(valkeyx.CMD, cache_provider=valkeyx.APP), + pin=pin, + service=trace_utils.ext_service(pin, config_integration), + span_type=SpanTypes.VALKEY, + resource=query.split(" ")[0] if config_integration.resource_only_command else query, + ) as ctx, ctx.span as span: + _set_span_tags(span, pin, config_integration, args, instance, query) + yield ctx + + +@contextmanager +def _instrument_valkey_execute_pipeline(pin, config_integration, cmds, instance, is_cluster=False): + cmd_string = resource = "\n".join(cmds) + if config_integration.resource_only_command: + resource = "\n".join([cmd.split(" ")[0] for cmd in cmds]) + + with pin.tracer.trace( + schematize_cache_operation(valkeyx.CMD, cache_provider=valkeyx.APP), + resource=resource, + service=trace_utils.ext_service(pin, config_integration), + span_type=SpanTypes.VALKEY, + ) as span: + _set_span_tags(span, pin, config_integration, None, instance, cmd_string) + yield span + + +@contextmanager +def _instrument_valkey_execute_async_cluster_pipeline(pin, config_integration, cmds, instance): + cmd_string = resource = "\n".join(cmds) + if config_integration.resource_only_command: + resource = "\n".join([cmd.split(" ")[0] for cmd in cmds]) + + with pin.tracer.trace( + schematize_cache_operation(valkeyx.CMD, cache_provider=valkeyx.APP), + resource=resource, + service=trace_utils.ext_service(pin, config_integration), + span_type=SpanTypes.VALKEY, + ) as span: + _set_span_tags(span, pin, config_integration, None, instance, cmd_string) + yield span diff --git a/ddtrace/contrib/internal/valkey/asyncio_patch.py b/ddtrace/contrib/internal/valkey/asyncio_patch.py new file mode 100644 index 00000000000..ef36fdd2bf4 --- /dev/null +++ b/ddtrace/contrib/internal/valkey/asyncio_patch.py @@ -0,0 +1,36 @@ +from ddtrace import config +from ddtrace._trace.utils_valkey import _instrument_valkey_cmd +from ddtrace._trace.utils_valkey import _instrument_valkey_execute_async_cluster_pipeline +from ddtrace._trace.utils_valkey import _instrument_valkey_execute_pipeline +from ddtrace.contrib.valkey_utils import _run_valkey_command_async +from ddtrace.internal.utils.formats import stringify_cache_args +from ddtrace.trace import Pin + + +async def instrumented_async_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + with _instrument_valkey_cmd(pin, config.valkey, instance, args) as ctx: + return await _run_valkey_command_async(ctx=ctx, func=func, args=args, kwargs=kwargs) + + +async def instrumented_async_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c, cmd_max_len=config.valkey.cmd_max_length) for c, _ in instance.command_stack] + with _instrument_valkey_execute_pipeline(pin, config.valkey, cmds, instance): + return await func(*args, **kwargs) + + +async def instrumented_async_execute_cluster_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return await func(*args, **kwargs) + + cmds = [stringify_cache_args(c.args, cmd_max_len=config.valkey.cmd_max_length) for c in instance._command_stack] + with _instrument_valkey_execute_async_cluster_pipeline(pin, config.valkey, cmds, instance): + return await func(*args, **kwargs) diff --git a/ddtrace/contrib/internal/valkey/patch.py b/ddtrace/contrib/internal/valkey/patch.py new file mode 100644 index 00000000000..7de63f947c1 --- /dev/null +++ b/ddtrace/contrib/internal/valkey/patch.py @@ -0,0 +1,223 @@ +""" +The valkey integration traces valkey requests. + + +Enabling +~~~~~~~~ + +The valkey integration is enabled automatically when using +:ref:`ddtrace-run` or :ref:`import ddtrace.auto`. + +Or use :func:`patch()` to manually enable the integration:: + + from ddtrace import patch + patch(valkey=True) + + +Global Configuration +~~~~~~~~~~~~~~~~~~~~ + +.. py:data:: ddtrace.config.valkey["service"] + + The service name reported by default for valkey traces. + + This option can also be set with the ``DD_VALKEY_SERVICE`` environment + variable. + + Default: ``"valkey"`` + + +.. py:data:: ddtrace.config.valkey["cmd_max_length"] + + Max allowable size for the valkey command span tag. + Anything beyond the max length will be replaced with ``"..."``. + + This option can also be set with the ``DD_VALKEY_CMD_MAX_LENGTH`` environment + variable. + + Default: ``1000`` + + +.. py:data:: ddtrace.config.valkey["resource_only_command"] + + The span resource will only include the command executed. To include all + arguments in the span resource, set this value to ``False``. + + This option can also be set with the ``DD_VALKEY_RESOURCE_ONLY_COMMAND`` environment + variable. + + Default: ``True`` + + +Instance Configuration +~~~~~~~~~~~~~~~~~~~~~~ + +To configure particular valkey instances use the :class:`Pin ` API:: + + import valkey + from ddtrace.trace import Pin + + client = valkey.StrictValkey(host="localhost", port=6379) + + # Override service name for this instance + Pin.override(client, service="my-custom-queue") + + # Traces reported for this client will now have "my-custom-queue" + # as the service name. + client.get("my-key") +""" +import os + +import valkey +import wrapt + +from ddtrace import config +from ddtrace._trace.utils_valkey import _instrument_valkey_cmd +from ddtrace._trace.utils_valkey import _instrument_valkey_execute_pipeline +from ddtrace.contrib.internal.valkey_utils import ROW_RETURNING_COMMANDS +from ddtrace.contrib.internal.valkey_utils import determine_row_count +from ddtrace.contrib.trace_utils import unwrap +from ddtrace.internal import core +from ddtrace.internal.schema import schematize_service_name +from ddtrace.internal.utils.formats import CMD_MAX_LEN +from ddtrace.internal.utils.formats import asbool +from ddtrace.internal.utils.formats import stringify_cache_args +from ddtrace.trace import Pin + + +config._add( + "valkey", + { + "_default_service": schematize_service_name("valkey"), + "cmd_max_length": int(os.getenv("DD_VALKEY_CMD_MAX_LENGTH", CMD_MAX_LEN)), + "resource_only_command": asbool(os.getenv("DD_VALKEY_RESOURCE_ONLY_COMMAND", True)), + }, +) + + +def get_version(): + # type: () -> str + return getattr(valkey, "__version__", "") + + +def patch(): + """Patch the instrumented methods + + This duplicated doesn't look nice. The nicer alternative is to use an ObjectProxy on top + of Valkey and StrictValkey. However, it means that any "import valkey.Valkey" won't be instrumented. + """ + if getattr(valkey, "_datadog_patch", False): + return + valkey._datadog_patch = True + + _w = wrapt.wrap_function_wrapper + + from .asyncio_patch import instrumented_async_execute_cluster_pipeline + from .asyncio_patch import instrumented_async_execute_command + from .asyncio_patch import instrumented_async_execute_pipeline + + _w("valkey", "Valkey.execute_command", instrumented_execute_command(config.valkey)) + _w("valkey", "Valkey.pipeline", instrumented_pipeline) + _w("valkey.client", "Pipeline.execute", instrumented_execute_pipeline(config.valkey, False)) + _w("valkey.client", "Pipeline.immediate_execute_command", instrumented_execute_command(config.valkey)) + _w("valkey.cluster", "ValkeyCluster.execute_command", instrumented_execute_command(config.valkey)) + _w("valkey.cluster", "ValkeyCluster.pipeline", instrumented_pipeline) + _w("valkey.cluster", "ClusterPipeline.execute", instrumented_execute_pipeline(config.valkey, True)) + Pin(service=None).onto(valkey.cluster.ValkeyCluster) + + _w("valkey.asyncio.client", "Valkey.execute_command", instrumented_async_execute_command) + _w("valkey.asyncio.client", "Valkey.pipeline", instrumented_pipeline) + _w("valkey.asyncio.client", "Pipeline.execute", instrumented_async_execute_pipeline) + _w("valkey.asyncio.client", "Pipeline.immediate_execute_command", instrumented_async_execute_command) + Pin(service=None).onto(valkey.asyncio.Valkey) + + _w("valkey.asyncio.cluster", "ValkeyCluster.execute_command", instrumented_async_execute_command) + _w("valkey.asyncio.cluster", "ValkeyCluster.pipeline", instrumented_pipeline) + _w("valkey.asyncio.cluster", "ClusterPipeline.execute", instrumented_async_execute_cluster_pipeline) + Pin(service=None).onto(valkey.asyncio.ValkeyCluster) + + Pin(service=None).onto(valkey.StrictValkey) + + +def unpatch(): + if getattr(valkey, "_datadog_patch", False): + valkey._datadog_patch = False + + unwrap(valkey.Valkey, "execute_command") + unwrap(valkey.Valkey, "pipeline") + unwrap(valkey.client.Pipeline, "execute") + unwrap(valkey.client.Pipeline, "immediate_execute_command") + unwrap(valkey.cluster.ValkeyCluster, "execute_command") + unwrap(valkey.cluster.ValkeyCluster, "pipeline") + unwrap(valkey.cluster.ClusterPipeline, "execute") + unwrap(valkey.asyncio.client.Valkey, "execute_command") + unwrap(valkey.asyncio.client.Valkey, "pipeline") + unwrap(valkey.asyncio.client.Pipeline, "execute") + unwrap(valkey.asyncio.client.Pipeline, "immediate_execute_command") + unwrap(valkey.asyncio.cluster.ValkeyCluster, "execute_command") + unwrap(valkey.asyncio.cluster.ValkeyCluster, "pipeline") + unwrap(valkey.asyncio.cluster.ClusterPipeline, "execute") + + +def _run_valkey_command(ctx: core.ExecutionContext, func, args, kwargs): + parsed_command = stringify_cache_args(args) + valkey_command = parsed_command.split(" ")[0] + rowcount = None + result = None + try: + result = func(*args, **kwargs) + return result + except Exception: + rowcount = 0 + raise + finally: + if rowcount is None: + rowcount = determine_row_count(valkey_command=valkey_command, result=result) + if valkey_command not in ROW_RETURNING_COMMANDS: + rowcount = None + core.dispatch("valkey.command.post", [ctx, rowcount]) + + +# +# tracing functions +# +def instrumented_execute_command(integration_config): + def _instrumented_execute_command(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + with _instrument_valkey_cmd(pin, integration_config, instance, args) as ctx: + return _run_valkey_command(ctx=ctx, func=func, args=args, kwargs=kwargs) + + return _instrumented_execute_command + + +def instrumented_pipeline(func, instance, args, kwargs): + pipeline = func(*args, **kwargs) + pin = Pin.get_from(instance) + if pin: + pin.onto(pipeline) + return pipeline + + +def instrumented_execute_pipeline(integration_config, is_cluster=False): + def _instrumented_execute_pipeline(func, instance, args, kwargs): + pin = Pin.get_from(instance) + if not pin or not pin.enabled(): + return func(*args, **kwargs) + + if is_cluster: + cmds = [ + stringify_cache_args(c.args, cmd_max_len=integration_config.cmd_max_length) + for c in instance.command_stack + ] + else: + cmds = [ + stringify_cache_args(c, cmd_max_len=integration_config.cmd_max_length) + for c, _ in instance.command_stack + ] + with _instrument_valkey_execute_pipeline(pin, integration_config, cmds, instance, is_cluster): + return func(*args, **kwargs) + + return _instrumented_execute_pipeline diff --git a/ddtrace/contrib/internal/valkey_utils.py b/ddtrace/contrib/internal/valkey_utils.py new file mode 100644 index 00000000000..8518dbe648a --- /dev/null +++ b/ddtrace/contrib/internal/valkey_utils.py @@ -0,0 +1,84 @@ +from typing import Dict +from typing import List +from typing import Optional +from typing import Union + +from ddtrace.ext import net +from ddtrace.ext import valkey as valkeyx +from ddtrace.internal import core +from ddtrace.internal.utils.formats import stringify_cache_args + + +SINGLE_KEY_COMMANDS = [ + "GET", + "GETDEL", + "GETEX", + "GETRANGE", + "GETSET", + "LINDEX", + "LRANGE", + "RPOP", + "LPOP", + "HGET", + "HGETALL", + "HKEYS", + "HMGET", + "HRANDFIELD", + "HVALS", +] +MULTI_KEY_COMMANDS = ["MGET"] +ROW_RETURNING_COMMANDS = SINGLE_KEY_COMMANDS + MULTI_KEY_COMMANDS + + +def _extract_conn_tags(conn_kwargs): + """Transform valkey conn info into dogtrace metas""" + try: + conn_tags = { + net.TARGET_HOST: conn_kwargs["host"], + net.TARGET_PORT: conn_kwargs["port"], + net.SERVER_ADDRESS: conn_kwargs["host"], + valkeyx.DB: conn_kwargs.get("db") or 0, + } + client_name = conn_kwargs.get("client_name") + if client_name: + conn_tags[valkeyx.CLIENT_NAME] = client_name + return conn_tags + except Exception: + return {} + + +def determine_row_count(valkey_command: str, result: Optional[Union[List, Dict, str]]) -> int: + empty_results = [b"", [], {}, None] + # result can be an empty list / dict / string + if result not in empty_results: + if valkey_command == "MGET": + # only include valid key results within count + result = [x for x in result if x not in empty_results] + return len(result) + elif valkey_command == "HMGET": + # only include valid key results within count + result = [x for x in result if x not in empty_results] + return 1 if len(result) > 0 else 0 + else: + return 1 + else: + return 0 + + +async def _run_valkey_command_async(ctx: core.ExecutionContext, func, args, kwargs): + parsed_command = stringify_cache_args(args) + valkey_command = parsed_command.split(" ")[0] + rowcount = None + result = None + try: + result = await func(*args, **kwargs) + return result + except BaseException: + rowcount = 0 + raise + finally: + if rowcount is None: + rowcount = determine_row_count(valkey_command=valkey_command, result=result) + if valkey_command not in ROW_RETURNING_COMMANDS: + rowcount = None + core.dispatch("valkey.async_command.post", [ctx, rowcount]) diff --git a/ddtrace/ext/__init__.py b/ddtrace/ext/__init__.py index 965dd04f43f..98cc5226100 100644 --- a/ddtrace/ext/__init__.py +++ b/ddtrace/ext/__init__.py @@ -16,6 +16,7 @@ class SpanTypes(object): AUTH = "auth" SYSTEM = "system" LLM = "llm" + VALKEY = "valkey" class SpanKind(object): @@ -35,5 +36,6 @@ class SpanKind(object): SpanTypes.REDIS, SpanTypes.SQL, SpanTypes.WORKER, + SpanTypes.VALKEY, } ) diff --git a/ddtrace/ext/valkey.py b/ddtrace/ext/valkey.py new file mode 100644 index 00000000000..3246af841f6 --- /dev/null +++ b/ddtrace/ext/valkey.py @@ -0,0 +1,14 @@ +# defaults +APP = "valkey" +DEFAULT_SERVICE = "valkey" + +# net extension +DB = "out.valkey_db" + +# standard tags +RAWCMD = "valkey.raw_command" +CMD = "valkey.command" +ARGS_LEN = "valkey.args_length" +PIPELINE_LEN = "valkey.pipeline_length" +PIPELINE_AGE = "valkey.pipeline_age" +CLIENT_NAME = "valkey.client_name" diff --git a/docker-compose.yml b/docker-compose.yml index 118ad8cc5db..30cc08d0ae7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -207,5 +207,35 @@ services: - DD_TRACE_AGENT_URL=http://testagent:8126 - _DD_APPSEC_DEDUPLICATION_ENABLED=false + valkey: + image: valkey/valkey:8.0-alpine + ports: + - "127.0.0.1:6379:6379" + + valkeycluster: + image: valkey/valkey:8.0-alpine + ports: + - "127.0.0.1:7000:7000" + - "127.0.0.1:7001:7001" + - "127.0.0.1:7002:7002" + - "127.0.0.1:7003:7003" + - "127.0.0.1:7004:7004" + - "127.0.0.1:7005:7005" + entrypoint: + - /bin/sh + - -c + - | + PORTS='7000 7001 7002 7003 7004 7005' + for PORT in $${PORTS} + do + mkdir $${PORT} + cd $${PORT} + valkey-server --port $${PORT} --cluster-enabled yes --cluster-node-timeout 5000 --appendonly yes & + cd .. + done + sleep 2 + valkey-cli --cluster create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 --cluster-yes --cluster-replicas 1 + sleep infinity + volumes: ddagent: diff --git a/docs/index.rst b/docs/index.rst index 23c0cd48a06..2b2f61d91c4 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -174,6 +174,8 @@ contacting support. +--------------------------------------------------+---------------+----------------+ | :ref:`urllib3` | >= 1.25.8 | No | +--------------------------------------------------+---------------+----------------+ +| :ref:`valkey` | >= 6.0.0 | Yes | ++--------------------------------------------------+---------------+----------------+ | :ref:`vertexai` | >= 1.71.1 | Yes | +--------------------------------------------------+---------------+----------------+ | :ref:`vertica` | >= 0.6 | Yes | diff --git a/docs/integrations.rst b/docs/integrations.rst index 62e096aa668..541ad07322b 100644 --- a/docs/integrations.rst +++ b/docs/integrations.rst @@ -485,6 +485,13 @@ urllib3 .. automodule:: ddtrace.contrib.urllib3 +.. _valkey: + +valkey +^^^^^^ +.. automodule:: ddtrace.contrib.valkey.patch + + .. _vertexai: vertexai diff --git a/docs/spelling_wordlist.txt b/docs/spelling_wordlist.txt index ff2cfc09c6d..913ad7f8319 100644 --- a/docs/spelling_wordlist.txt +++ b/docs/spelling_wordlist.txt @@ -301,6 +301,7 @@ urls username uvicorn uWSGI +valkey vendored versioned vertexai diff --git a/min_compatible_versions.csv b/min_compatible_versions.csv index 4537863f24c..c128cd85ffc 100644 --- a/min_compatible_versions.csv +++ b/min_compatible_versions.csv @@ -182,6 +182,7 @@ typing-extensions,0 typing_extensions,0 urllib3,~=1.0 uwsgi,0 +valkey,~=6.0.0 vcrpy,==4.2.1 vertexai,0 vertica-python,>=0.6.0 diff --git a/releasenotes/notes/add-valkey-support-6cc9f41351dc0cd9.yaml b/releasenotes/notes/add-valkey-support-6cc9f41351dc0cd9.yaml new file mode 100644 index 00000000000..6a8c47f01da --- /dev/null +++ b/releasenotes/notes/add-valkey-support-6cc9f41351dc0cd9.yaml @@ -0,0 +1,3 @@ +features: + - | + valkey: adds automatic instrumentation of the Valkey package \ No newline at end of file diff --git a/riotfile.py b/riotfile.py index b52fa77cbe1..bab57242ab3 100644 --- a/riotfile.py +++ b/riotfile.py @@ -2947,6 +2947,16 @@ def select_pys(min_version: str = MIN_PYTHON_VERSION, max_version: str = MAX_PYT Venv(pys=select_pys(min_version="3.8"), pkgs={"ragas": "==0.1.21", "langchain": latest}), ], ), + Venv( + name="valkey", + command="pytest {cmdargs} tests/contrib/valkey", + pkgs={ + "valkey": latest, + "pytest-randomly": latest, + "pytest-asyncio": "==0.23.7", + }, + pys=select_pys(min_version="3.8"), + ), Venv( name="profile", # NB riot commands that use this Venv must include --pass-env to work properly diff --git a/supported_versions_output.json b/supported_versions_output.json index a51bb17bb9a..06eff37be25 100644 --- a/supported_versions_output.json +++ b/supported_versions_output.json @@ -294,6 +294,12 @@ "max_tracer_supported": "2.2.3", "auto-instrumented": false }, + { + "integration": "valkey", + "minimum_tracer_supported": "6.0.0", + "max_tracer_supported": "6.0.2", + "auto-instrumented": true + }, { "integration": "vertexai", "minimum_tracer_supported": "1.71.1", diff --git a/supported_versions_table.csv b/supported_versions_table.csv index 3f7384a0cdd..edbe73503cd 100644 --- a/supported_versions_table.csv +++ b/supported_versions_table.csv @@ -47,5 +47,6 @@ starlette,0.13.6,0.41.3,True structlog,20.2.0,24.4.0,False tornado *,4.5.3,6.4,False urllib3,1.24.3,2.2.3,False +valkey,6.0.0,6.0.2,True vertexai,1.71.1,1.71.1,True yaaredis,2.0.4,3.0.0,True diff --git a/tests/contrib/config.py b/tests/contrib/config.py index 6ed086109a2..0b5f3d2bfbb 100644 --- a/tests/contrib/config.py +++ b/tests/contrib/config.py @@ -97,3 +97,13 @@ "host": os.getenv("TEST_KAFKA_HOST", "127.0.0.1"), "port": int(os.getenv("TEST_KAFKA_PORT", 29092)), } + +VALKEY_CONFIG = { + "host": os.getenv("TEST_VALKEY_HOST", "localhost"), + "port": int(os.getenv("TEST_VALKEY_PORT", 6379)), +} + +VALKEY_CLUSTER_CONFIG = { + "host": "127.0.0.1", + "ports": os.getenv("TEST_VALKEYCLUSTER_PORTS", "7000,7001,7002,7003,7004,7005"), +} diff --git a/tests/contrib/suitespec.yml b/tests/contrib/suitespec.yml index 8c5fbc72da2..6f852650892 100644 --- a/tests/contrib/suitespec.yml +++ b/tests/contrib/suitespec.yml @@ -240,6 +240,11 @@ components: urllib3: - ddtrace/contrib/urllib3/* - ddtrace/contrib/internal/urllib3/* + valkey: + - ddtrace/contrib/valkey/* + - ddtrace/contrib/internal/valkey/* + - ddtrace/_trace/utils_valkey.py + - ddtrace/ext/valkey.py vertica: - ddtrace/contrib/vertica/* - ddtrace/contrib/internal/vertica/* @@ -1177,3 +1182,19 @@ suites: services: - redis snapshot: true + valkey: + parallelism: 5 + paths: + - '@bootstrap' + - '@core' + - '@contrib' + - '@tracing' + - '@valkey' + - tests/contrib/valkey/* + - tests/snapshots/tests.contrib.valkey.* + pattern: ^valkey$ + runner: riot + services: + - valkeycluster + - valkey + snapshot: true diff --git a/tests/contrib/valkey/__init__.py b/tests/contrib/valkey/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/contrib/valkey/test_valkey.py b/tests/contrib/valkey/test_valkey.py new file mode 100644 index 00000000000..1f78e744a7a --- /dev/null +++ b/tests/contrib/valkey/test_valkey.py @@ -0,0 +1,617 @@ +# -*- coding: utf-8 -*- +from unittest import mock + +import pytest +import valkey + +import ddtrace +from ddtrace.contrib.internal.valkey.patch import patch +from ddtrace.contrib.internal.valkey.patch import unpatch +from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME +from ddtrace.trace import Pin +from tests.opentracer.utils import init_tracer +from tests.utils import DummyTracer +from tests.utils import TracerTestCase +from tests.utils import snapshot + +from ..config import VALKEY_CONFIG + + +class TestValkeyPatch(TracerTestCase): + TEST_PORT = VALKEY_CONFIG["port"] + + def setUp(self): + super(TestValkeyPatch, self).setUp() + patch() + r = valkey.Valkey(port=self.TEST_PORT) + r.flushall() + Pin.override(r, tracer=self.tracer) + self.r = r + + def tearDown(self): + unpatch() + super(TestValkeyPatch, self).tearDown() + + def command_test_rowcount(self, raw_command, row_count, expect_result=True, **kwargs): + command_args_as_list = raw_command.split(" ") + + command_name = command_args_as_list[0].lower() + + if hasattr(self.r, command_name): + func = getattr(self.r, command_name) + + try: + # try to run function with kwargs, may fail due to valkey version + result = yield func(*command_args_as_list[1:], **kwargs) + for k in kwargs.keys(): + raw_command += " " + str(kwargs[k]) + except Exception: + # try without keyword arguments + result = func(*command_args_as_list[1:]) + + if expect_result: + assert result is not None + else: + empty_result = [None, [], {}, b""] + if isinstance(result, list): + result = [x for x in result if x] + assert result in empty_result + + command_span = self.get_spans()[-1] + + assert command_span.name == "valkey.command" + assert command_span.get_tag("valkey.raw_command") == raw_command + assert command_span.get_metric("db.row_count") == row_count + + def test_long_command(self): + self.r.mget(*range(1000)) + + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + + self.assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.span_type == "valkey" + assert span.error == 0 + meta = { + "out.host": "localhost", + } + metrics = { + "network.destination.port": self.TEST_PORT, + "out.valkey_db": 0, + } + for k, v in meta.items(): + assert span.get_tag(k) == v + for k, v in metrics.items(): + assert span.get_metric(k) == v + + assert span.get_tag("valkey.raw_command").startswith("MGET 0 1 2 3") + assert span.get_tag("valkey.raw_command").endswith("...") + assert span.get_tag("component") == "valkey" + assert span.get_tag("span.kind") == "client" + assert span.get_tag("db.system") == "valkey" + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) + def test_service_name_v1(self): + us = self.r.get("cheese") + assert us is None + spans = self.get_spans() + span = spans[0] + assert span.service == DEFAULT_SPAN_SERVICE_NAME + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) + def test_operation_name_v0_schema(self): + us = self.r.get("cheese") + assert us is None + spans = self.get_spans() + span = spans[0] + assert span.name == "valkey.command" + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) + def test_operation_name_v1_schema(self): + us = self.r.get("cheese") + assert us is None + spans = self.get_spans() + span = spans[0] + assert span.name == "valkey.command" + + def test_basics(self): + us = self.r.get("cheese") + assert us is None + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + self.assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_metric("out.valkey_db") == 0 + assert span.get_tag("out.host") == "localhost" + assert span.get_tag("valkey.raw_command") == "GET cheese" + assert span.get_tag("component") == "valkey" + assert span.get_tag("span.kind") == "client" + assert span.get_tag("db.system") == "valkey" + assert span.get_metric("valkey.args_length") == 2 + assert span.resource == "GET" + + def test_connection_error(self): + with mock.patch.object( + valkey.connection.ConnectionPool, + "get_connection", + side_effect=valkey.exceptions.ConnectionError("whatever"), + ): + with pytest.raises(valkey.exceptions.ConnectionError): + self.r.get("foo") + + def test_pipeline_traced(self): + with self.r.pipeline(transaction=False) as p: + p.set("blah", 32) + p.rpush("foo", "éé") + p.hgetall("xxx") + p.execute() + + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + self.assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.resource == "SET\nRPUSH\nHGETALL" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_metric("out.valkey_db") == 0 + assert span.get_tag("out.host") == "localhost" + assert span.get_tag("valkey.raw_command") == "SET blah 32\nRPUSH foo éé\nHGETALL xxx" + assert span.get_tag("component") == "valkey" + assert span.get_tag("span.kind") == "client" + assert span.get_metric("valkey.pipeline_length") == 3 + assert span.get_metric("valkey.pipeline_length") == 3 + + def test_pipeline_immediate(self): + with self.r.pipeline() as p: + p.set("a", 1) + p.immediate_execute_command("SET", "a", 1) + p.execute() + + spans = self.get_spans() + assert len(spans) == 2 + span = spans[0] + self.assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.resource == "SET" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_metric("out.valkey_db") == 0 + assert span.get_tag("out.host") == "localhost" + assert span.get_tag("component") == "valkey" + assert span.get_tag("span.kind") == "client" + + def test_meta_override(self): + r = self.r + pin = Pin.get_from(r) + if pin: + pin.clone(tags={"cheese": "camembert"}).onto(r) + + r.get("cheese") + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + assert span.service == "valkey" + assert "cheese" in span.get_tags() and span.get_tag("cheese") == "camembert" + + def test_patch_unpatch(self): + tracer = DummyTracer() + + # Test patch idempotence + patch() + patch() + + r = valkey.Valkey(port=VALKEY_CONFIG["port"]) + Pin.get_from(r).clone(tracer=tracer).onto(r) + r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + # Test unpatch + unpatch() + + r = valkey.Valkey(port=VALKEY_CONFIG["port"]) + r.get("key") + + spans = tracer.pop() + assert not spans, spans + + # Test patch again + patch() + + r = valkey.Valkey(port=VALKEY_CONFIG["port"]) + Pin.get_from(r).clone(tracer=tracer).onto(r) + r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + def test_opentracing(self): + """Ensure OpenTracing works with valkey.""" + ot_tracer = init_tracer("valkey_svc", self.tracer) + + with ot_tracer.start_active_span("valkey_get"): + us = self.r.get("cheese") + assert us is None + + spans = self.get_spans() + assert len(spans) == 2 + ot_span, dd_span = spans + + # confirm the parenting + assert ot_span.parent_id is None + assert dd_span.parent_id == ot_span.span_id + + assert ot_span.name == "valkey_get" + assert ot_span.service == "valkey_svc" + + self.assert_is_measured(dd_span) + assert dd_span.service == "valkey" + assert dd_span.name == "valkey.command" + assert dd_span.span_type == "valkey" + assert dd_span.error == 0 + assert dd_span.get_metric("out.valkey_db") == 0 + assert dd_span.get_tag("out.host") == "localhost" + assert dd_span.get_tag("valkey.raw_command") == "GET cheese" + assert dd_span.get_tag("component") == "valkey" + assert dd_span.get_tag("span.kind") == "client" + assert dd_span.get_tag("db.system") == "valkey" + assert dd_span.get_metric("valkey.args_length") == 2 + assert dd_span.resource == "GET" + + def test_valkey_rowcount_all_keys_valid(self): + self.r.set("key1", "value1") + + get1 = self.r.get("key1") + + assert get1 == b"value1" + + spans = self.get_spans() + get_valid_key_span = spans[1] + + assert get_valid_key_span.name == "valkey.command" + assert get_valid_key_span.get_tag("valkey.raw_command") == "GET key1" + assert get_valid_key_span.get_metric("db.row_count") == 1 + + get_commands = ["GET key", "GETEX key", "GETRANGE key 0 2"] + list_get_commands = ["LINDEX lkey 0", "LRANGE lkey 0 3", "RPOP lkey", "LPOP lkey"] + hashing_get_commands = [ + "HGET hkey field1", + "HGETALL hkey", + "HKEYS hkey", + "HMGET hkey field1 field2", + "HRANDFIELD hkey", + "HVALS hkey", + ] + multi_key_get_commands = ["MGET key key2", "MGET key key2 key3", "MGET key key2 key3 key4"] + + for command in get_commands: + self.r.set("key", "value") + self.command_test_rowcount(command, 1) + for command in list_get_commands: + self.r.lpush("lkey", "1", "2", "3", "4", "5") + self.command_test_rowcount(command, 1) + if command == "RPOP lkey": # lets get multiple values from the set and ensure rowcount is still 1 + self.command_test_rowcount(command, 1, count=2) + for command in hashing_get_commands: + self.r.hset("hkey", "field1", "value1") + self.r.hset("hkey", "field2", "value2") + self.command_test_rowcount(command, 1) + for command in multi_key_get_commands: + self.r.mset({"key": "value", "key2": "value2", "key3": "value3", "key4": "value4"}) + self.command_test_rowcount(command, len(command.split(" ")) - 1) + + def test_valkey_rowcount_some_keys_valid(self): + self.r.mset({"key": "value", "key2": "value2"}) + + get_both_valid = self.r.mget("key", "key2") + get_one_missing = self.r.mget("key", "missing_key") + + assert get_both_valid == [b"value", b"value2"] + assert get_one_missing == [b"value", None] + + spans = self.get_spans() + get_both_valid_span = spans[1] + get_one_missing_span = spans[2] + + assert get_both_valid_span.name == "valkey.command" + assert get_both_valid_span.get_tag("valkey.raw_command") == "MGET key key2" + assert get_both_valid_span.get_metric("db.row_count") == 2 + + assert get_one_missing_span.name == "valkey.command" + assert get_one_missing_span.get_tag("valkey.raw_command") == "MGET key missing_key" + assert get_one_missing_span.get_metric("db.row_count") == 1 + + multi_key_get_commands = [ + "MGET key key2", + "MGET key missing_key", + "MGET key key2 missing_key", + "MGET key missing_key missing_key2 key2", + ] + + for command in multi_key_get_commands: + command_keys = command.split(" ")[1:] + self.command_test_rowcount(command, len([key for key in command_keys if "missing_key" not in key])) + + def test_valkey_rowcount_no_keys_valid(self): + get_missing = self.r.get("missing_key") + + assert get_missing is None + + spans = self.get_spans() + get_missing_key_span = spans[0] + + assert get_missing_key_span.name == "valkey.command" + assert get_missing_key_span.get_tag("valkey.raw_command") == "GET missing_key" + assert get_missing_key_span.get_metric("db.row_count") == 0 + + get_commands = ["GET key", "GETDEL key", "GETEX key", "GETRANGE key 0 2"] + list_get_commands = ["LINDEX lkey 0", "LRANGE lkey 0 3", "RPOP lkey", "LPOP lkey"] + hashing_get_commands = [ + "HGET hkey field1", + "HGETALL hkey", + "HKEYS hkey", + "HMGET hkey field1 field2", + "HRANDFIELD hkey", + "HVALS hkey", + ] + multi_key_get_commands = ["MGET key key2", "MGET key key2 key3", "MGET key key2 key3 key4"] + + for command in get_commands: + self.command_test_rowcount(command, 0, expect_result=False) + for command in list_get_commands: + self.command_test_rowcount(command, 0, expect_result=False) + if command == "RPOP lkey": # lets get multiple values from the set and ensure rowcount is still 1 + self.command_test_rowcount(command, 0, expect_result=False, count=2) + for command in hashing_get_commands: + self.command_test_rowcount(command, 0, expect_result=False) + for command in multi_key_get_commands: + self.command_test_rowcount(command, 0, expect_result=False) + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc")) + def test_user_specified_service_default(self): + from ddtrace import config + + assert config.service == "mysvc" + + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "valkey" + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) + def test_user_specified_service_v0(self): + from ddtrace import config + + assert config.service == "mysvc" + + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "valkey" + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) + def test_user_specified_service_v1(self): + from ddtrace import config + + assert config.service == "mysvc" + + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "mysvc" + + @TracerTestCase.run_in_subprocess( + env_overrides=dict(DD_VALKEY_SERVICE="myvalkey", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0") + ) + def test_env_user_specified_valkey_service_v0(self): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "myvalkey", span.service + + self.reset() + + # Global config + with self.override_config("valkey", dict(service="cfg-valkey")): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "cfg-valkey", span.service + + self.reset() + + # Manual override + Pin.override(self.r, service="mysvc", tracer=self.tracer) + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "mysvc", span.service + + @TracerTestCase.run_in_subprocess( + env_overrides=dict( + DD_SERVICE="app-svc", DD_VALKEY_SERVICE="env-specified-valkey-svc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0" + ) + ) + def test_service_precedence_v0(self): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "env-specified-valkey-svc", span.service + + self.reset() + + # Do a manual override + Pin.override(self.r, service="override-valkey", tracer=self.tracer) + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "override-valkey", span.service + + +class TestValkeyPatchSnapshot(TracerTestCase): + TEST_PORT = VALKEY_CONFIG["port"] + + def setUp(self): + super(TestValkeyPatchSnapshot, self).setUp() + patch() + r = valkey.Valkey(port=self.TEST_PORT) + self.r = r + + def tearDown(self): + unpatch() + super(TestValkeyPatchSnapshot, self).tearDown() + self.r.flushall() + + @snapshot() + def test_long_command(self): + self.r.mget(*range(1000)) + + @snapshot() + def test_basics(self): + us = self.r.get("cheese") + assert us is None + + @snapshot() + def test_unicode(self): + us = self.r.get("😐") + assert us is None + + @snapshot() + def test_pipeline_traced(self): + with self.r.pipeline(transaction=False) as p: + p.set("blah", 32) + p.rpush("foo", "éé") + p.hgetall("xxx") + p.execute() + + @snapshot() + def test_pipeline_immediate(self): + with self.r.pipeline() as p: + p.set("a", 1) + p.immediate_execute_command("SET", "a", 1) + p.execute() + + @snapshot() + def test_meta_override(self): + r = self.r + pin = Pin.get_from(r) + if pin: + pin.clone(tags={"cheese": "camembert"}).onto(r) + + r.get("cheese") + + def test_patch_unpatch(self): + tracer = DummyTracer() + + # Test patch idempotence + patch() + patch() + + r = valkey.Valkey(port=VALKEY_CONFIG["port"]) + Pin.get_from(r).clone(tracer=tracer).onto(r) + r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + # Test unpatch + unpatch() + + r = valkey.Valkey(port=VALKEY_CONFIG["port"]) + r.get("key") + + spans = tracer.pop() + assert not spans, spans + + # Test patch again + patch() + + r = valkey.Valkey(port=VALKEY_CONFIG["port"]) + Pin.get_from(r).clone(tracer=tracer).onto(r) + r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + @snapshot() + def test_opentracing(self): + """Ensure OpenTracing works with valkey.""" + writer = ddtrace.tracer._writer + ot_tracer = init_tracer("valkey_svc", ddtrace.tracer) + ddtrace.tracer.configure(writer=writer) + + with ot_tracer.start_active_span("valkey_get"): + us = self.r.get("cheese") + assert us is None + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc")) + @snapshot() + def test_user_specified_service(self): + from ddtrace import config + + assert config.service == "mysvc" + + self.r.get("cheese") + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_VALKEY_SERVICE="myvalkey")) + @snapshot() + def test_env_user_specified_valkey_service(self): + self.r.get("cheese") + + self.reset() + + # Global config + with self.override_config("valkey", dict(service="cfg-valkey")): + self.r.get("cheese") + + self.reset() + + # Manual override + Pin.override(self.r, service="mysvc", tracer=self.tracer) + self.r.get("cheese") + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="app-svc", DD_VALKEY_SERVICE="env-valkey")) + @snapshot() + def test_service_precedence(self): + self.r.get("cheese") + + self.reset() + + # Do a manual override + Pin.override(self.r, service="override-valkey", tracer=self.tracer) + self.r.get("cheese") + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_VALKEY_CMD_MAX_LENGTH="10")) + @snapshot() + def test_custom_cmd_length_env(self): + self.r.get("here-is-a-long-key-name") + + @snapshot() + def test_custom_cmd_length(self): + with self.override_config("valkey", dict(cmd_max_length=7)): + self.r.get("here-is-a-long-key-name") + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_VALKEY_RESOURCE_ONLY_COMMAND="false")) + @snapshot() + def test_full_command_in_resource_env(self): + self.r.get("put_key_in_resource") + p = self.r.pipeline(transaction=False) + p.set("pipeline-cmd1", 1) + p.set("pipeline-cmd2", 2) + p.execute() + + @snapshot() + def test_full_command_in_resource_config(self): + with self.override_config("valkey", dict(resource_only_command=False)): + self.r.get("put_key_in_resource") + p = self.r.pipeline(transaction=False) + p.set("pipeline-cmd1", 1) + p.set("pipeline-cmd2", 2) + p.execute() diff --git a/tests/contrib/valkey/test_valkey_asyncio.py b/tests/contrib/valkey/test_valkey_asyncio.py new file mode 100644 index 00000000000..64743a7770b --- /dev/null +++ b/tests/contrib/valkey/test_valkey_asyncio.py @@ -0,0 +1,221 @@ +import asyncio +import typing +from unittest import mock + +import pytest +import valkey +import valkey.asyncio +from wrapt import ObjectProxy + +from ddtrace import tracer +from ddtrace.contrib.internal.valkey.patch import patch +from ddtrace.contrib.internal.valkey.patch import unpatch +from ddtrace.trace import Pin +from tests.utils import override_config + +from ..config import VALKEY_CONFIG + + +def get_valkey_instance(max_connections: int, client_name: typing.Optional[str] = None): + return valkey.asyncio.from_url( + "valkey://127.0.0.1:%s" % VALKEY_CONFIG["port"], max_connections=max_connections, client_name=client_name + ) + + +@pytest.fixture +def valkey_client(): + r = get_valkey_instance(max_connections=10) # default values + yield r + + +@pytest.fixture +def single_pool_valkey_client(): + r = get_valkey_instance(max_connections=1) + yield r + + +@pytest.fixture(autouse=True) +async def traced_valkey(valkey_client): + await valkey_client.flushall() + + patch() + try: + yield + finally: + unpatch() + await valkey_client.flushall() + + +def test_patching(): + """ + When patching valkey library + We wrap the correct methods + When unpatching valkey library + We unwrap the correct methods + """ + assert isinstance(valkey.asyncio.client.Valkey.execute_command, ObjectProxy) + assert isinstance(valkey.asyncio.client.Valkey.pipeline, ObjectProxy) + assert isinstance(valkey.asyncio.client.Pipeline.pipeline, ObjectProxy) + unpatch() + assert not isinstance(valkey.asyncio.client.Valkey.execute_command, ObjectProxy) + assert not isinstance(valkey.asyncio.client.Valkey.pipeline, ObjectProxy) + assert not isinstance(valkey.asyncio.client.Pipeline.pipeline, ObjectProxy) + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_basic_request(valkey_client): + val = await valkey_client.get("cheese") + assert val is None + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_unicode_request(valkey_client): + val = await valkey_client.get("😐") + assert val is None + + +@pytest.mark.snapshot(wait_for_num_traces=1, ignores=["meta.error.stack"]) +async def test_connection_error(valkey_client): + with mock.patch.object( + valkey.asyncio.connection.ConnectionPool, + "get_connection", + side_effect=valkey.exceptions.ConnectionError("whatever"), + ): + with pytest.raises(valkey.exceptions.ConnectionError): + await valkey_client.get("foo") + + +@pytest.mark.snapshot(wait_for_num_traces=2) +async def test_decoding_non_utf8_args(valkey_client): + await valkey_client.set(b"\x80foo", b"\x80abc") + val = await valkey_client.get(b"\x80foo") + assert val == b"\x80abc" + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_decoding_non_utf8_pipeline_args(valkey_client): + p = valkey_client.pipeline() + p.set(b"\x80blah", "boo") + p.set("foo", b"\x80abc") + p.get(b"\x80blah") + p.get("foo") + + response_list = await p.execute() + assert response_list[0] is True # response from valkey.set is OK if successfully pushed + assert response_list[1] is True + assert response_list[2].decode() == "boo" + assert response_list[3] == b"\x80abc" + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_long_command(valkey_client): + length = 1000 + val_list = await valkey_client.mget(*range(length)) + assert len(val_list) == length + for val in val_list: + assert val is None + + +@pytest.mark.snapshot(wait_for_num_traces=3) +async def test_override_service_name(valkey_client): + with override_config("valkey", dict(service_name="myvalkey")): + val = await valkey_client.get("cheese") + assert val is None + await valkey_client.set("cheese", "my-cheese") + val = await valkey_client.get("cheese") + if isinstance(val, bytes): + val = val.decode() + assert val == "my-cheese" + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_pin(valkey_client): + Pin.override(valkey_client, service="my-valkey") + val = await valkey_client.get("cheese") + assert val is None + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_pipeline_traced(valkey_client): + p = valkey_client.pipeline(transaction=False) + p.set("blah", "boo") + p.set("foo", "bar") + p.get("blah") + p.get("foo") + + response_list = await p.execute() + assert response_list[0] is True # response from valkey.set is OK if successfully pushed + assert response_list[1] is True + assert ( + response_list[2].decode() == "boo" + ) # response from hset is 'Integer reply: The number of fields that were added.' + assert response_list[3].decode() == "bar" + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_pipeline_traced_context_manager_transaction(valkey_client): + """ + Regression test for: https://github.com/DataDog/dd-trace-py/issues/3106 + + Example:: + + async def main(): + valkey = await valkey.from_url("valkey://localhost") + async with valkey.pipeline(transaction=True) as pipe: + ok1, ok2 = await (pipe.set("key1", "value1").set("key2", "value2").execute()) + assert ok1 + assert ok2 + """ + + async with valkey_client.pipeline(transaction=True) as p: + set_1, set_2, get_1, get_2 = await p.set("blah", "boo").set("foo", "bar").get("blah").get("foo").execute() + + # response from valkey.set is OK if successfully pushed + assert set_1 is True + assert set_2 is True + assert get_1.decode() == "boo" + assert get_2.decode() == "bar" + + +@pytest.mark.snapshot(wait_for_num_traces=1) +async def test_two_traced_pipelines(valkey_client): + with tracer.trace("web-request", service="test"): + p1 = await valkey_client.pipeline(transaction=False) + p2 = await valkey_client.pipeline(transaction=False) + await p1.set("blah", "boo") + await p2.set("foo", "bar") + await p1.get("blah") + await p2.get("foo") + + response_list1 = await p1.execute() + response_list2 = await p2.execute() + + assert response_list1[0] is True # response from valkey.set is OK if successfully pushed + assert response_list2[0] is True + assert ( + response_list1[1].decode() == "boo" + ) # response from hset is 'Integer reply: The number of fields that were added.' + assert response_list2[1].decode() == "bar" + + +async def test_parenting(valkey_client, snapshot_context): + with snapshot_context(wait_for_num_traces=1): + with tracer.trace("web-request", service="test"): + await valkey_client.set("blah", "boo") + await valkey_client.get("blah") + + +async def test_client_name(snapshot_context): + with snapshot_context(wait_for_num_traces=1): + with tracer.trace("web-request", service="test"): + valkey_client = get_valkey_instance(10, client_name="testing-client-name") + await valkey_client.get("blah") + + +@pytest.mark.asyncio +async def test_asyncio_task_cancelled(valkey_client): + with mock.patch.object( + valkey.asyncio.connection.ConnectionPool, "get_connection", side_effect=asyncio.CancelledError + ): + with pytest.raises(asyncio.CancelledError): + await valkey_client.get("foo") diff --git a/tests/contrib/valkey/test_valkey_cluster.py b/tests/contrib/valkey/test_valkey_cluster.py new file mode 100644 index 00000000000..3876426d25d --- /dev/null +++ b/tests/contrib/valkey/test_valkey_cluster.py @@ -0,0 +1,209 @@ +# -*- coding: utf-8 -*- +import valkey + +from ddtrace.contrib.internal.valkey.patch import patch +from ddtrace.contrib.internal.valkey.patch import unpatch +from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME +from ddtrace.trace import Pin +from tests.contrib.config import VALKEY_CLUSTER_CONFIG +from tests.utils import DummyTracer +from tests.utils import TracerTestCase +from tests.utils import assert_is_measured + + +class TestValkeyClusterPatch(TracerTestCase): + TEST_HOST = VALKEY_CLUSTER_CONFIG["host"] + TEST_PORTS = VALKEY_CLUSTER_CONFIG["ports"] + + def _get_test_client(self): + startup_nodes = [valkey.cluster.ClusterNode(self.TEST_HOST, int(port)) for port in self.TEST_PORTS.split(",")] + return valkey.cluster.ValkeyCluster(startup_nodes=startup_nodes) + + def setUp(self): + super(TestValkeyClusterPatch, self).setUp() + patch() + r = self._get_test_client() + r.flushall() + Pin.override(r, tracer=self.tracer) + self.r = r + + def tearDown(self): + unpatch() + super(TestValkeyClusterPatch, self).tearDown() + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) + def test_span_service_name_v1(self): + us = self.r.get("cheese") + assert us is None + spans = self.get_spans() + span = spans[0] + assert span.service == DEFAULT_SPAN_SERVICE_NAME + + def test_basics(self): + us = self.r.get("cheese") + assert us is None + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_tag("valkey.raw_command") == "GET cheese" + assert span.get_tag("component") == "valkey" + assert span.get_tag("db.system") == "valkey" + assert span.get_metric("valkey.args_length") == 2 + assert span.resource == "GET" + + def test_unicode(self): + us = self.r.get("😐") + assert us is None + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_tag("valkey.raw_command") == "GET 😐" + assert span.get_tag("component") == "valkey" + assert span.get_tag("db.system") == "valkey" + assert span.get_metric("valkey.args_length") == 2 + assert span.resource == "GET" + + def test_pipeline(self): + with self.r.pipeline(transaction=False) as p: + p.set("blah", 32) + p.rpush("foo", "éé") + p.hgetall("xxx") + p.execute() + + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.resource == "SET\nRPUSH\nHGETALL" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_tag("valkey.raw_command") == "SET blah 32\nRPUSH foo éé\nHGETALL xxx" + assert span.get_tag("component") == "valkey" + assert span.get_metric("valkey.pipeline_length") == 3 + + def test_patch_unpatch(self): + tracer = DummyTracer() + + # Test patch idempotence + patch() + patch() + + r = self._get_test_client() + Pin.get_from(r).clone(tracer=tracer).onto(r) + r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + # Test unpatch + unpatch() + + r = self._get_test_client() + r.get("key") + + spans = tracer.pop() + assert not spans, spans + + # Test patch again + patch() + + r = self._get_test_client() + Pin.get_from(r).clone(tracer=tracer).onto(r) + r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0")) + def test_user_specified_service_v0(self): + """ + When a user specifies a service for the app + The valkeycluster integration should not use it. + """ + # Ensure that the service name was configured + from ddtrace import config + + assert config.service == "mysvc" + + r = self._get_test_client() + Pin.get_from(r).clone(tracer=self.tracer).onto(r) + r.get("key") + + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + assert span.service != "mysvc" + + @TracerTestCase.run_in_subprocess(env_overrides=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1")) + def test_user_specified_service_v1(self): + """ + When a user specifies a service for the app + The valkeycluster integration should use it. + """ + # Ensure that the service name was configured + from ddtrace import config + + assert config.service == "mysvc" + + r = self._get_test_client() + Pin.get_from(r).clone(tracer=self.tracer).onto(r) + r.get("key") + + spans = self.get_spans() + assert len(spans) == 1 + span = spans[0] + assert span.service == "mysvc" + + @TracerTestCase.run_in_subprocess( + env_overrides=dict(DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0") + ) + def test_env_user_specified_valkeycluster_service_v0(self): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "myvalkeycluster", span.service + + @TracerTestCase.run_in_subprocess( + env_overrides=dict(DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1") + ) + def test_env_user_specified_valkeycluster_service_v1(self): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "myvalkeycluster", span.service + + @TracerTestCase.run_in_subprocess( + env_overrides=dict( + DD_SERVICE="app-svc", DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0" + ) + ) + def test_service_precedence_v0(self): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "myvalkeycluster" + + self.reset() + + @TracerTestCase.run_in_subprocess( + env_overrides=dict( + DD_SERVICE="app-svc", DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1" + ) + ) + def test_service_precedence_v1(self): + self.r.get("cheese") + span = self.get_spans()[0] + assert span.service == "myvalkeycluster" + + self.reset() diff --git a/tests/contrib/valkey/test_valkey_cluster_asyncio.py b/tests/contrib/valkey/test_valkey_cluster_asyncio.py new file mode 100644 index 00000000000..8f63aa9d542 --- /dev/null +++ b/tests/contrib/valkey/test_valkey_cluster_asyncio.py @@ -0,0 +1,457 @@ +# -*- coding: utf-8 -*- +import pytest +import valkey + +from ddtrace.contrib.internal.valkey.patch import patch +from ddtrace.contrib.internal.valkey.patch import unpatch +from ddtrace.trace import Pin +from tests.contrib.config import VALKEY_CLUSTER_CONFIG +from tests.utils import DummyTracer +from tests.utils import assert_is_measured + + +TEST_HOST = VALKEY_CLUSTER_CONFIG["host"] +TEST_PORTS = VALKEY_CLUSTER_CONFIG["ports"] + + +@pytest.mark.asyncio +@pytest.fixture +async def valkey_cluster(): + startup_nodes = [valkey.asyncio.cluster.ClusterNode(TEST_HOST, int(port)) for port in TEST_PORTS.split(",")] + yield valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + + +@pytest.mark.asyncio +@pytest.fixture +async def traced_valkey_cluster(tracer, test_spans): + patch() + startup_nodes = [valkey.asyncio.cluster.ClusterNode(TEST_HOST, int(port)) for port in TEST_PORTS.split(",")] + valkey_cluster = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + await valkey_cluster.flushall() + Pin.override(valkey_cluster, tracer=tracer) + try: + yield valkey_cluster, test_spans + finally: + unpatch() + await valkey_cluster.flushall() + + +@pytest.mark.asyncio +async def test_basics(traced_valkey_cluster): + cluster, test_spans = traced_valkey_cluster + us = await cluster.get("cheese") + assert us is None + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + + span = spans[0] + assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_tag("valkey.raw_command") == "GET cheese" + assert span.get_tag("component") == "valkey" + assert span.get_tag("db.system") == "valkey" + assert span.get_metric("valkey.args_length") == 2 + assert span.resource == "GET" + + +@pytest.mark.asyncio +async def test_unicode(traced_valkey_cluster): + cluster, test_spans = traced_valkey_cluster + us = await cluster.get("😐") + assert us is None + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + + span = spans[0] + assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_tag("valkey.raw_command") == "GET 😐" + assert span.get_tag("component") == "valkey" + assert span.get_tag("db.system") == "valkey" + assert span.get_metric("valkey.args_length") == 2 + assert span.resource == "GET" + + +@pytest.mark.asyncio +async def test_pipeline(traced_valkey_cluster): + cluster, test_spans = traced_valkey_cluster + async with cluster.pipeline(transaction=False) as p: + p.set("blah", 32) + p.rpush("foo", "éé") + p.hgetall("xxx") + await p.execute() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + + span = spans[0] + assert_is_measured(span) + assert span.service == "valkey" + assert span.name == "valkey.command" + assert span.resource == "SET\nRPUSH\nHGETALL" + assert span.span_type == "valkey" + assert span.error == 0 + assert span.get_tag("valkey.raw_command") == "SET blah 32\nRPUSH foo éé\nHGETALL xxx" + assert span.get_tag("component") == "valkey" + assert span.get_metric("valkey.pipeline_length") == 3 + + +@pytest.mark.asyncio +async def test_patch_unpatch(valkey_cluster): + tracer = DummyTracer() + + # Test patch idempotence + patch() + patch() + + r = valkey_cluster + Pin.override(r, tracer=tracer) + await r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + + # Test unpatch + unpatch() + + r = valkey_cluster + await r.get("key") + + spans = tracer.pop() + assert not spans, spans + + # Test patch again + patch() + + r = valkey_cluster + Pin.override(r, tracer=tracer) + await r.get("key") + + spans = tracer.pop() + assert spans, spans + assert len(spans) == 1 + unpatch() + + +@pytest.mark.subprocess( + env=dict(DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1"), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_default_service_name_v1(): + import asyncio + + import valkey + + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.internal.schema import DEFAULT_SPAN_SERVICE_NAME + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service == DEFAULT_SPAN_SERVICE_NAME + + asyncio.run(test()) + + +@pytest.mark.subprocess( + env=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0"), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_user_specified_service_v0(): + """ + When a user specifies a service for the app + The valkeycluster integration should not use it. + """ + import asyncio + + import valkey + + from ddtrace import config + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + # # Ensure that the service name was configured + assert config.service == "mysvc" + + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service != "mysvc" + + asyncio.run(test()) + + +@pytest.mark.subprocess( + env=dict(DD_SERVICE="mysvc", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1"), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_user_specified_service_v1(): + """ + When a user specifies a service for the app + The valkeycluster integration should use it. + """ + import asyncio + + import valkey + + from ddtrace import config + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + # # Ensure that the service name was configured + assert config.service == "mysvc" + + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service == "mysvc" + + asyncio.run(test()) + + +@pytest.mark.subprocess( + env=dict(DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0"), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_env_user_specified_valkeycluster_service_v0(): + import asyncio + + import valkey + + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service == "myvalkeycluster" + + asyncio.run(test()) + + +@pytest.mark.subprocess( + env=dict(DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1"), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_env_user_specified_valkeycluster_service_v1(): + import asyncio + + import valkey + + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service == "myvalkeycluster" + + asyncio.run(test()) + + +@pytest.mark.subprocess( + env=dict( + DD_SERVICE="mysvc", + DD_VALKEY_SERVICE="myvalkeycluster", + DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v0", + ), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_service_precedence_v0(): + import asyncio + + import valkey + + from ddtrace import config + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + # # Ensure that the service name was configured + assert config.service == "mysvc" + + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service == "myvalkeycluster" + + asyncio.run(test()) + + +@pytest.mark.subprocess( + env=dict(DD_SERVICE="mysvc", DD_VALKEY_SERVICE="myvalkeycluster", DD_TRACE_SPAN_ATTRIBUTE_SCHEMA="v1"), + err=None, # avoid checking stderr because of an expected deprecation warning +) +def test_service_precedence_v1(): + import asyncio + + import valkey + + from ddtrace import config + from ddtrace.contrib.internal.valkey.patch import patch + from ddtrace.trace import Pin + from tests.contrib.config import VALKEY_CLUSTER_CONFIG + from tests.utils import DummyTracer + from tests.utils import TracerSpanContainer + + patch() + + async def test(): + # # Ensure that the service name was configured + assert config.service == "mysvc" + + startup_nodes = [ + valkey.asyncio.cluster.ClusterNode(VALKEY_CLUSTER_CONFIG["host"], int(port)) + for port in VALKEY_CLUSTER_CONFIG["ports"].split(",") + ] + r = valkey.asyncio.cluster.ValkeyCluster(startup_nodes=startup_nodes) + tracer = DummyTracer() + test_spans = TracerSpanContainer(tracer) + + Pin.get_from(r).clone(tracer=tracer).onto(r) + await r.get("key") + await r.close() + + traces = test_spans.pop_traces() + assert len(traces) == 1 + spans = traces[0] + assert len(spans) == 1 + span = spans[0] + assert span.service == "myvalkeycluster" + + asyncio.run(test()) diff --git a/tests/contrib/valkey/test_valkey_patch.py b/tests/contrib/valkey/test_valkey_patch.py new file mode 100644 index 00000000000..320d2b82b6a --- /dev/null +++ b/tests/contrib/valkey/test_valkey_patch.py @@ -0,0 +1,31 @@ +# This test script was automatically generated by the contrib-patch-tests.py +# script. If you want to make changes to it, you should make sure that you have +# removed the ``_generated`` suffix from the file name, to prevent the content +# from being overwritten by future re-generations. + +from ddtrace.contrib.internal.valkey.patch import get_version +from ddtrace.contrib.internal.valkey.patch import patch + + +try: + from ddtrace.contrib.internal.valkey.patch import unpatch +except ImportError: + unpatch = None +from tests.contrib.patch import PatchTestCase + + +class TestValkeyPatch(PatchTestCase.Base): + __integration_name__ = "valkey" + __module_name__ = "valkey" + __patch_func__ = patch + __unpatch_func__ = unpatch + __get_version__ = get_version + + def assert_module_patched(self, valkey): + pass + + def assert_not_module_patched(self, valkey): + pass + + def assert_not_module_double_patched(self, valkey): + pass diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_analytics_with_rate.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_analytics_with_rate.json new file mode 100644 index 00000000000..cdc76343f08 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_analytics_with_rate.json @@ -0,0 +1,38 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_dd1.sr.eausr": 0.5, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 516916, + "start": 1692651820581556875 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_analytics_without_rate.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_analytics_without_rate.json new file mode 100644 index 00000000000..9a2bb9f2e4f --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_analytics_without_rate.json @@ -0,0 +1,38 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_dd1.sr.eausr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 340708, + "start": 1692651820591814875 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_basics.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_basics.json new file mode 100644 index 00000000000..e6da74211bb --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_basics.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 335292, + "start": 1692651820600962708 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_custom_cmd_length.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_custom_cmd_length.json new file mode 100644 index 00000000000..5614e912961 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_custom_cmd_length.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET here..." + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 326167, + "start": 1692651820609597416 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_custom_cmd_length_env.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_custom_cmd_length_env.json new file mode 100644 index 00000000000..75f058f3700 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_custom_cmd_length_env.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "ea409d0295db44adbf88dda3e4806547", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET here-is..." + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 20043, + "valkey.args_length": 2 + }, + "duration": 404084, + "start": 1692651821117540958 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_env_user_specified_valkey_service.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_env_user_specified_valkey_service.json new file mode 100644 index 00000000000..f4b7d26f3a6 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_env_user_specified_valkey_service.json @@ -0,0 +1,74 @@ +[[ + { + "name": "valkey.command", + "service": "myvalkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "e263ff9ad1cd43099216a11ca5e19377", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 20046, + "valkey.args_length": 2 + }, + "duration": 501125, + "start": 1692651821692035875 + }], +[ + { + "name": "valkey.command", + "service": "cfg-valkey", + "resource": "GET", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "e263ff9ad1cd43099216a11ca5e19377", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 20046, + "valkey.args_length": 2 + }, + "duration": 329333, + "start": 1692651821722196292 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_full_command_in_resource_config.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_full_command_in_resource_config.json new file mode 100644 index 00000000000..c447412ee09 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_full_command_in_resource_config.json @@ -0,0 +1,71 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET put_key_in_resource", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3a1f7ba9b1ab42f4858e5effd03877ef", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET put_key_in_resource" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 65639, + "valkey.args_length": 2 + }, + "duration": 2978000, + "start": 1698858795260743000 + }], +[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET pipeline-cmd1 1\nSET pipeline-cmd2 2", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3a1f7ba9b1ab42f4858e5effd03877ef", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "SET pipeline-cmd1 1\nSET pipeline-cmd2 2" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 65639, + "valkey.pipeline_length": 2 + }, + "duration": 1408000, + "start": 1698858795278553000 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_full_command_in_resource_env.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_full_command_in_resource_env.json new file mode 100644 index 00000000000..f7f89e8565a --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_full_command_in_resource_env.json @@ -0,0 +1,71 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET put_key_in_resource", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "451464ac55804a488cf355b1d96c7002", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET put_key_in_resource" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 65646, + "valkey.args_length": 2 + }, + "duration": 3112000, + "start": 1698858796156355000 + }], +[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET pipeline-cmd1 1\nSET pipeline-cmd2 2", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "", + "_dd.p.dm": "-0", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "451464ac55804a488cf355b1d96c7002", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "SET pipeline-cmd1 1\nSET pipeline-cmd2 2" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 65646, + "valkey.pipeline_length": 2 + }, + "duration": 1246000, + "start": 1698858796167913000 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_long_command.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_long_command.json new file mode 100644 index 00000000000..15378c706ba --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_long_command.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "MGET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "MGET 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 36..." + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 1001 + }, + "duration": 3428042, + "start": 1692651821775339875 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_meta_override.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_meta_override.json new file mode 100644 index 00000000000..5edc6b45665 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_meta_override.json @@ -0,0 +1,38 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "cheese": "camembert", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 474500, + "start": 1692651821790889125 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_opentracing.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_opentracing.json new file mode 100644 index 00000000000..749bd3d3307 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_opentracing.json @@ -0,0 +1,56 @@ +[[ + { + "name": "valkey_get", + "service": "valkey_svc", + "resource": "valkey_get", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "language": "python", + "runtime-id": "3cf1df7fb079462ab81608355e026651" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 19999 + }, + "duration": 534179, + "start": 1692651821803009280 + }, + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "out.host": "localhost", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "valkey.args_length": 2 + }, + "duration": 358500, + "start": 1692651821803151542 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_pipeline_immediate.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_pipeline_immediate.json new file mode 100644 index 00000000000..5559b6a5959 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_pipeline_immediate.json @@ -0,0 +1,72 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "SET a 1" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 3 + }, + "duration": 343500, + "start": 1692651821823333917 + }], +[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "SET a 1" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.pipeline_length": 1 + }, + "duration": 158750, + "start": 1692651821823756750 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_pipeline_traced.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_pipeline_traced.json new file mode 100644 index 00000000000..c5e90a181b3 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_pipeline_traced.json @@ -0,0 +1,36 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET\nRPUSH\nHGETALL", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "SET blah 32\nRPUSH foo \u00e9\u00e9\nHGETALL xxx" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.pipeline_length": 3 + }, + "duration": 589917, + "start": 1692651821833429417 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_service_precedence.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_service_precedence.json new file mode 100644 index 00000000000..27979635427 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_service_precedence.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "env-valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "app-svc", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "0f782133fa34462daf85cad95bb55fd2", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 20052, + "valkey.args_length": 2 + }, + "duration": 423750, + "start": 1692651822324419751 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_unicode.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_unicode.json new file mode 100644 index 00000000000..09f6f46fd3d --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_unicode.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET \ud83d\ude10" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 512083, + "start": 1692651822408832834 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey.test_user_specified_service.json b/tests/snapshots/tests.contrib.valkey.test_valkey.test_user_specified_service.json new file mode 100644 index 00000000000..7a91612554e --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey.test_user_specified_service.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "mysvc", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "localhost", + "runtime-id": "9d4dd102c4394715976611e15b961233", + "server.address": "localhost", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 20056, + "valkey.args_length": 2 + }, + "duration": 439500, + "start": 1692651822941153668 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_basic_request.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_basic_request.json new file mode 100644 index 00000000000..70b0e166d97 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_basic_request.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 595750, + "start": 1692651823036625793 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_client_name.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_client_name.json new file mode 100644 index 00000000000..4f7b2688d76 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_client_name.json @@ -0,0 +1,57 @@ +[[ + { + "name": "web-request", + "service": "test", + "resource": "web-request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "language": "python", + "runtime-id": "3cf1df7fb079462ab81608355e026651" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 19999 + }, + "duration": 828125, + "start": 1692651823188535376 + }, + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "out.host": "127.0.0.1", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.client_name": "testing-client-name", + "valkey.raw_command": "GET blah" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "valkey.args_length": 2 + }, + "duration": 541041, + "start": 1692651823188798168 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_connection_error.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_connection_error.json new file mode 100644 index 00000000000..64609dd8614 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_connection_error.json @@ -0,0 +1,40 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 1, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "error.message": "whatever", + "error.stack": "Traceback (most recent call last):\n File \"/root/project/ddtrace/contrib/trace_utils_valkey.py\", line 117, in _trace_valkey_cmd\n yield span\n File \"/root/project/ddtrace/contrib/valkey/asyncio_patch.py\", line 22, in traced_async_execute_command\n return await _run_valkey_command_async(span=span, func=func, args=args, kwargs=kwargs)\n File \"/root/project/ddtrace/contrib/valkey/asyncio_patch.py\", line 41, in _run_valkey_command_async\n result = await func(*args, **kwargs)\n File \"/root/project/.riot/venv_py31011_mock_pytest_pytest-mock_coverage_pytest-cov_opentracing_hypothesis6451_pytest-asyncio_valkey~41/lib/python3.10/site-packages/valkey/asyncio/client.py\", line 509, in execute_command\n conn = self.connection or await pool.get_connection(command_name, **options)\n File \"/root/.pyenv/versions/3.10.11/lib/python3.10/unittest/mock.py\", line 2234, in _execute_mock_call\n raise effect\nvalkey.exceptions.ConnectionError: whatever\n", + "error.type": "valkey.exceptions.ConnectionError", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "dc59875580884b52bebd2f9c402238f8", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET foo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 2340, + "valkey.args_length": 2 + }, + "duration": 935417, + "start": 1695409673533997174 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_decoding_non_utf8_args.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_decoding_non_utf8_args.json new file mode 100644 index 00000000000..649d89db933 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_decoding_non_utf8_args.json @@ -0,0 +1,73 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET \\x80foo \\x80abc" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 3 + }, + "duration": 512917, + "start": 1692651823066497751 + }], +[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET \\x80foo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 330333, + "start": 1692651823067101001 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_decoding_non_utf8_pipeline_args.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_decoding_non_utf8_pipeline_args.json new file mode 100644 index 00000000000..c22d2347b5e --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_decoding_non_utf8_pipeline_args.json @@ -0,0 +1,36 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET\nSET\nGET\nGET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET \\x80blah boo\nSET foo \\x80abc\nGET \\x80blah\nGET foo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.pipeline_length": 4 + }, + "duration": 404709, + "start": 1692651823079707584 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_long_command.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_long_command.json new file mode 100644 index 00000000000..9f4e40ffd1b --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_long_command.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "MGET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "MGET 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 36..." + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 1001 + }, + "duration": 5689625, + "start": 1692651823091333793 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_override_service_name.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_override_service_name.json new file mode 100644 index 00000000000..f3d0bce583a --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_override_service_name.json @@ -0,0 +1,110 @@ +[[ + { + "name": "valkey.command", + "service": "myvalkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 297666, + "start": 1692651823109161293 + }], +[ + { + "name": "valkey.command", + "service": "myvalkey", + "resource": "SET", + "trace_id": 1, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET cheese my-cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 3 + }, + "duration": 230084, + "start": 1692651823109550709 + }], +[ + { + "name": "valkey.command", + "service": "myvalkey", + "resource": "GET", + "trace_id": 2, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 160875, + "start": 1692651823109840043 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_parenting.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_parenting.json new file mode 100644 index 00000000000..c9a38d7fa31 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_parenting.json @@ -0,0 +1,85 @@ +[[ + { + "name": "web-request", + "service": "test", + "resource": "web-request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "language": "python", + "runtime-id": "3cf1df7fb079462ab81608355e026651" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 19999 + }, + "duration": 953000, + "start": 1692651823176740209 + }, + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "out.host": "127.0.0.1", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET blah boo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "valkey.args_length": 3 + }, + "duration": 270791, + "start": 1692651823176857918 + }, + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "out.host": "127.0.0.1", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET blah" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "db.row_count": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "valkey.args_length": 2 + }, + "duration": 499000, + "start": 1692651823177170168 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pin.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pin.json new file mode 100644 index 00000000000..91c995cc259 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pin.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "my-valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET cheese" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 327417, + "start": 1692651823121474251 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pipeline_traced.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pipeline_traced.json new file mode 100644 index 00000000000..e267216e24f --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pipeline_traced.json @@ -0,0 +1,36 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET\nSET\nGET\nGET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET blah boo\nSET foo bar\nGET blah\nGET foo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.pipeline_length": 4 + }, + "duration": 384125, + "start": 1692651823134602834 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pipeline_traced_context_manager_transaction.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pipeline_traced_context_manager_transaction.json new file mode 100644 index 00000000000..72633ef5e16 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_pipeline_traced_context_manager_transaction.json @@ -0,0 +1,36 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET\nSET\nGET\nGET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET blah boo\nSET foo bar\nGET blah\nGET foo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.pipeline_length": 4 + }, + "duration": 507125, + "start": 1692651823152247501 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_two_traced_pipelines.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_two_traced_pipelines.json new file mode 100644 index 00000000000..60ff68c9b1b --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_two_traced_pipelines.json @@ -0,0 +1,84 @@ +[[ + { + "name": "web-request", + "service": "test", + "resource": "web-request", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "language": "python", + "runtime-id": "3cf1df7fb079462ab81608355e026651" + }, + "metrics": { + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "process_id": 19999 + }, + "duration": 940000, + "start": 1692651823164019209 + }, + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET\nGET", + "trace_id": 0, + "span_id": 2, + "parent_id": 1, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "out.host": "127.0.0.1", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET blah boo\nGET blah" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "valkey.pipeline_length": 2 + }, + "duration": 352833, + "start": 1692651823164207293 + }, + { + "name": "valkey.command", + "service": "valkey", + "resource": "SET\nGET", + "trace_id": 0, + "span_id": 3, + "parent_id": 1, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "out.host": "127.0.0.1", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "SET foo bar\nGET foo" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "network.destination.port": 6379, + "out.valkey_db": 0, + "valkey.pipeline_length": 2 + }, + "duration": 310042, + "start": 1692651823164624126 + }]] diff --git a/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_unicode_request.json b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_unicode_request.json new file mode 100644 index 00000000000..c6db207fd51 --- /dev/null +++ b/tests/snapshots/tests.contrib.valkey.test_valkey_asyncio.test_unicode_request.json @@ -0,0 +1,37 @@ +[[ + { + "name": "valkey.command", + "service": "valkey", + "resource": "GET", + "trace_id": 0, + "span_id": 1, + "parent_id": 0, + "type": "valkey", + "error": 0, + "meta": { + "_dd.base_service": "tests.contrib.valkey", + "_dd.p.dm": "-0", + "_dd.p.tid": "654a694400000000", + "component": "valkey", + "db.system": "valkey", + "language": "python", + "out.host": "127.0.0.1", + "runtime-id": "3cf1df7fb079462ab81608355e026651", + "server.address": "127.0.0.1", + "span.kind": "client", + "valkey.raw_command": "GET \ud83d\ude10" + }, + "metrics": { + "_dd.measured": 1, + "_dd.top_level": 1, + "_dd.tracer_kr": 1.0, + "_sampling_priority_v1": 1, + "db.row_count": 0, + "network.destination.port": 6379, + "out.valkey_db": 0, + "process_id": 19999, + "valkey.args_length": 2 + }, + "duration": 300041, + "start": 1692651823049427543 + }]]