From c292ff2242594e081656136a2a3a9f623cbde4de Mon Sep 17 00:00:00 2001 From: mARTin-B78 Date: Tue, 9 Jun 2026 23:30:33 +0200 Subject: [PATCH] fix: probe Prometheus /metrics at server root, not under /v1 (v2.03.10) Co-Authored-By: Claude Sonnet 4.6 --- linux/CHANGELOG.md | 7 +++++ linux/blitztext/__init__.py | 2 +- linux/blitztext/stt.py | 55 ++++++++++++++++++++++--------------- 3 files changed, 41 insertions(+), 23 deletions(-) diff --git a/linux/CHANGELOG.md b/linux/CHANGELOG.md index eb5f57f..ace7c91 100644 --- a/linux/CHANGELOG.md +++ b/linux/CHANGELOG.md @@ -9,6 +9,13 @@ The version is defined in [`blitztext/__init__.py`](blitztext/__init__.py). ## [Unreleased] +## [2.03.10] - 2026-06-09 + +### Fixed +- **Server RAM probe.** Prometheus `/metrics` is almost always at the server + root (`http://host:port/metrics`), not under `/v1`. Now tries the root URL + first before falling back to the API base path. + ## [2.03.09] - 2026-06-09 ### Added diff --git a/linux/blitztext/__init__.py b/linux/blitztext/__init__.py index 55b59df..3993cc4 100644 --- a/linux/blitztext/__init__.py +++ b/linux/blitztext/__init__.py @@ -6,4 +6,4 @@ counterpart to the macOS Blitztext menu bar app: it runs natively on the host (not in a container) so it can type into any application via xdotool. """ -__version__ = "2.03.09" +__version__ = "2.03.10" diff --git a/linux/blitztext/stt.py b/linux/blitztext/stt.py index 40d74f5..b5f9e3f 100644 --- a/linux/blitztext/stt.py +++ b/linux/blitztext/stt.py @@ -154,34 +154,45 @@ def probe_server_ram_mb(base_url: str, timeout: float = 3.0) -> float | None: Returns RSS in MB if the server exposes Prometheus metrics with either ``process_resident_memory_bytes`` (standard Python/Go exporter) or ``container_memory_rss`` (cAdvisor). Returns None if not available. + + Tries the server root first (http://host:port/metrics) then the api base + path (http://host:port/v1/metrics), because Prometheus endpoints are almost + always mounted at root even when the API lives under /v1. """ if not base_url: return None - base = _api_base(base_url) - try: - req = urllib.request.Request(base + "/metrics") - with urllib.request.urlopen(req, timeout=timeout) as resp: - text = resp.read().decode("utf-8", "replace") - except Exception: + p = urlparse(base_url.rstrip("/")) + root = f"{p.scheme}://{p.netloc}" + candidates = [root + "/metrics"] + api_base_metrics = _api_base(base_url) + "/metrics" + if api_base_metrics != candidates[0]: + candidates.append(api_base_metrics) + + def _parse(text: str) -> float | None: + for line in text.splitlines(): + line = line.strip() + if line.startswith("#"): + continue + for metric in ("process_resident_memory_bytes", "container_memory_rss"): + if line.startswith(metric): + parts = line.split() + if len(parts) >= 2: + try: + return float(parts[-1]) / (1024 * 1024) # bytes → MB + except ValueError: + pass return None - # Parse Prometheus text format — we only need two specific metric names. - # Lines look like: process_resident_memory_bytes 1.23456e+08 - best: float | None = None - for line in text.splitlines(): - line = line.strip() - if line.startswith("#"): + for url in candidates: + try: + req = urllib.request.Request(url) + with urllib.request.urlopen(req, timeout=timeout) as resp: + result = _parse(resp.read().decode("utf-8", "replace")) + if result is not None: + return result + except Exception: continue - for metric in ("process_resident_memory_bytes", "container_memory_rss"): - if line.startswith(metric): - parts = line.split() - if len(parts) >= 2: - try: - best = float(parts[-1]) / (1024 * 1024) # bytes → MB - return best - except ValueError: - pass - return best + return None def detect_remote_device(base_url: str, timeout: float = 3.0) -> str: