feat: add Prometheus metrics instrumentation
- New whisper_live/metrics.py with Counter, Gauge, Histogram metrics - Track connections (opened/closed/rejected), transcription latency, audio processed, segments emitted, REST requests, and errors - All metric helpers are no-ops when prometheus_client not installed - --metrics_port CLI flag to expose /metrics endpoint (0 = disabled) - Metrics integrated into server.py, base.py at key instrumentation points - 17 new tests in tests/test_metrics.py (178 total passing)
This commit is contained in:
+19
-1
@@ -16,6 +16,8 @@ from faster_whisper import WhisperModel
|
||||
import torch
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from whisper_live import metrics as wl_metrics
|
||||
from typing import List, Optional
|
||||
import numpy as np
|
||||
from websockets.sync.server import serve
|
||||
@@ -345,6 +347,7 @@ class TranscriptionServer:
|
||||
|
||||
self.use_vad = options.get('use_vad')
|
||||
if self.client_manager.is_server_full(websocket, options):
|
||||
wl_metrics.track_connection_rejected(reason="full")
|
||||
websocket.close()
|
||||
return False # Indicates that the connection should not continue
|
||||
|
||||
@@ -352,6 +355,7 @@ class TranscriptionServer:
|
||||
self.vad_detector = VoiceActivityDetector(frame_rate=self.RATE)
|
||||
self.initialize_client(websocket, options, faster_whisper_custom_model_path,
|
||||
whisper_tensorrt_path, trt_multilingual, trt_py_session=trt_py_session)
|
||||
wl_metrics.track_connection_opened()
|
||||
return True
|
||||
except json.JSONDecodeError:
|
||||
logging.error("Failed to decode JSON from client")
|
||||
@@ -430,6 +434,7 @@ class TranscriptionServer:
|
||||
if self.client_manager.get_client(websocket):
|
||||
self.cleanup(websocket)
|
||||
websocket.close()
|
||||
wl_metrics.track_connection_closed()
|
||||
del websocket
|
||||
|
||||
def run(self,
|
||||
@@ -450,7 +455,8 @@ class TranscriptionServer:
|
||||
batch_enabled=False,
|
||||
batch_max_size=8,
|
||||
batch_window_ms=50,
|
||||
raw_pcm_input=False):
|
||||
raw_pcm_input=False,
|
||||
metrics_port: int = 0):
|
||||
"""
|
||||
Run the transcription server.
|
||||
|
||||
@@ -507,6 +513,10 @@ class TranscriptionServer:
|
||||
if not BackendType.is_valid(backend):
|
||||
raise ValueError(f"{backend} is not a valid backend type. Choose backend from {BackendType.valid_types()}")
|
||||
|
||||
# Start Prometheus metrics endpoint if port is specified
|
||||
if metrics_port > 0:
|
||||
wl_metrics.start_metrics_server(metrics_port)
|
||||
|
||||
# New OpenAI-compatible REST API (toggleable via enable_rest boolean)
|
||||
if enable_rest:
|
||||
app = FastAPI(title="WhisperLive OpenAI-Compatible API")
|
||||
@@ -538,12 +548,14 @@ class TranscriptionServer:
|
||||
hotwords: Optional[str] = Form(default=None),
|
||||
):
|
||||
if stream:
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=400)
|
||||
return JSONResponse({"error": "Streaming not supported in this backend."}, status_code=400)
|
||||
if chunking_strategy or known_speaker_names or known_speaker_references:
|
||||
logging.warning("Diarization/chunking params ignored; not supported.")
|
||||
|
||||
supported_formats = ["json", "text", "srt", "verbose_json", "vtt"]
|
||||
if response_format not in supported_formats:
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=400)
|
||||
return JSONResponse({"error": f"Unsupported response_format. Supported: {supported_formats}"}, status_code=400)
|
||||
|
||||
if model != "whisper-1":
|
||||
@@ -574,8 +586,10 @@ class TranscriptionServer:
|
||||
os.unlink(tmp_path)
|
||||
|
||||
if response_format == "text":
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=200)
|
||||
return PlainTextResponse(text)
|
||||
elif response_format == "json":
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=200)
|
||||
return {"text": text}
|
||||
elif response_format == "verbose_json":
|
||||
verbose = {
|
||||
@@ -601,6 +615,7 @@ class TranscriptionServer:
|
||||
if timestamp_granularities and "word" in timestamp_granularities:
|
||||
seg_dict["words"] = [{"word": w.word, "start": w.start, "end": w.end, "probability": w.probability} for w in seg.words]
|
||||
verbose["segments"].append(seg_dict)
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=200)
|
||||
return verbose
|
||||
elif response_format in ["srt", "vtt"]:
|
||||
output = []
|
||||
@@ -611,8 +626,11 @@ class TranscriptionServer:
|
||||
output.append(f"{i}\n{start.replace('.', ',')} --> {end.replace('.', ',')}\n{seg.text.strip()}\n")
|
||||
else: # vtt
|
||||
output.append(f"{start} --> {end}\n{seg.text.strip()}\n")
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=200)
|
||||
return PlainTextResponse("\n".join(output))
|
||||
except Exception as e:
|
||||
wl_metrics.track_rest_request(endpoint="transcriptions", status=500)
|
||||
wl_metrics.track_error("rest_transcription")
|
||||
return JSONResponse({"error": str(e)}, status_code=500)
|
||||
|
||||
threading.Thread(
|
||||
|
||||
Reference in New Issue
Block a user