VRDate commited on
Commit
2274171
·
verified ·
1 Parent(s): e533fcb

Update app.py

Browse files

HTTP 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

Files changed (1) hide show
  1. app.py +21 -26
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: default port (8501)
10
- - /health: Health check (configurable via METRICS_PORT, default 80)
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: port={METRICS_PORT} log_interval={METRICS_LOG_INTERVAL}s")
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 = 80):
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
- # Start metrics server on import
414
- _metrics_thread = start_metrics_server(port=METRICS_PORT)
 
 
 
 
 
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 endpoints
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
- port = sys.get('metrics_port', 80)
1822
- st.code(f"""Endpoints (port {port}):
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
- METRICS_PORT={port}
1832
- METRICS_LOG_INTERVAL={METRICS_LOG_INTERVAL}s""", language=None)
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