NCS (services/node-capabilities/metrics.py): - NodeLoad: inflight_jobs, queue_depth, concurrency_limit, estimated_wait_ms, cpu_load_1m, mem_pressure (macOS + Linux), rtt_ms_to_hub - RuntimeLoad: per-runtime healthy, p50_ms, p95_ms from rolling 50-sample window - POST /capabilities/report_latency for node-worker → NCS reporting - NCS fetches worker metrics via NODE_WORKER_URL Node Worker: - GET /metrics endpoint (inflight, concurrency, latency buffers) - Latency tracking per job type (llm/vision) with rolling buffer - Fire-and-forget latency reporting to NCS after each successful job Router (model_select v3): - score_candidate(): wait + model_latency + cross_node_penalty + prefer_bonus - LOCAL_THRESHOLD_MS=250: prefer local if within threshold of remote - ModelSelection.score field for observability - Structured [score] logs with chosen node, model, and score breakdown Tests: 19 new (12 scoring + 7 NCS metrics), 36 total pass Docs: ops/runbook_p3_1.md, ops/CHANGELOG_FABRIC.md No breaking changes to JobRequest/JobResponse or capabilities schema. Made-with: Cursor
59 lines
1.4 KiB
Python
59 lines
1.4 KiB
Python
"""Node Worker — NATS offload executor for cross-node inference."""
|
|
import logging
|
|
import os
|
|
|
|
from fastapi import FastAPI
|
|
|
|
import config
|
|
import worker
|
|
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger("node-worker")
|
|
|
|
app = FastAPI(title="Node Worker", version="1.0.0")
|
|
|
|
_nats_client = None
|
|
|
|
|
|
@app.get("/healthz")
|
|
async def healthz():
|
|
connected = _nats_client is not None and _nats_client.is_connected if _nats_client else False
|
|
return {
|
|
"status": "ok" if connected else "degraded",
|
|
"node_id": config.NODE_ID,
|
|
"nats_connected": connected,
|
|
"max_concurrency": config.MAX_CONCURRENCY,
|
|
}
|
|
|
|
|
|
@app.get("/metrics")
|
|
async def metrics():
|
|
return worker.get_metrics()
|
|
|
|
|
|
@app.on_event("startup")
|
|
async def startup():
|
|
global _nats_client
|
|
try:
|
|
import nats as nats_lib
|
|
_nats_client = await nats_lib.connect(config.NATS_URL)
|
|
logger.info(f"✅ NATS connected: {config.NATS_URL}")
|
|
await worker.start(_nats_client)
|
|
logger.info(f"✅ Node Worker ready: node={config.NODE_ID} concurrency={config.MAX_CONCURRENCY}")
|
|
except Exception as e:
|
|
logger.error(f"❌ Startup failed: {e}")
|
|
|
|
|
|
@app.on_event("shutdown")
|
|
async def shutdown():
|
|
if _nats_client:
|
|
try:
|
|
await _nats_client.close()
|
|
except Exception:
|
|
pass
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
uvicorn.run(app, host="0.0.0.0", port=config.PORT)
|