forked from Ikalus1988/MisakaNet
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_telemetry_pipeline.py
More file actions
192 lines (153 loc) · 6.58 KB
/
Copy pathtest_telemetry_pipeline.py
File metadata and controls
192 lines (153 loc) · 6.58 KB
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
"""Tests for TelemetryPipeline — async producer-consumer pipeline."""
from __future__ import annotations
import asyncio
import sqlite3
import tempfile
import unittest
from pathlib import Path
from misakanet.tools.telemetry_pipeline import (
BATCH_SIZE,
QUEUE_MAXSIZE,
TelemetryPipeline,
_ensure_schema,
)
def _count_rows(db_path: Path) -> int:
conn = sqlite3.connect(str(db_path))
try:
count = conn.execute("SELECT COUNT(*) FROM search_telemetry").fetchone()[0]
return count
finally:
conn.close()
def _get_all_rows(db_path: Path) -> list[tuple]:
conn = sqlite3.connect(str(db_path))
try:
rows = conn.execute(
"SELECT query, latency_ms, cache_hit FROM search_telemetry ORDER BY timestamp"
).fetchall()
return rows
finally:
conn.close()
class TestTelemetryPipeline(unittest.IsolatedAsyncioTestCase):
async def asyncSetUp(self):
self.tmpdir = tempfile.mkdtemp()
self.db_path = Path(self.tmpdir) / "test_telemetry.db"
async def asyncTearDown(self):
pass # tmpdir cleaned up by OS
async def test_emit_and_batch_flush(self):
"""Emit events and verify they're flushed to DB."""
async with TelemetryPipeline(self.db_path) as pipeline:
for i in range(5):
await pipeline.emit(f"query_{i}", float(i * 10), cache_hit=(i % 2 == 0))
# Wait for consumer to flush
await asyncio.sleep(2.0)
# After shutdown, all events should be flushed
rows = _get_all_rows(self.db_path)
self.assertEqual(len(rows), 5)
self.assertEqual(rows[0][0], "query_0")
self.assertEqual(rows[0][2], 1) # cache_hit = True -> 1
self.assertEqual(rows[1][2], 0) # cache_hit = False -> 0
async def test_batch_size_trigger(self):
"""Verify flush triggers at BATCH_SIZE threshold."""
async with TelemetryPipeline(self.db_path) as pipeline:
# Emit exactly BATCH_SIZE events
for i in range(BATCH_SIZE):
await pipeline.emit(f"batch_{i}", 1.0, cache_hit=False)
# Should flush immediately (batch size reached)
await asyncio.sleep(0.5)
rows = _get_all_rows(self.db_path)
self.assertEqual(len(rows), BATCH_SIZE)
async def test_backpressure_fallback(self):
"""Fill queue to capacity, verify fallback to sync write."""
async with TelemetryPipeline(self.db_path) as pipeline:
# Fill the queue completely
for i in range(QUEUE_MAXSIZE):
await pipeline.emit(f"fill_{i}", 1.0, cache_hit=False)
# Next emit should trigger fallback
await pipeline.emit("overflow", 1.0, cache_hit=True)
# Wait for consumer
await asyncio.sleep(2.0)
# All events should be persisted (some via fallback)
total = _count_rows(self.db_path)
self.assertEqual(total, QUEUE_MAXSIZE + 1)
async def test_graceful_shutdown_flushes(self):
"""Shutdown should flush remaining events in queue."""
pipeline = TelemetryPipeline(self.db_path)
await pipeline.start()
# Emit some events
for i in range(3):
await pipeline.emit(f"shutdown_{i}", 2.0, cache_hit=True)
# Shutdown before consumer has a chance to flush
await pipeline.shutdown()
# All events should be flushed
rows = _get_all_rows(self.db_path)
self.assertEqual(len(rows), 3)
async def test_get_summary_empty(self):
"""Summary on empty DB returns zeros."""
async with TelemetryPipeline(self.db_path) as pipeline:
summary = await pipeline.get_summary()
self.assertEqual(summary["total_searches"], 0)
self.assertEqual(summary["cache_hit_rate"], 0.0)
self.assertEqual(summary["avg_latency_ms"], 0.0)
self.assertEqual(summary["saved_time_ms"], 0)
async def test_get_summary_with_data(self):
"""Summary returns correct aggregates."""
async with TelemetryPipeline(self.db_path) as pipeline:
# 3 cache hits, 2 misses
await pipeline.emit("q1", 10.0, cache_hit=True)
await pipeline.emit("q2", 20.0, cache_hit=False)
await pipeline.emit("q3", 30.0, cache_hit=True)
await pipeline.emit("q4", 40.0, cache_hit=False)
await pipeline.emit("q5", 50.0, cache_hit=True)
await asyncio.sleep(2.0)
summary = await pipeline.get_summary()
self.assertEqual(summary["total_searches"], 5)
self.assertAlmostEqual(summary["cache_hit_rate"], 0.6)
self.assertAlmostEqual(summary["avg_latency_ms"], 30.0)
async def test_schema_creation(self):
"""Verify schema is created correctly."""
conn = sqlite3.connect(str(self.db_path))
_ensure_schema(conn)
# Check search_telemetry table
columns = {
row[1]
for row in conn.execute("PRAGMA table_info(search_telemetry)").fetchall()
}
self.assertIn("query", columns)
self.assertIn("timestamp", columns)
self.assertIn("latency_ms", columns)
self.assertIn("cache_hit", columns)
self.assertIn("query_signature", columns)
# Check local_blacklist table
bl_columns = {
row[1]
for row in conn.execute("PRAGMA table_info(local_blacklist)").fetchall()
}
self.assertIn("blocked_until", bl_columns)
self.assertIn("reason", bl_columns)
self.assertIn("hit_count", bl_columns)
conn.close()
async def test_query_signature_stored(self):
"""Verify query_signature is stored correctly."""
async with TelemetryPipeline(self.db_path) as pipeline:
await pipeline.emit("test_query", 15.0, cache_hit=False, query_signature="sig_123")
await asyncio.sleep(2.0)
conn = sqlite3.connect(str(self.db_path))
try:
row = conn.execute(
"SELECT query_signature FROM search_telemetry LIMIT 1"
).fetchone()
self.assertEqual(row[0], "sig_123")
finally:
conn.close()
async def test_double_shutdown_idempotent(self):
"""Calling shutdown twice should not raise."""
pipeline = TelemetryPipeline(self.db_path)
await pipeline.start()
await pipeline.emit("q", 1.0, cache_hit=False)
await pipeline.shutdown()
# Second shutdown should be a no-op
await pipeline.shutdown()
rows = _get_all_rows(self.db_path)
self.assertEqual(len(rows), 1)
if __name__ == "__main__":
unittest.main()