Skip to content

Commit 435dec2

Browse files
fix: redis instrumentation fixes (#24)
1 parent 8aded86 commit 435dec2

File tree

7 files changed

+433
-41
lines changed

7 files changed

+433
-41
lines changed

drift/core/communication/types.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,9 +82,8 @@ def _python_to_value(value: Any) -> Any:
8282
from betterproto.lib.google.protobuf import ListValue, Value
8383

8484
if value is None:
85-
from betterproto.lib.google.protobuf import NullValue
86-
87-
return Value(null_value=NullValue.NULL_VALUE) # type: ignore[arg-type]
85+
# betterproto 2.0.0b7 uses integer 0 for null value (NullValue.NULL_VALUE doesn't exist)
86+
return Value(null_value=0) # type: ignore[arg-type]
8887
elif isinstance(value, bool):
8988
return Value(bool_value=value)
9089
elif isinstance(value, (int, float)):

drift/core/span_serialization.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,8 @@ def _value_to_proto(value: Any) -> ProtoValue:
3737
proto_value = ProtoValue()
3838

3939
if value is None:
40-
from betterproto.lib.google.protobuf import NullValue
41-
42-
proto_value.null_value = NullValue.NULL_VALUE # type: ignore[assignment]
40+
# betterproto 2.0.0b7 uses integer 0 for null value (NullValue.NULL_VALUE doesn't exist)
41+
proto_value.null_value = 0 # type: ignore[assignment]
4342
elif isinstance(value, bool):
4443
proto_value.bool_value = value
4544
elif isinstance(value, (int, float)):

drift/core/tracing/adapters/api.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,9 +239,8 @@ def _dict_to_struct(data: dict[str, Any]) -> Struct:
239239
def value_to_proto(val: Any) -> Value:
240240
"""Convert a Python value to protobuf Value."""
241241
if val is None:
242-
from betterproto.lib.google.protobuf import NullValue
243-
244-
return Value(null_value=NullValue.NULL_VALUE) # type: ignore[arg-type]
242+
# betterproto 2.0.0b7 uses integer 0 for null value (NullValue.NULL_VALUE doesn't exist)
243+
return Value(null_value=0) # type: ignore[arg-type]
245244
elif isinstance(val, bool):
246245
return Value(bool_value=val)
247246
elif isinstance(val, (int, float)):

drift/instrumentation/redis/e2e-tests/src/app.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,162 @@ def redis_keys(pattern):
8989
return jsonify({"error": str(e)}), 500
9090

9191

92+
@app.route("/test/mget-mset", methods=["GET"])
93+
def test_mget_mset():
94+
"""Test MGET/MSET - multiple key operations."""
95+
try:
96+
# MSET multiple keys
97+
redis_client.mset({"test:mset:key1": "value1", "test:mset:key2": "value2", "test:mset:key3": "value3"})
98+
# MGET multiple keys
99+
result = redis_client.mget(["test:mset:key1", "test:mset:key2", "test:mset:key3", "test:mset:nonexistent"])
100+
# Clean up
101+
redis_client.delete("test:mset:key1", "test:mset:key2", "test:mset:key3")
102+
return jsonify({"success": True, "result": result})
103+
except Exception as e:
104+
return jsonify({"error": str(e)}), 500
105+
106+
107+
@app.route("/test/pipeline-basic", methods=["GET"])
108+
def test_pipeline_basic():
109+
"""Test basic pipeline operations."""
110+
try:
111+
pipe = redis_client.pipeline()
112+
pipe.set("test:pipe:key1", "value1")
113+
pipe.set("test:pipe:key2", "value2")
114+
pipe.get("test:pipe:key1")
115+
pipe.get("test:pipe:key2")
116+
results = pipe.execute()
117+
# Clean up
118+
redis_client.delete("test:pipe:key1", "test:pipe:key2")
119+
return jsonify({"success": True, "results": results})
120+
except Exception as e:
121+
return jsonify({"error": str(e)}), 500
122+
123+
124+
@app.route("/test/pipeline-no-transaction", methods=["GET"])
125+
def test_pipeline_no_transaction():
126+
"""Test pipeline with transaction=False."""
127+
try:
128+
pipe = redis_client.pipeline(transaction=False)
129+
pipe.set("test:pipe:notx:key1", "value1")
130+
pipe.incr("test:pipe:notx:counter")
131+
pipe.get("test:pipe:notx:key1")
132+
results = pipe.execute()
133+
# Clean up
134+
redis_client.delete("test:pipe:notx:key1", "test:pipe:notx:counter")
135+
return jsonify({"success": True, "results": results})
136+
except Exception as e:
137+
return jsonify({"error": str(e)}), 500
138+
139+
140+
@app.route("/test/async-pipeline", methods=["GET"])
141+
def test_async_pipeline():
142+
"""Test async pipeline operations using asyncio."""
143+
import asyncio
144+
145+
import redis.asyncio as aioredis
146+
147+
async def run_async_pipeline():
148+
# Create async Redis client
149+
async_client = aioredis.Redis(
150+
host=os.getenv("REDIS_HOST", "redis"),
151+
port=int(os.getenv("REDIS_PORT", "6379")),
152+
db=0,
153+
decode_responses=True,
154+
)
155+
156+
try:
157+
# Create async pipeline
158+
pipe = async_client.pipeline()
159+
pipe.set("test:async:pipe:key1", "async_value1")
160+
pipe.set("test:async:pipe:key2", "async_value2")
161+
pipe.get("test:async:pipe:key1")
162+
pipe.get("test:async:pipe:key2")
163+
results = await pipe.execute()
164+
165+
# Clean up
166+
await async_client.delete("test:async:pipe:key1", "test:async:pipe:key2")
167+
168+
return results
169+
finally:
170+
await async_client.aclose()
171+
172+
try:
173+
results = asyncio.run(run_async_pipeline())
174+
return jsonify({"success": True, "results": results})
175+
except Exception as e:
176+
return jsonify({"error": str(e)}), 500
177+
178+
179+
@app.route("/test/binary-data", methods=["GET"])
180+
def test_binary_data():
181+
"""Test binary data that cannot be decoded as UTF-8."""
182+
try:
183+
# Create a Redis client without decode_responses for binary data
184+
binary_client = redis.Redis(
185+
host=os.getenv("REDIS_HOST", "redis"),
186+
port=int(os.getenv("REDIS_PORT", "6379")),
187+
db=0,
188+
decode_responses=False,
189+
)
190+
191+
# Binary data that cannot be decoded as UTF-8
192+
binary_value = bytes([0x80, 0x81, 0x82, 0xFF, 0xFE, 0xFD])
193+
194+
# Set binary data
195+
binary_client.set("test:binary:key", binary_value)
196+
197+
# Get binary data back
198+
retrieved = binary_client.get("test:binary:key")
199+
200+
# Clean up
201+
binary_client.delete("test:binary:key")
202+
203+
return jsonify(
204+
{
205+
"success": True,
206+
"original_hex": binary_value.hex(),
207+
"retrieved_hex": retrieved.hex() if retrieved else None,
208+
"match": binary_value == retrieved,
209+
}
210+
)
211+
except Exception as e:
212+
return jsonify({"error": str(e)}), 500
213+
214+
215+
@app.route("/test/transaction-watch", methods=["GET"])
216+
def test_transaction_watch():
217+
"""Test transaction with WATCH pattern.
218+
219+
This tests whether WATCH/MULTI/EXEC transaction pattern works correctly.
220+
"""
221+
try:
222+
# Set initial value
223+
redis_client.set("test:watch:counter", "10")
224+
225+
# Start a watched transaction
226+
pipe = redis_client.pipeline(transaction=True)
227+
pipe.watch("test:watch:counter")
228+
229+
# Get current value (this happens outside the transaction)
230+
current = int(redis_client.get("test:watch:counter"))
231+
232+
# Start the transaction
233+
pipe.multi()
234+
pipe.set("test:watch:counter", str(current + 5))
235+
pipe.get("test:watch:counter")
236+
237+
# Execute
238+
results = pipe.execute()
239+
240+
# Clean up
241+
redis_client.delete("test:watch:counter")
242+
243+
return jsonify({"success": True, "initial_value": 10, "expected_final": 15, "results": results})
244+
except Exception as e:
245+
return jsonify({"error": str(e)}), 500
246+
247+
92248
if __name__ == "__main__":
93249
sdk.mark_app_as_ready()
94250
app.run(host="0.0.0.0", port=8000, debug=False)

drift/instrumentation/redis/e2e-tests/src/test_requests.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@ def make_request(method, endpoint, **kwargs):
3333
# Get operations
3434
make_request("GET", "/redis/get/test_key")
3535
make_request("GET", "/redis/get/test_key_expiry")
36-
# TODO: figure out why this test fails during replay
37-
# make_request("GET", "/redis/get/nonexistent_key")
36+
make_request("GET", "/redis/get/nonexistent_key")
3837

3938
# Increment operations
4039
make_request("POST", "/redis/incr/counter")
@@ -49,4 +48,18 @@ def make_request(method, endpoint, **kwargs):
4948
make_request("DELETE", "/redis/delete/test_key")
5049
make_request("DELETE", "/redis/delete/counter")
5150

51+
make_request("GET", "/test/mget-mset")
52+
53+
# Pipeline operations
54+
make_request("GET", "/test/pipeline-basic")
55+
make_request("GET", "/test/pipeline-no-transaction")
56+
57+
# Async Pipeline operations
58+
make_request("GET", "/test/async-pipeline")
59+
60+
# Binary data handling
61+
make_request("GET", "/test/binary-data")
62+
63+
make_request("GET", "/test/transaction-watch")
64+
5265
print("\nAll requests completed successfully")

0 commit comments

Comments
 (0)