Update app.py
Browse filesHTTP metrics server kept but disabled by default.
To enable:
export METRICS_PORT=7861
streamlit run app.py
Log output:
# Disabled (default)
2025-12-06 [INFO] Metrics: log_interval=60s http_port=disabled
# Enabled
2025-12-06 [INFO] Metrics: log_interval=60s http_port=7861
2025-12-06 [INFO] Metrics server started on port 7861
Env vars:
| Variable | Default | Description |
|----------|---------|-------------|
| TORCH_THREADS | CPU/4 | PyTorch threads |
| WORKERS | CPU-TORCH | ThreadPool workers |
| METRICS_LOG_INTERVAL | 60 | Log interval (seconds) |
| METRICS_PORT | disabled | Set to enable HTTP server |
When enabled, endpoints:
/health - Health JSON
/metrics - Prometheus
/ready - Readiness probe
/live - Liveness probe
app.py
CHANGED
|
@@ -2,21 +2,20 @@
|
|
| 2 |
Bilingual NLP + TTS - Nikud · Syntax · NER · Speech
|
| 3 |
====================================================
|
| 4 |
|
|
|
|
|
|
|
| 5 |
Architecture:
|
| 6 |
Text → pysbd Sentence Breaker → async.gather([process(s) for s in sentences]) → JSON Array
|
| 7 |
|
| 8 |
Endpoints:
|
| 9 |
-
- Streamlit UI:
|
| 10 |
-
- /health
|
| 11 |
-
- /metrics: Prometheus metrics
|
| 12 |
-
- /ready: Readiness probe
|
| 13 |
-
- /live: Liveness probe
|
| 14 |
|
| 15 |
Environment Variables:
|
| 16 |
TORCH_THREADS - PyTorch threads (default: CPU_COUNT // 4)
|
| 17 |
WORKERS - ThreadPool workers (default: CPU_COUNT - TORCH_THREADS)
|
| 18 |
-
METRICS_PORT - Metrics server port (default: 80)
|
| 19 |
METRICS_LOG_INTERVAL - Periodic metrics log interval in seconds (default: 60)
|
|
|
|
| 20 |
"""
|
| 21 |
|
| 22 |
# ============================================================================
|
|
@@ -58,8 +57,8 @@ _default_workers = max(1, CPU_COUNT - _default_torch)
|
|
| 58 |
|
| 59 |
TORCH_THREADS = _parse_env_int('TORCH_THREADS', _default_torch, min_val=1, max_val=CPU_COUNT)
|
| 60 |
WORKERS = _parse_env_int('WORKERS', _default_workers, min_val=1, max_val=CPU_COUNT)
|
| 61 |
-
METRICS_PORT = _parse_env_int('METRICS_PORT', 80, min_val=1, max_val=65535)
|
| 62 |
METRICS_LOG_INTERVAL = _parse_env_int('METRICS_LOG_INTERVAL', 60, min_val=10, max_val=3600)
|
|
|
|
| 63 |
|
| 64 |
if TORCH_THREADS + WORKERS > CPU_COUNT:
|
| 65 |
logger.warning(f"TORCH_THREADS({TORCH_THREADS}) + WORKERS({WORKERS}) exceeds CPU_COUNT({CPU_COUNT})")
|
|
@@ -70,7 +69,7 @@ torch.set_num_threads(TORCH_THREADS)
|
|
| 70 |
# Note: set_num_interop_threads removed - can only be called once before any parallel work
|
| 71 |
|
| 72 |
logger.info(f"CPU Config: CPUs={CPU_COUNT} Torch={TORCH_THREADS} Workers={WORKERS}")
|
| 73 |
-
logger.info(f"Metrics:
|
| 74 |
|
| 75 |
# ============================================================================
|
| 76 |
# Now safe to import everything else
|
|
@@ -289,7 +288,6 @@ class MetricsCollector:
|
|
| 289 |
"cpu_count": CPU_COUNT,
|
| 290 |
"torch_threads": TORCH_THREADS,
|
| 291 |
"workers": WORKERS,
|
| 292 |
-
"metrics_port": METRICS_PORT,
|
| 293 |
},
|
| 294 |
"requests_total": self.requests_total,
|
| 295 |
"errors_total": self.errors_total,
|
|
@@ -380,7 +378,6 @@ class MetricsHandler(BaseHTTPRequestHandler):
|
|
| 380 |
self.wfile.write(metrics.to_prometheus().encode())
|
| 381 |
|
| 382 |
elif self.path == '/ready':
|
| 383 |
-
# Readiness probe
|
| 384 |
ready = metrics.models_loaded
|
| 385 |
self.send_response(200 if ready else 503)
|
| 386 |
self.send_header('Content-Type', 'application/json')
|
|
@@ -388,7 +385,6 @@ class MetricsHandler(BaseHTTPRequestHandler):
|
|
| 388 |
self.wfile.write(json.dumps({"ready": ready}).encode())
|
| 389 |
|
| 390 |
elif self.path == '/live':
|
| 391 |
-
# Liveness probe
|
| 392 |
self.send_response(200)
|
| 393 |
self.send_header('Content-Type', 'application/json')
|
| 394 |
self.end_headers()
|
|
@@ -399,10 +395,11 @@ class MetricsHandler(BaseHTTPRequestHandler):
|
|
| 399 |
self.end_headers()
|
| 400 |
|
| 401 |
|
| 402 |
-
def start_metrics_server(port: int =
|
| 403 |
"""Start metrics server in background thread."""
|
| 404 |
def run_server():
|
| 405 |
server = HTTPServer(('0.0.0.0', port), MetricsHandler)
|
|
|
|
| 406 |
server.serve_forever()
|
| 407 |
|
| 408 |
thread = threading.Thread(target=run_server, daemon=True)
|
|
@@ -410,8 +407,13 @@ def start_metrics_server(port: int = 80):
|
|
| 410 |
return thread
|
| 411 |
|
| 412 |
|
| 413 |
-
#
|
| 414 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 415 |
|
| 416 |
|
| 417 |
def detect_script(text: str) -> str:
|
|
@@ -947,7 +949,6 @@ class AsyncHebrewNLP:
|
|
| 947 |
"cpu_count": CPU_COUNT,
|
| 948 |
"torch_threads": TORCH_THREADS,
|
| 949 |
"workers": max_workers,
|
| 950 |
-
"metrics_port": METRICS_PORT,
|
| 951 |
}
|
| 952 |
|
| 953 |
async def process(
|
|
@@ -1809,7 +1810,7 @@ if models_loaded:
|
|
| 1809 |
noise_w = st.slider(t("noise_w"), 0.0, 2.0, 1.0, 0.1)
|
| 1810 |
sentence_pause = st.slider(t("sentence_pause"), 0.1, 1.0, 0.5, 0.1)
|
| 1811 |
|
| 1812 |
-
# Telemetry
|
| 1813 |
st.divider()
|
| 1814 |
with st.expander("📊 Telemetry", expanded=False):
|
| 1815 |
health = metrics.to_health_json()
|
|
@@ -1818,18 +1819,12 @@ if models_loaded:
|
|
| 1818 |
st.caption(f"CPUs: {sys.get('cpu_count', '?')} | Torch: {sys.get('torch_threads', '?')} | Workers: {sys.get('workers', '?')}")
|
| 1819 |
st.caption(f"Requests: {health['requests_total']} | Errors: {health['errors_total']}")
|
| 1820 |
st.caption(f"Sentences: {health['totals']['sentences']} | Words: {health['totals']['words']}")
|
| 1821 |
-
|
| 1822 |
-
st.code(f"""
|
| 1823 |
-
/health - Health JSON
|
| 1824 |
-
/metrics - Prometheus
|
| 1825 |
-
/ready - Readiness probe
|
| 1826 |
-
/live - Liveness probe
|
| 1827 |
-
|
| 1828 |
-
Env vars:
|
| 1829 |
TORCH_THREADS={TORCH_THREADS}
|
| 1830 |
WORKERS={WORKERS}
|
| 1831 |
-
|
| 1832 |
-
|
| 1833 |
|
| 1834 |
# ========== MAIN INPUT SECTION ==========
|
| 1835 |
|
|
|
|
| 2 |
Bilingual NLP + TTS - Nikud · Syntax · NER · Speech
|
| 3 |
====================================================
|
| 4 |
|
| 5 |
+
Title: Bilingual NLP + TTS - Nikud · Syntax · NER · Speech
|
| 6 |
+
|
| 7 |
Architecture:
|
| 8 |
Text → pysbd Sentence Breaker → async.gather([process(s) for s in sentences]) → JSON Array
|
| 9 |
|
| 10 |
Endpoints:
|
| 11 |
+
- Streamlit UI: port 7860
|
| 12 |
+
- /health, /metrics, /ready, /live: disabled (enable via METRICS_PORT env var)
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
Environment Variables:
|
| 15 |
TORCH_THREADS - PyTorch threads (default: CPU_COUNT // 4)
|
| 16 |
WORKERS - ThreadPool workers (default: CPU_COUNT - TORCH_THREADS)
|
|
|
|
| 17 |
METRICS_LOG_INTERVAL - Periodic metrics log interval in seconds (default: 60)
|
| 18 |
+
METRICS_PORT - Enable HTTP metrics server on this port (disabled by default)
|
| 19 |
"""
|
| 20 |
|
| 21 |
# ============================================================================
|
|
|
|
| 57 |
|
| 58 |
TORCH_THREADS = _parse_env_int('TORCH_THREADS', _default_torch, min_val=1, max_val=CPU_COUNT)
|
| 59 |
WORKERS = _parse_env_int('WORKERS', _default_workers, min_val=1, max_val=CPU_COUNT)
|
|
|
|
| 60 |
METRICS_LOG_INTERVAL = _parse_env_int('METRICS_LOG_INTERVAL', 60, min_val=10, max_val=3600)
|
| 61 |
+
METRICS_PORT = os.environ.get('METRICS_PORT') # None = disabled, set to enable
|
| 62 |
|
| 63 |
if TORCH_THREADS + WORKERS > CPU_COUNT:
|
| 64 |
logger.warning(f"TORCH_THREADS({TORCH_THREADS}) + WORKERS({WORKERS}) exceeds CPU_COUNT({CPU_COUNT})")
|
|
|
|
| 69 |
# Note: set_num_interop_threads removed - can only be called once before any parallel work
|
| 70 |
|
| 71 |
logger.info(f"CPU Config: CPUs={CPU_COUNT} Torch={TORCH_THREADS} Workers={WORKERS}")
|
| 72 |
+
logger.info(f"Metrics: log_interval={METRICS_LOG_INTERVAL}s http_port={METRICS_PORT or 'disabled'}")
|
| 73 |
|
| 74 |
# ============================================================================
|
| 75 |
# Now safe to import everything else
|
|
|
|
| 288 |
"cpu_count": CPU_COUNT,
|
| 289 |
"torch_threads": TORCH_THREADS,
|
| 290 |
"workers": WORKERS,
|
|
|
|
| 291 |
},
|
| 292 |
"requests_total": self.requests_total,
|
| 293 |
"errors_total": self.errors_total,
|
|
|
|
| 378 |
self.wfile.write(metrics.to_prometheus().encode())
|
| 379 |
|
| 380 |
elif self.path == '/ready':
|
|
|
|
| 381 |
ready = metrics.models_loaded
|
| 382 |
self.send_response(200 if ready else 503)
|
| 383 |
self.send_header('Content-Type', 'application/json')
|
|
|
|
| 385 |
self.wfile.write(json.dumps({"ready": ready}).encode())
|
| 386 |
|
| 387 |
elif self.path == '/live':
|
|
|
|
| 388 |
self.send_response(200)
|
| 389 |
self.send_header('Content-Type', 'application/json')
|
| 390 |
self.end_headers()
|
|
|
|
| 395 |
self.end_headers()
|
| 396 |
|
| 397 |
|
| 398 |
+
def start_metrics_server(port: int = 7861):
|
| 399 |
"""Start metrics server in background thread."""
|
| 400 |
def run_server():
|
| 401 |
server = HTTPServer(('0.0.0.0', port), MetricsHandler)
|
| 402 |
+
logger.info(f"Metrics server started on port {port}")
|
| 403 |
server.serve_forever()
|
| 404 |
|
| 405 |
thread = threading.Thread(target=run_server, daemon=True)
|
|
|
|
| 407 |
return thread
|
| 408 |
|
| 409 |
|
| 410 |
+
# Metrics HTTP server - disabled by default, enable via METRICS_PORT env var
|
| 411 |
+
if METRICS_PORT:
|
| 412 |
+
try:
|
| 413 |
+
_metrics_port = int(METRICS_PORT)
|
| 414 |
+
_metrics_thread = start_metrics_server(port=_metrics_port)
|
| 415 |
+
except ValueError:
|
| 416 |
+
logger.warning(f"METRICS_PORT={METRICS_PORT} is not a valid port number, server disabled")
|
| 417 |
|
| 418 |
|
| 419 |
def detect_script(text: str) -> str:
|
|
|
|
| 949 |
"cpu_count": CPU_COUNT,
|
| 950 |
"torch_threads": TORCH_THREADS,
|
| 951 |
"workers": max_workers,
|
|
|
|
| 952 |
}
|
| 953 |
|
| 954 |
async def process(
|
|
|
|
| 1810 |
noise_w = st.slider(t("noise_w"), 0.0, 2.0, 1.0, 0.1)
|
| 1811 |
sentence_pause = st.slider(t("sentence_pause"), 0.1, 1.0, 0.5, 0.1)
|
| 1812 |
|
| 1813 |
+
# Telemetry
|
| 1814 |
st.divider()
|
| 1815 |
with st.expander("📊 Telemetry", expanded=False):
|
| 1816 |
health = metrics.to_health_json()
|
|
|
|
| 1819 |
st.caption(f"CPUs: {sys.get('cpu_count', '?')} | Torch: {sys.get('torch_threads', '?')} | Workers: {sys.get('workers', '?')}")
|
| 1820 |
st.caption(f"Requests: {health['requests_total']} | Errors: {health['errors_total']}")
|
| 1821 |
st.caption(f"Sentences: {health['totals']['sentences']} | Words: {health['totals']['words']}")
|
| 1822 |
+
metrics_status = f"enabled on port {METRICS_PORT}" if METRICS_PORT else "disabled"
|
| 1823 |
+
st.code(f"""Env vars:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1824 |
TORCH_THREADS={TORCH_THREADS}
|
| 1825 |
WORKERS={WORKERS}
|
| 1826 |
+
METRICS_LOG_INTERVAL={METRICS_LOG_INTERVAL}s
|
| 1827 |
+
METRICS_PORT={metrics_status}""", language=None)
|
| 1828 |
|
| 1829 |
# ========== MAIN INPUT SECTION ==========
|
| 1830 |
|