emergent-translator 1.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- emergent_translator/__init__.py +126 -0
- emergent_translator/adaptive_codebook.py +342 -0
- emergent_translator/api_server.py +4988 -0
- emergent_translator/batch_encoder.py +555 -0
- emergent_translator/chunk_collector.py +978 -0
- emergent_translator/chunk_coordinator.py +738 -0
- emergent_translator/claude_compression.py +375 -0
- emergent_translator/cli.py +413 -0
- emergent_translator/client_sdk.py +903 -0
- emergent_translator/code_skeleton.py +448 -0
- emergent_translator/core.py +1081 -0
- emergent_translator/emergent_symbols.py +690 -0
- emergent_translator/format_handlers.py +901 -0
- emergent_translator/gpu_batch_encoder.py +848 -0
- emergent_translator/intelligent_router.py +509 -0
- emergent_translator/metrics.py +436 -0
- emergent_translator/py.typed +0 -0
- emergent_translator-1.1.0.dist-info/METADATA +568 -0
- emergent_translator-1.1.0.dist-info/RECORD +23 -0
- emergent_translator-1.1.0.dist-info/WHEEL +5 -0
- emergent_translator-1.1.0.dist-info/entry_points.txt +2 -0
- emergent_translator-1.1.0.dist-info/licenses/LICENSE +82 -0
- emergent_translator-1.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Emergent Language Translator - Metrics & Event Emission
|
|
3
|
+
|
|
4
|
+
Real-time metrics collection and event broadcasting for stress testing.
|
|
5
|
+
Supports:
|
|
6
|
+
- Structured logging with JSON output
|
|
7
|
+
- PartyKit WebSocket event emission
|
|
8
|
+
- Prometheus-compatible metrics
|
|
9
|
+
- Real-time dashboard updates
|
|
10
|
+
|
|
11
|
+
Usage:
|
|
12
|
+
from .metrics import MetricsCollector, emit_event
|
|
13
|
+
|
|
14
|
+
metrics = MetricsCollector()
|
|
15
|
+
metrics.record_request(latency_ms=15.2, success=True, compression_ratio=0.016)
|
|
16
|
+
|
|
17
|
+
await emit_event("stress_test", {"instance": "vps-1", "rps": 150})
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import asyncio
|
|
21
|
+
import json
|
|
22
|
+
import logging
|
|
23
|
+
import time
|
|
24
|
+
from dataclasses import dataclass, field, asdict
|
|
25
|
+
from datetime import datetime
|
|
26
|
+
from typing import Dict, Any, Optional, List
|
|
27
|
+
from collections import deque
|
|
28
|
+
import os
|
|
29
|
+
|
|
30
|
+
import httpx
|
|
31
|
+
|
|
32
|
+
logger = logging.getLogger(__name__)
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class RequestMetric:
|
|
37
|
+
"""Single request metric."""
|
|
38
|
+
timestamp: float
|
|
39
|
+
latency_ms: float
|
|
40
|
+
success: bool
|
|
41
|
+
original_size: int = 0
|
|
42
|
+
compressed_size: int = 0
|
|
43
|
+
compression_ratio: float = 0.0
|
|
44
|
+
endpoint: str = "/translate"
|
|
45
|
+
client_ip: str = "unknown"
|
|
46
|
+
error: Optional[str] = None
|
|
47
|
+
cache_hit: bool = False
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class AggregatedMetrics:
|
|
52
|
+
"""Aggregated metrics for a time window."""
|
|
53
|
+
window_start: float
|
|
54
|
+
window_end: float
|
|
55
|
+
total_requests: int
|
|
56
|
+
successful_requests: int
|
|
57
|
+
failed_requests: int
|
|
58
|
+
requests_per_second: float
|
|
59
|
+
|
|
60
|
+
latency_min_ms: float
|
|
61
|
+
latency_max_ms: float
|
|
62
|
+
latency_avg_ms: float
|
|
63
|
+
latency_p50_ms: float
|
|
64
|
+
latency_p95_ms: float
|
|
65
|
+
latency_p99_ms: float
|
|
66
|
+
|
|
67
|
+
avg_compression_ratio: float
|
|
68
|
+
total_bytes_in: int
|
|
69
|
+
total_bytes_out: int
|
|
70
|
+
bandwidth_savings_percent: float
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class MetricsCollector:
|
|
74
|
+
"""
|
|
75
|
+
Collects and aggregates metrics for stress testing.
|
|
76
|
+
|
|
77
|
+
Features:
|
|
78
|
+
- Rolling window metrics
|
|
79
|
+
- Real-time event emission
|
|
80
|
+
- PartyKit integration
|
|
81
|
+
- Prometheus export
|
|
82
|
+
"""
|
|
83
|
+
|
|
84
|
+
def __init__(
|
|
85
|
+
self,
|
|
86
|
+
window_size: int = 60, # seconds
|
|
87
|
+
partykit_url: Optional[str] = None,
|
|
88
|
+
instance_id: str = "primary"
|
|
89
|
+
):
|
|
90
|
+
self.window_size = window_size
|
|
91
|
+
self.partykit_url = partykit_url or os.getenv("PARTYKIT_URL")
|
|
92
|
+
self.instance_id = instance_id
|
|
93
|
+
|
|
94
|
+
# Rolling metrics storage
|
|
95
|
+
self.recent_requests: deque = deque(maxlen=10000)
|
|
96
|
+
self.start_time = time.time()
|
|
97
|
+
|
|
98
|
+
# Counters
|
|
99
|
+
self.total_requests = 0
|
|
100
|
+
self.total_successful = 0
|
|
101
|
+
self.total_failed = 0
|
|
102
|
+
self.total_bytes_in = 0
|
|
103
|
+
self.total_bytes_out = 0
|
|
104
|
+
|
|
105
|
+
# HTTP client for PartyKit
|
|
106
|
+
self._http_client: Optional[httpx.AsyncClient] = None
|
|
107
|
+
|
|
108
|
+
async def _get_client(self) -> httpx.AsyncClient:
|
|
109
|
+
"""Get or create HTTP client."""
|
|
110
|
+
if self._http_client is None:
|
|
111
|
+
self._http_client = httpx.AsyncClient(timeout=5.0)
|
|
112
|
+
return self._http_client
|
|
113
|
+
|
|
114
|
+
async def close(self):
|
|
115
|
+
"""Close HTTP client."""
|
|
116
|
+
if self._http_client:
|
|
117
|
+
await self._http_client.aclose()
|
|
118
|
+
self._http_client = None
|
|
119
|
+
|
|
120
|
+
def record_request(
|
|
121
|
+
self,
|
|
122
|
+
latency_ms: float,
|
|
123
|
+
success: bool,
|
|
124
|
+
original_size: int = 0,
|
|
125
|
+
compressed_size: int = 0,
|
|
126
|
+
compression_ratio: float = 0.0,
|
|
127
|
+
endpoint: str = "/translate",
|
|
128
|
+
client_ip: str = "unknown",
|
|
129
|
+
error: Optional[str] = None,
|
|
130
|
+
cache_hit: bool = False
|
|
131
|
+
):
|
|
132
|
+
"""Record a single request metric."""
|
|
133
|
+
metric = RequestMetric(
|
|
134
|
+
timestamp=time.time(),
|
|
135
|
+
latency_ms=latency_ms,
|
|
136
|
+
success=success,
|
|
137
|
+
original_size=original_size,
|
|
138
|
+
compressed_size=compressed_size,
|
|
139
|
+
compression_ratio=compression_ratio,
|
|
140
|
+
endpoint=endpoint,
|
|
141
|
+
client_ip=client_ip,
|
|
142
|
+
error=error,
|
|
143
|
+
cache_hit=cache_hit
|
|
144
|
+
)
|
|
145
|
+
|
|
146
|
+
self.recent_requests.append(metric)
|
|
147
|
+
self.total_requests += 1
|
|
148
|
+
|
|
149
|
+
if success:
|
|
150
|
+
self.total_successful += 1
|
|
151
|
+
self.total_bytes_in += original_size
|
|
152
|
+
self.total_bytes_out += compressed_size
|
|
153
|
+
else:
|
|
154
|
+
self.total_failed += 1
|
|
155
|
+
|
|
156
|
+
def get_window_metrics(self, window_seconds: Optional[int] = None) -> AggregatedMetrics:
|
|
157
|
+
"""Get aggregated metrics for a time window."""
|
|
158
|
+
window = window_seconds or self.window_size
|
|
159
|
+
now = time.time()
|
|
160
|
+
cutoff = now - window
|
|
161
|
+
|
|
162
|
+
# Filter to window
|
|
163
|
+
window_requests = [r for r in self.recent_requests if r.timestamp >= cutoff]
|
|
164
|
+
|
|
165
|
+
if not window_requests:
|
|
166
|
+
return AggregatedMetrics(
|
|
167
|
+
window_start=cutoff,
|
|
168
|
+
window_end=now,
|
|
169
|
+
total_requests=0,
|
|
170
|
+
successful_requests=0,
|
|
171
|
+
failed_requests=0,
|
|
172
|
+
requests_per_second=0,
|
|
173
|
+
latency_min_ms=0,
|
|
174
|
+
latency_max_ms=0,
|
|
175
|
+
latency_avg_ms=0,
|
|
176
|
+
latency_p50_ms=0,
|
|
177
|
+
latency_p95_ms=0,
|
|
178
|
+
latency_p99_ms=0,
|
|
179
|
+
avg_compression_ratio=0,
|
|
180
|
+
total_bytes_in=0,
|
|
181
|
+
total_bytes_out=0,
|
|
182
|
+
bandwidth_savings_percent=0
|
|
183
|
+
)
|
|
184
|
+
|
|
185
|
+
successful = [r for r in window_requests if r.success]
|
|
186
|
+
failed = [r for r in window_requests if not r.success]
|
|
187
|
+
|
|
188
|
+
# Latency percentiles
|
|
189
|
+
latencies = sorted([r.latency_ms for r in window_requests])
|
|
190
|
+
n = len(latencies)
|
|
191
|
+
|
|
192
|
+
bytes_in = sum(r.original_size for r in successful)
|
|
193
|
+
bytes_out = sum(r.compressed_size for r in successful)
|
|
194
|
+
|
|
195
|
+
return AggregatedMetrics(
|
|
196
|
+
window_start=cutoff,
|
|
197
|
+
window_end=now,
|
|
198
|
+
total_requests=len(window_requests),
|
|
199
|
+
successful_requests=len(successful),
|
|
200
|
+
failed_requests=len(failed),
|
|
201
|
+
requests_per_second=len(window_requests) / window,
|
|
202
|
+
latency_min_ms=min(latencies),
|
|
203
|
+
latency_max_ms=max(latencies),
|
|
204
|
+
latency_avg_ms=sum(latencies) / n,
|
|
205
|
+
latency_p50_ms=latencies[int(n * 0.5)],
|
|
206
|
+
latency_p95_ms=latencies[int(n * 0.95)] if n > 1 else latencies[-1],
|
|
207
|
+
latency_p99_ms=latencies[int(n * 0.99)] if n > 1 else latencies[-1],
|
|
208
|
+
avg_compression_ratio=sum(r.compression_ratio for r in successful) / len(successful) if successful else 0,
|
|
209
|
+
total_bytes_in=bytes_in,
|
|
210
|
+
total_bytes_out=bytes_out,
|
|
211
|
+
bandwidth_savings_percent=((bytes_in - bytes_out) / bytes_in * 100) if bytes_in > 0 else 0
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
def get_lifetime_stats(self) -> Dict[str, Any]:
|
|
215
|
+
"""Get lifetime statistics."""
|
|
216
|
+
uptime = time.time() - self.start_time
|
|
217
|
+
return {
|
|
218
|
+
"instance_id": self.instance_id,
|
|
219
|
+
"uptime_seconds": uptime,
|
|
220
|
+
"total_requests": self.total_requests,
|
|
221
|
+
"total_successful": self.total_successful,
|
|
222
|
+
"total_failed": self.total_failed,
|
|
223
|
+
"success_rate": (self.total_successful / self.total_requests * 100) if self.total_requests > 0 else 0,
|
|
224
|
+
"requests_per_second": self.total_requests / uptime if uptime > 0 else 0,
|
|
225
|
+
"total_bytes_in": self.total_bytes_in,
|
|
226
|
+
"total_bytes_out": self.total_bytes_out,
|
|
227
|
+
"bandwidth_savings_percent": ((self.total_bytes_in - self.total_bytes_out) / self.total_bytes_in * 100) if self.total_bytes_in > 0 else 0
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
async def emit_to_partykit(self, event_type: str, data: Dict[str, Any]):
|
|
231
|
+
"""Emit event to PartyKit room."""
|
|
232
|
+
if not self.partykit_url:
|
|
233
|
+
return
|
|
234
|
+
|
|
235
|
+
try:
|
|
236
|
+
client = await self._get_client()
|
|
237
|
+
|
|
238
|
+
event = {
|
|
239
|
+
"type": event_type,
|
|
240
|
+
"instance_id": self.instance_id,
|
|
241
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
242
|
+
"data": data
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
# POST to PartyKit HTTP endpoint
|
|
246
|
+
await client.post(
|
|
247
|
+
f"{self.partykit_url}/parties/main/stress-test",
|
|
248
|
+
json=event
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
except Exception as e:
|
|
252
|
+
logger.debug(f"PartyKit emit failed: {e}")
|
|
253
|
+
|
|
254
|
+
def to_prometheus(self) -> str:
|
|
255
|
+
"""Export metrics in Prometheus format."""
|
|
256
|
+
stats = self.get_lifetime_stats()
|
|
257
|
+
window = self.get_window_metrics(60)
|
|
258
|
+
|
|
259
|
+
lines = [
|
|
260
|
+
f'# HELP emergent_requests_total Total number of translation requests',
|
|
261
|
+
f'# TYPE emergent_requests_total counter',
|
|
262
|
+
f'emergent_requests_total{{instance="{self.instance_id}"}} {stats["total_requests"]}',
|
|
263
|
+
f'',
|
|
264
|
+
f'# HELP emergent_requests_successful_total Successful requests',
|
|
265
|
+
f'# TYPE emergent_requests_successful_total counter',
|
|
266
|
+
f'emergent_requests_successful_total{{instance="{self.instance_id}"}} {stats["total_successful"]}',
|
|
267
|
+
f'',
|
|
268
|
+
f'# HELP emergent_requests_failed_total Failed requests',
|
|
269
|
+
f'# TYPE emergent_requests_failed_total counter',
|
|
270
|
+
f'emergent_requests_failed_total{{instance="{self.instance_id}"}} {stats["total_failed"]}',
|
|
271
|
+
f'',
|
|
272
|
+
f'# HELP emergent_latency_ms Request latency in milliseconds',
|
|
273
|
+
f'# TYPE emergent_latency_ms summary',
|
|
274
|
+
f'emergent_latency_ms{{instance="{self.instance_id}",quantile="0.5"}} {window.latency_p50_ms}',
|
|
275
|
+
f'emergent_latency_ms{{instance="{self.instance_id}",quantile="0.95"}} {window.latency_p95_ms}',
|
|
276
|
+
f'emergent_latency_ms{{instance="{self.instance_id}",quantile="0.99"}} {window.latency_p99_ms}',
|
|
277
|
+
f'',
|
|
278
|
+
f'# HELP emergent_compression_ratio Average compression ratio',
|
|
279
|
+
f'# TYPE emergent_compression_ratio gauge',
|
|
280
|
+
f'emergent_compression_ratio{{instance="{self.instance_id}"}} {window.avg_compression_ratio}',
|
|
281
|
+
f'',
|
|
282
|
+
f'# HELP emergent_rps Requests per second (60s window)',
|
|
283
|
+
f'# TYPE emergent_rps gauge',
|
|
284
|
+
f'emergent_rps{{instance="{self.instance_id}"}} {window.requests_per_second}',
|
|
285
|
+
f'',
|
|
286
|
+
f'# HELP emergent_bytes_in_total Total bytes received',
|
|
287
|
+
f'# TYPE emergent_bytes_in_total counter',
|
|
288
|
+
f'emergent_bytes_in_total{{instance="{self.instance_id}"}} {stats["total_bytes_in"]}',
|
|
289
|
+
f'',
|
|
290
|
+
f'# HELP emergent_bytes_out_total Total bytes sent (compressed)',
|
|
291
|
+
f'# TYPE emergent_bytes_out_total counter',
|
|
292
|
+
f'emergent_bytes_out_total{{instance="{self.instance_id}"}} {stats["total_bytes_out"]}',
|
|
293
|
+
]
|
|
294
|
+
|
|
295
|
+
return '\n'.join(lines)
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
# Global metrics instance
|
|
299
|
+
_metrics: Optional[MetricsCollector] = None
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def get_metrics() -> MetricsCollector:
|
|
303
|
+
"""Get or create global metrics collector."""
|
|
304
|
+
global _metrics
|
|
305
|
+
if _metrics is None:
|
|
306
|
+
_metrics = MetricsCollector(
|
|
307
|
+
instance_id=os.getenv("FLY_ALLOC_ID", "local")[:8]
|
|
308
|
+
)
|
|
309
|
+
return _metrics
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
async def emit_event(event_type: str, data: Dict[str, Any]):
|
|
313
|
+
"""Emit event to PartyKit (convenience function)."""
|
|
314
|
+
metrics = get_metrics()
|
|
315
|
+
await metrics.emit_to_partykit(event_type, data)
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
# Structured logging setup
|
|
319
|
+
|
|
320
|
+
class JSONFormatter(logging.Formatter):
|
|
321
|
+
"""JSON log formatter for structured logging."""
|
|
322
|
+
|
|
323
|
+
def format(self, record: logging.LogRecord) -> str:
|
|
324
|
+
log_data = {
|
|
325
|
+
"timestamp": datetime.utcnow().isoformat(),
|
|
326
|
+
"level": record.levelname,
|
|
327
|
+
"logger": record.name,
|
|
328
|
+
"message": record.getMessage(),
|
|
329
|
+
"instance": os.getenv("FLY_ALLOC_ID", "local")[:8],
|
|
330
|
+
"region": os.getenv("FLY_REGION", "local"),
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
# Add extra fields
|
|
334
|
+
if hasattr(record, "request_id"):
|
|
335
|
+
log_data["request_id"] = record.request_id
|
|
336
|
+
if hasattr(record, "latency_ms"):
|
|
337
|
+
log_data["latency_ms"] = record.latency_ms
|
|
338
|
+
if hasattr(record, "compression_ratio"):
|
|
339
|
+
log_data["compression_ratio"] = record.compression_ratio
|
|
340
|
+
if hasattr(record, "client_ip"):
|
|
341
|
+
log_data["client_ip"] = record.client_ip
|
|
342
|
+
|
|
343
|
+
# Add exception info
|
|
344
|
+
if record.exc_info:
|
|
345
|
+
log_data["exception"] = self.formatException(record.exc_info)
|
|
346
|
+
|
|
347
|
+
return json.dumps(log_data)
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
def setup_structured_logging(level: str = "INFO"):
|
|
351
|
+
"""Configure structured JSON logging."""
|
|
352
|
+
root_logger = logging.getLogger()
|
|
353
|
+
root_logger.setLevel(getattr(logging, level.upper()))
|
|
354
|
+
|
|
355
|
+
# Remove existing handlers
|
|
356
|
+
for handler in root_logger.handlers[:]:
|
|
357
|
+
root_logger.removeHandler(handler)
|
|
358
|
+
|
|
359
|
+
# Add JSON handler
|
|
360
|
+
handler = logging.StreamHandler()
|
|
361
|
+
handler.setFormatter(JSONFormatter())
|
|
362
|
+
root_logger.addHandler(handler)
|
|
363
|
+
|
|
364
|
+
logger.info("Structured logging configured", extra={"level": level})
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
# Stress test event types
|
|
368
|
+
|
|
369
|
+
class StressTestEvents:
|
|
370
|
+
"""Event types for stress test monitoring."""
|
|
371
|
+
|
|
372
|
+
TEST_STARTED = "stress_test.started"
|
|
373
|
+
TEST_PROGRESS = "stress_test.progress"
|
|
374
|
+
TEST_COMPLETED = "stress_test.completed"
|
|
375
|
+
|
|
376
|
+
INSTANCE_JOINED = "instance.joined"
|
|
377
|
+
INSTANCE_HEARTBEAT = "instance.heartbeat"
|
|
378
|
+
INSTANCE_LEFT = "instance.left"
|
|
379
|
+
|
|
380
|
+
METRICS_UPDATE = "metrics.update"
|
|
381
|
+
ERROR_OCCURRED = "error.occurred"
|
|
382
|
+
|
|
383
|
+
SCALING_EVENT = "scaling.event"
|
|
384
|
+
|
|
385
|
+
|
|
386
|
+
async def emit_test_started(
|
|
387
|
+
instance_id: str,
|
|
388
|
+
target_url: str,
|
|
389
|
+
num_clients: int,
|
|
390
|
+
requests_per_client: int
|
|
391
|
+
):
|
|
392
|
+
"""Emit stress test started event."""
|
|
393
|
+
await emit_event(StressTestEvents.TEST_STARTED, {
|
|
394
|
+
"instance_id": instance_id,
|
|
395
|
+
"target_url": target_url,
|
|
396
|
+
"num_clients": num_clients,
|
|
397
|
+
"requests_per_client": requests_per_client,
|
|
398
|
+
"total_requests": num_clients * requests_per_client
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
|
|
402
|
+
async def emit_test_progress(
|
|
403
|
+
instance_id: str,
|
|
404
|
+
completed: int,
|
|
405
|
+
total: int,
|
|
406
|
+
current_rps: float,
|
|
407
|
+
avg_latency_ms: float
|
|
408
|
+
):
|
|
409
|
+
"""Emit stress test progress event."""
|
|
410
|
+
await emit_event(StressTestEvents.TEST_PROGRESS, {
|
|
411
|
+
"instance_id": instance_id,
|
|
412
|
+
"completed": completed,
|
|
413
|
+
"total": total,
|
|
414
|
+
"percent": (completed / total * 100) if total > 0 else 0,
|
|
415
|
+
"current_rps": current_rps,
|
|
416
|
+
"avg_latency_ms": avg_latency_ms
|
|
417
|
+
})
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
async def emit_test_completed(
|
|
421
|
+
instance_id: str,
|
|
422
|
+
report: Dict[str, Any]
|
|
423
|
+
):
|
|
424
|
+
"""Emit stress test completed event."""
|
|
425
|
+
await emit_event(StressTestEvents.TEST_COMPLETED, {
|
|
426
|
+
"instance_id": instance_id,
|
|
427
|
+
"report": report
|
|
428
|
+
})
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
async def emit_metrics_update(window_seconds: int = 10):
|
|
432
|
+
"""Emit current metrics update."""
|
|
433
|
+
metrics = get_metrics()
|
|
434
|
+
window = metrics.get_window_metrics(window_seconds)
|
|
435
|
+
|
|
436
|
+
await emit_event(StressTestEvents.METRICS_UPDATE, asdict(window))
|
|
File without changes
|