qyro 2.0.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.
- qyro/__init__.py +17 -0
- qyro/adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/__init__.py +4 -0
- qyro/adapters/language_adapters/c/__init__.py +4 -0
- qyro/adapters/language_adapters/python/__init__.py +4 -0
- qyro/adapters/language_adapters/python/python_adapter.py +584 -0
- qyro/cli/__init__.py +8 -0
- qyro/cli/__main__.py +5 -0
- qyro/cli/cli.py +392 -0
- qyro/cli/interactive.py +297 -0
- qyro/common/__init__.py +37 -0
- qyro/common/animation.py +82 -0
- qyro/common/builder.py +434 -0
- qyro/common/compiler.py +895 -0
- qyro/common/config.py +93 -0
- qyro/common/constants.py +99 -0
- qyro/common/errors.py +176 -0
- qyro/common/frontend.py +74 -0
- qyro/common/health.py +358 -0
- qyro/common/kafka_manager.py +192 -0
- qyro/common/logging.py +149 -0
- qyro/common/memory.py +147 -0
- qyro/common/metrics.py +301 -0
- qyro/common/monitoring.py +468 -0
- qyro/common/parser.py +91 -0
- qyro/common/platform.py +609 -0
- qyro/common/redis_memory.py +1108 -0
- qyro/common/rpc.py +287 -0
- qyro/common/sandbox.py +191 -0
- qyro/common/schema_loader.py +33 -0
- qyro/common/secure_sandbox.py +490 -0
- qyro/common/toolchain_validator.py +617 -0
- qyro/common/type_generator.py +176 -0
- qyro/common/validation.py +401 -0
- qyro/common/validator.py +204 -0
- qyro/gateway/__init__.py +8 -0
- qyro/gateway/gateway.py +303 -0
- qyro/orchestrator/__init__.py +8 -0
- qyro/orchestrator/orchestrator.py +1223 -0
- qyro-2.0.0.dist-info/METADATA +244 -0
- qyro-2.0.0.dist-info/RECORD +45 -0
- qyro-2.0.0.dist-info/WHEEL +5 -0
- qyro-2.0.0.dist-info/entry_points.txt +2 -0
- qyro-2.0.0.dist-info/licenses/LICENSE +21 -0
- qyro-2.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Qyro Monitoring and Observability System
|
|
3
|
+
Production-grade metrics, health checks, and observability for the Qyro runtime.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import time
|
|
7
|
+
import threading
|
|
8
|
+
import json
|
|
9
|
+
from typing import Dict, Any, List, Optional, Callable
|
|
10
|
+
from dataclasses import dataclass, field
|
|
11
|
+
from datetime import datetime
|
|
12
|
+
from enum import Enum
|
|
13
|
+
import psutil
|
|
14
|
+
import os
|
|
15
|
+
import logging
|
|
16
|
+
from collections import deque
|
|
17
|
+
from contextlib import contextmanager
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Severity(Enum):
|
|
21
|
+
"""Log severity levels."""
|
|
22
|
+
DEBUG = 10
|
|
23
|
+
INFO = 20
|
|
24
|
+
WARNING = 30
|
|
25
|
+
ERROR = 40
|
|
26
|
+
CRITICAL = 50
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass
|
|
30
|
+
class LogEntry:
|
|
31
|
+
"""A single log entry."""
|
|
32
|
+
timestamp: float
|
|
33
|
+
level: Severity
|
|
34
|
+
message: str
|
|
35
|
+
module: str
|
|
36
|
+
details: Dict[str, Any] = field(default_factory=dict)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class MetricPoint:
|
|
41
|
+
"""A single metric measurement."""
|
|
42
|
+
name: str
|
|
43
|
+
value: float
|
|
44
|
+
timestamp: float
|
|
45
|
+
labels: Dict[str, str] = field(default_factory=dict)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class MetricsCollector:
|
|
49
|
+
"""Collects and stores metrics for the Qyro system."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, retention_minutes: int = 60):
|
|
52
|
+
self.retention_seconds = retention_minutes * 60
|
|
53
|
+
self.metrics: Dict[str, deque] = {}
|
|
54
|
+
self._lock = threading.Lock()
|
|
55
|
+
self.start_time = time.time()
|
|
56
|
+
|
|
57
|
+
def record_metric(self, name: str, value: float, labels: Dict[str, str] = None):
|
|
58
|
+
"""Record a metric value."""
|
|
59
|
+
labels = labels or {}
|
|
60
|
+
key = f"{name}:{json.dumps(labels, sort_keys=True)}"
|
|
61
|
+
|
|
62
|
+
with self._lock:
|
|
63
|
+
if key not in self.metrics:
|
|
64
|
+
self.metrics[key] = deque()
|
|
65
|
+
|
|
66
|
+
# Remove old metrics beyond retention period
|
|
67
|
+
now = time.time()
|
|
68
|
+
while self.metrics[key] and (now - self.metrics[key][0].timestamp) > self.retention_seconds:
|
|
69
|
+
self.metrics[key].popleft()
|
|
70
|
+
|
|
71
|
+
metric_point = MetricPoint(
|
|
72
|
+
name=name,
|
|
73
|
+
value=value,
|
|
74
|
+
timestamp=now,
|
|
75
|
+
labels=labels
|
|
76
|
+
)
|
|
77
|
+
self.metrics[key].append(metric_point)
|
|
78
|
+
|
|
79
|
+
def get_latest(self, name: str, labels: Dict[str, str] = None) -> Optional[MetricPoint]:
|
|
80
|
+
"""Get the latest value for a metric."""
|
|
81
|
+
labels = labels or {}
|
|
82
|
+
key = f"{name}:{json.dumps(labels, sort_keys=True)}"
|
|
83
|
+
|
|
84
|
+
with self._lock:
|
|
85
|
+
if key in self.metrics and self.metrics[key]:
|
|
86
|
+
return self.metrics[key][-1]
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
def get_values(self, name: str, labels: Dict[str, str] = None,
|
|
90
|
+
since: float = None) -> List[MetricPoint]:
|
|
91
|
+
"""Get metric values, optionally filtered by time."""
|
|
92
|
+
labels = labels or {}
|
|
93
|
+
key = f"{name}:{json.dumps(labels, sort_keys=True)}"
|
|
94
|
+
|
|
95
|
+
with self._lock:
|
|
96
|
+
if key not in self.metrics:
|
|
97
|
+
return []
|
|
98
|
+
|
|
99
|
+
values = list(self.metrics[key])
|
|
100
|
+
if since is not None:
|
|
101
|
+
values = [v for v in values if v.timestamp >= since]
|
|
102
|
+
|
|
103
|
+
return values
|
|
104
|
+
|
|
105
|
+
def get_system_metrics(self) -> Dict[str, float]:
|
|
106
|
+
"""Get system-level metrics."""
|
|
107
|
+
return {
|
|
108
|
+
'cpu_percent': psutil.cpu_percent(interval=0.1),
|
|
109
|
+
'memory_percent': psutil.virtual_memory().percent,
|
|
110
|
+
'disk_percent': psutil.disk_usage('/').percent,
|
|
111
|
+
'process_count': len(psutil.pids()),
|
|
112
|
+
'uptime_seconds': time.time() - self.start_time
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Logger:
|
|
117
|
+
"""Production-grade logger with structured logging."""
|
|
118
|
+
|
|
119
|
+
def __init__(self, name: str = "qyro", level: Severity = Severity.INFO):
|
|
120
|
+
self.name = name
|
|
121
|
+
self.level = level
|
|
122
|
+
self.handlers: List[Callable[[LogEntry], None]] = []
|
|
123
|
+
self._lock = threading.Lock()
|
|
124
|
+
|
|
125
|
+
# Add default console handler
|
|
126
|
+
self.add_handler(self._console_handler)
|
|
127
|
+
|
|
128
|
+
def add_handler(self, handler: Callable[[LogEntry], None]):
|
|
129
|
+
"""Add a log handler."""
|
|
130
|
+
with self._lock:
|
|
131
|
+
self.handlers.append(handler)
|
|
132
|
+
|
|
133
|
+
def _console_handler(self, entry: LogEntry):
|
|
134
|
+
"""Default console handler."""
|
|
135
|
+
timestamp = datetime.fromtimestamp(entry.timestamp).strftime('%Y-%m-%d %H:%M:%S')
|
|
136
|
+
level_name = entry.level.name
|
|
137
|
+
print(f"[{timestamp}] [{level_name}] [{entry.module}] {entry.message}")
|
|
138
|
+
if entry.details:
|
|
139
|
+
print(f" Details: {entry.details}")
|
|
140
|
+
|
|
141
|
+
def _log(self, level: Severity, message: str, module: str = "", **details):
|
|
142
|
+
"""Internal logging method."""
|
|
143
|
+
if level.value < self.level.value:
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
entry = LogEntry(
|
|
147
|
+
timestamp=time.time(),
|
|
148
|
+
level=level,
|
|
149
|
+
message=message,
|
|
150
|
+
module=module or self.name,
|
|
151
|
+
details=details
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
with self._lock:
|
|
155
|
+
for handler in self.handlers:
|
|
156
|
+
try:
|
|
157
|
+
handler(entry)
|
|
158
|
+
except Exception:
|
|
159
|
+
# Never let logging errors break the application
|
|
160
|
+
pass
|
|
161
|
+
|
|
162
|
+
def debug(self, message: str, module: str = "", **details):
|
|
163
|
+
self._log(Severity.DEBUG, message, module, **details)
|
|
164
|
+
|
|
165
|
+
def info(self, message: str, module: str = "", **details):
|
|
166
|
+
self._log(Severity.INFO, message, module, **details)
|
|
167
|
+
|
|
168
|
+
def warning(self, message: str, module: str = "", **details):
|
|
169
|
+
self._log(Severity.WARNING, message, module, **details)
|
|
170
|
+
|
|
171
|
+
def error(self, message: str, module: str = "", **details):
|
|
172
|
+
self._log(Severity.ERROR, message, module, **details)
|
|
173
|
+
|
|
174
|
+
def critical(self, message: str, module: str = "", **details):
|
|
175
|
+
self._log(Severity.CRITICAL, message, module, **details)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class HealthChecker:
|
|
179
|
+
"""Health checking system for the Qyro runtime."""
|
|
180
|
+
|
|
181
|
+
def __init__(self, metrics_collector: MetricsCollector, logger: Logger):
|
|
182
|
+
self.metrics = metrics_collector
|
|
183
|
+
self.logger = logger
|
|
184
|
+
self.checks: Dict[str, Callable[[], Dict[str, Any]]] = {}
|
|
185
|
+
self._lock = threading.Lock()
|
|
186
|
+
|
|
187
|
+
def register_check(self, name: str, check_func: Callable[[], Dict[str, Any]]):
|
|
188
|
+
"""Register a health check function."""
|
|
189
|
+
with self._lock:
|
|
190
|
+
self.checks[name] = check_func
|
|
191
|
+
|
|
192
|
+
def run_checks(self) -> Dict[str, Dict[str, Any]]:
|
|
193
|
+
"""Run all registered health checks."""
|
|
194
|
+
results = {}
|
|
195
|
+
|
|
196
|
+
for name, check_func in self.checks.items():
|
|
197
|
+
try:
|
|
198
|
+
result = check_func()
|
|
199
|
+
results[name] = result
|
|
200
|
+
except Exception as e:
|
|
201
|
+
results[name] = {
|
|
202
|
+
'status': 'error',
|
|
203
|
+
'error': str(e),
|
|
204
|
+
'timestamp': time.time()
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return results
|
|
208
|
+
|
|
209
|
+
def get_health_status(self) -> str:
|
|
210
|
+
"""Get overall health status."""
|
|
211
|
+
results = self.run_checks()
|
|
212
|
+
|
|
213
|
+
# If any check failed, return unhealthy
|
|
214
|
+
for result in results.values():
|
|
215
|
+
if result.get('status') == 'error' or result.get('healthy') is False:
|
|
216
|
+
return 'unhealthy'
|
|
217
|
+
|
|
218
|
+
return 'healthy'
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
class QyroMonitor:
|
|
222
|
+
"""Main monitoring system for Qyro runtime."""
|
|
223
|
+
|
|
224
|
+
def __init__(self):
|
|
225
|
+
self.metrics = MetricsCollector()
|
|
226
|
+
self.logger = Logger()
|
|
227
|
+
self.health = HealthChecker(self.metrics, self.logger)
|
|
228
|
+
self._monitoring_thread: Optional[threading.Thread] = None
|
|
229
|
+
self._stop_event = threading.Event()
|
|
230
|
+
self._running = False
|
|
231
|
+
|
|
232
|
+
# Register default health checks
|
|
233
|
+
self._register_default_checks()
|
|
234
|
+
|
|
235
|
+
def _register_default_checks(self):
|
|
236
|
+
"""Register default health checks."""
|
|
237
|
+
def check_system_resources():
|
|
238
|
+
cpu_percent = psutil.cpu_percent(interval=0.1)
|
|
239
|
+
memory_percent = psutil.virtual_memory().percent
|
|
240
|
+
disk_percent = psutil.disk_usage('/').percent
|
|
241
|
+
|
|
242
|
+
healthy = cpu_percent < 90 and memory_percent < 90 and disk_percent < 90
|
|
243
|
+
|
|
244
|
+
return {
|
|
245
|
+
'status': 'healthy' if healthy else 'degraded',
|
|
246
|
+
'healthy': healthy,
|
|
247
|
+
'cpu_percent': cpu_percent,
|
|
248
|
+
'memory_percent': memory_percent,
|
|
249
|
+
'disk_percent': disk_percent,
|
|
250
|
+
'timestamp': time.time()
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
def check_process_count():
|
|
254
|
+
proc_count = len(psutil.pids())
|
|
255
|
+
healthy = proc_count < 1000 # Arbitrary limit
|
|
256
|
+
|
|
257
|
+
return {
|
|
258
|
+
'status': 'healthy' if healthy else 'warning',
|
|
259
|
+
'healthy': healthy,
|
|
260
|
+
'process_count': proc_count,
|
|
261
|
+
'timestamp': time.time()
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
self.health.register_check('system_resources', check_system_resources)
|
|
265
|
+
self.health.register_check('process_count', check_process_count)
|
|
266
|
+
|
|
267
|
+
def start_monitoring(self, interval: float = 30.0):
|
|
268
|
+
"""Start background monitoring."""
|
|
269
|
+
if self._running:
|
|
270
|
+
return
|
|
271
|
+
|
|
272
|
+
self._running = True
|
|
273
|
+
|
|
274
|
+
def monitor_loop():
|
|
275
|
+
while not self._stop_event.is_set():
|
|
276
|
+
try:
|
|
277
|
+
# Collect system metrics
|
|
278
|
+
sys_metrics = self.metrics.get_system_metrics()
|
|
279
|
+
for name, value in sys_metrics.items():
|
|
280
|
+
self.metrics.record_metric(f"system.{name}", value)
|
|
281
|
+
|
|
282
|
+
# Run health checks
|
|
283
|
+
health_results = self.health.run_checks()
|
|
284
|
+
|
|
285
|
+
# Log health status
|
|
286
|
+
overall_status = self.health.get_health_status()
|
|
287
|
+
self.logger.info(f"Health check completed - Overall: {overall_status}",
|
|
288
|
+
module="monitor", health_results=health_results)
|
|
289
|
+
|
|
290
|
+
# Wait for next interval or stop event
|
|
291
|
+
if self._stop_event.wait(timeout=interval):
|
|
292
|
+
break
|
|
293
|
+
|
|
294
|
+
except Exception as e:
|
|
295
|
+
self.logger.error(f"Monitoring error: {e}", module="monitor")
|
|
296
|
+
# Wait a bit before retrying
|
|
297
|
+
if self._stop_event.wait(timeout=5.0):
|
|
298
|
+
break
|
|
299
|
+
|
|
300
|
+
self._monitoring_thread = threading.Thread(target=monitor_loop, daemon=True)
|
|
301
|
+
self._monitoring_thread.start()
|
|
302
|
+
|
|
303
|
+
def stop_monitoring(self):
|
|
304
|
+
"""Stop background monitoring."""
|
|
305
|
+
if not self._running:
|
|
306
|
+
return
|
|
307
|
+
|
|
308
|
+
self._running = False
|
|
309
|
+
self._stop_event.set()
|
|
310
|
+
|
|
311
|
+
if self._monitoring_thread:
|
|
312
|
+
self._monitoring_thread.join(timeout=5.0) # Wait up to 5 seconds
|
|
313
|
+
|
|
314
|
+
self._stop_event.clear()
|
|
315
|
+
|
|
316
|
+
def get_metrics_summary(self) -> Dict[str, Any]:
|
|
317
|
+
"""Get a summary of current metrics."""
|
|
318
|
+
return {
|
|
319
|
+
'system': self.metrics.get_system_metrics(),
|
|
320
|
+
'health_status': self.health.get_health_status(),
|
|
321
|
+
'health_details': self.health.run_checks(),
|
|
322
|
+
'uptime': time.time() - self.metrics.start_time
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
def record_custom_metric(self, name: str, value: float, labels: Dict[str, str] = None):
|
|
326
|
+
"""Record a custom metric."""
|
|
327
|
+
self.metrics.record_metric(name, value, labels)
|
|
328
|
+
|
|
329
|
+
def log_event(self, level: Severity, message: str, module: str = "", **details):
|
|
330
|
+
"""Log an event."""
|
|
331
|
+
if level == Severity.DEBUG:
|
|
332
|
+
self.logger.debug(message, module, **details)
|
|
333
|
+
elif level == Severity.INFO:
|
|
334
|
+
self.logger.info(message, module, **details)
|
|
335
|
+
elif level == Severity.WARNING:
|
|
336
|
+
self.logger.warning(message, module, **details)
|
|
337
|
+
elif level == Severity.ERROR:
|
|
338
|
+
self.logger.error(message, module, **details)
|
|
339
|
+
elif level == Severity.CRITICAL:
|
|
340
|
+
self.logger.critical(message, module, **details)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
# Global monitor instance
|
|
344
|
+
_qyro_monitor: Optional[QyroMonitor] = None
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
def get_monitor() -> QyroMonitor:
|
|
348
|
+
"""Get the global monitor instance."""
|
|
349
|
+
global _qyro_monitor
|
|
350
|
+
if _qyro_monitor is None:
|
|
351
|
+
_qyro_monitor = QyroMonitor()
|
|
352
|
+
return _qyro_monitor
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
def start_monitoring(interval: float = 30.0):
|
|
356
|
+
"""Start the monitoring system."""
|
|
357
|
+
monitor = get_monitor()
|
|
358
|
+
monitor.start_monitoring(interval)
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
def stop_monitoring():
|
|
362
|
+
"""Stop the monitoring system."""
|
|
363
|
+
monitor = get_monitor()
|
|
364
|
+
monitor.stop_monitoring()
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
@contextmanager
|
|
368
|
+
def monitor_performance(metric_name: str, labels: Dict[str, str] = None):
|
|
369
|
+
"""Context manager to monitor performance of a code block."""
|
|
370
|
+
start_time = time.time()
|
|
371
|
+
try:
|
|
372
|
+
yield
|
|
373
|
+
finally:
|
|
374
|
+
duration = time.time() - start_time
|
|
375
|
+
monitor = get_monitor()
|
|
376
|
+
monitor.record_custom_metric(metric_name, duration, labels)
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
# Example usage and health check endpoints for integration
|
|
380
|
+
def create_health_endpoints(app):
|
|
381
|
+
"""Add health check endpoints to a web application (FastAPI/Flask)."""
|
|
382
|
+
monitor = get_monitor()
|
|
383
|
+
|
|
384
|
+
try:
|
|
385
|
+
# Try FastAPI
|
|
386
|
+
from fastapi import APIRouter
|
|
387
|
+
router = APIRouter()
|
|
388
|
+
|
|
389
|
+
@router.get("/health")
|
|
390
|
+
def health_check():
|
|
391
|
+
return {
|
|
392
|
+
"status": monitor.health.get_health_status(),
|
|
393
|
+
"checks": monitor.health.run_checks(),
|
|
394
|
+
"timestamp": time.time()
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@router.get("/metrics")
|
|
398
|
+
def metrics_endpoint():
|
|
399
|
+
return monitor.get_metrics_summary()
|
|
400
|
+
|
|
401
|
+
@router.get("/live")
|
|
402
|
+
def liveness():
|
|
403
|
+
# Liveness probe - just check if the service is running
|
|
404
|
+
return {"status": "alive", "timestamp": time.time()}
|
|
405
|
+
|
|
406
|
+
@router.get("/ready")
|
|
407
|
+
def readiness():
|
|
408
|
+
# Readiness probe - check if the service is ready to serve requests
|
|
409
|
+
health_status = monitor.health.get_health_status()
|
|
410
|
+
return {
|
|
411
|
+
"status": "ready" if health_status == "healthy" else "not_ready",
|
|
412
|
+
"health": health_status,
|
|
413
|
+
"timestamp": time.time()
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
app.include_router(router)
|
|
417
|
+
return router
|
|
418
|
+
|
|
419
|
+
except ImportError:
|
|
420
|
+
pass
|
|
421
|
+
|
|
422
|
+
try:
|
|
423
|
+
# Try Flask
|
|
424
|
+
from flask import jsonify
|
|
425
|
+
|
|
426
|
+
@app.route("/health")
|
|
427
|
+
def health_check():
|
|
428
|
+
return jsonify({
|
|
429
|
+
"status": monitor.health.get_health_status(),
|
|
430
|
+
"checks": monitor.health.run_checks(),
|
|
431
|
+
"timestamp": time.time()
|
|
432
|
+
})
|
|
433
|
+
|
|
434
|
+
@app.route("/metrics")
|
|
435
|
+
def metrics_endpoint():
|
|
436
|
+
return jsonify(monitor.get_metrics_summary())
|
|
437
|
+
|
|
438
|
+
@app.route("/live")
|
|
439
|
+
def liveness():
|
|
440
|
+
return jsonify({"status": "alive", "timestamp": time.time()})
|
|
441
|
+
|
|
442
|
+
@app.route("/ready")
|
|
443
|
+
def readiness():
|
|
444
|
+
health_status = monitor.health.get_health_status()
|
|
445
|
+
return jsonify({
|
|
446
|
+
"status": "ready" if health_status == "healthy" else "not_ready",
|
|
447
|
+
"health": health_status,
|
|
448
|
+
"timestamp": time.time()
|
|
449
|
+
})
|
|
450
|
+
|
|
451
|
+
except ImportError:
|
|
452
|
+
pass
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
# Initialize monitoring when module is loaded
|
|
456
|
+
def _init_monitoring():
|
|
457
|
+
"""Initialize monitoring system."""
|
|
458
|
+
monitor = get_monitor()
|
|
459
|
+
|
|
460
|
+
# Start monitoring in background
|
|
461
|
+
monitor.start_monitoring()
|
|
462
|
+
|
|
463
|
+
# Log system startup
|
|
464
|
+
monitor.log_event(Severity.INFO, "Qyro monitoring system initialized", "monitor")
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
# Initialize monitoring
|
|
468
|
+
_init_monitoring()
|
qyro/common/parser.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
class NexusParser:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self.blocks = {
|
|
6
|
+
'py': [],
|
|
7
|
+
'js': [],
|
|
8
|
+
'rs': [],
|
|
9
|
+
'c': [],
|
|
10
|
+
'java': [],
|
|
11
|
+
'node': [],
|
|
12
|
+
'ts': [],
|
|
13
|
+
'go': [],
|
|
14
|
+
'web': [],
|
|
15
|
+
'schema': [],
|
|
16
|
+
'react': [],
|
|
17
|
+
'nextjs': [],
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
def parse_file(self, filepath: str):
|
|
21
|
+
try:
|
|
22
|
+
with open(filepath, 'r', encoding='utf-8') as f:
|
|
23
|
+
content = f.read()
|
|
24
|
+
# Pass the directory of the file as base_path
|
|
25
|
+
import os
|
|
26
|
+
base_path = os.path.dirname(filepath) or "."
|
|
27
|
+
self.parse_string(content, base_path=base_path)
|
|
28
|
+
except FileNotFoundError:
|
|
29
|
+
print(f"[QYRO] Error: File {filepath} not found.")
|
|
30
|
+
|
|
31
|
+
def parse_string(self, content: str, base_path="."):
|
|
32
|
+
# Import handling: >>>import "path/to/file.qyro"
|
|
33
|
+
import_pattern = re.compile(r'^>>>import\s+"([^"]+)"', re.MULTILINE)
|
|
34
|
+
for match in import_pattern.finditer(content):
|
|
35
|
+
rel_path = match.group(1)
|
|
36
|
+
import os
|
|
37
|
+
# Resolve relative to base_path
|
|
38
|
+
full_path = os.path.join(base_path, rel_path)
|
|
39
|
+
print(f"[QYRO] Importing {full_path}...")
|
|
40
|
+
self.parse_file(full_path)
|
|
41
|
+
|
|
42
|
+
# Split by block markers: >>>py, >>>c, >>>java, etc.
|
|
43
|
+
parts = re.split(r'(?m)^>>>(\S+)\s*$', content)
|
|
44
|
+
|
|
45
|
+
if len(parts) < 2:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
for i in range(1, len(parts), 2):
|
|
49
|
+
raw_type = parts[i].strip()
|
|
50
|
+
block_content = parts[i+1].strip()
|
|
51
|
+
|
|
52
|
+
# Handle type:name syntax (e.g. py:server -> type=py, name=server)
|
|
53
|
+
block_name = f"module_{i}"
|
|
54
|
+
if ':' in raw_type:
|
|
55
|
+
parts_type = raw_type.split(':', 1)
|
|
56
|
+
block_type = parts_type[0].lower()
|
|
57
|
+
block_name = parts_type[1]
|
|
58
|
+
else:
|
|
59
|
+
block_type = raw_type.lower()
|
|
60
|
+
|
|
61
|
+
if block_type == 'import':
|
|
62
|
+
continue
|
|
63
|
+
|
|
64
|
+
# Store in legacy string list (Backward Compatibility)
|
|
65
|
+
if block_type not in self.blocks:
|
|
66
|
+
self.blocks[block_type] = []
|
|
67
|
+
self.blocks[block_type].append(block_content)
|
|
68
|
+
|
|
69
|
+
# Store in unified named blocks list (For Build System)
|
|
70
|
+
if not hasattr(self, 'named_blocks'):
|
|
71
|
+
self.named_blocks = {}
|
|
72
|
+
|
|
73
|
+
if block_type not in self.named_blocks:
|
|
74
|
+
self.named_blocks[block_type] = []
|
|
75
|
+
|
|
76
|
+
self.named_blocks[block_type].append({
|
|
77
|
+
"name": block_name,
|
|
78
|
+
"content": block_content,
|
|
79
|
+
"original_type": raw_type
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
def get_blocks(self, block_type: str):
|
|
83
|
+
return self.blocks.get(block_type, [])
|
|
84
|
+
|
|
85
|
+
def get_named_blocks(self):
|
|
86
|
+
"""Return all blocks with their metadata (name, type)."""
|
|
87
|
+
return getattr(self, 'named_blocks', {})
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
# Qyro compatibility alias
|
|
91
|
+
QyroParser = NexusParser
|