Replace transmission-exporter with homegrown Python exporter #283

Merged
eblume merged 2 commits from feature/transmission-exporter-python into main 2026-03-04 21:55:01 -08:00
2 changed files with 16 additions and 30 deletions
Showing only changes of commit 9c1b4a9a23 - Show all commits

Address PR feedback: uv run --script, latest Python/Alpine, serve_forever

- Use #!/usr/bin/env -S uv run --script shebang with PEP 723 metadata
- Drop venv/pip multi-stage build; uv handles deps at runtime
- Upgrade to python:3.13-alpine3.23
- Unpin dependency versions (no upper bounds)
- Replace threading.Event().wait() with wsgiref serve_forever()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Erich Blume 2026-03-04 21:47:21 -08:00

View file

@ -1,31 +1,21 @@
# Transmission Prometheus exporter - collect-on-scrape, no polling loop
# Two-stage build: uv installs deps into venv, runtime is minimal Alpine
# uv run --script handles dependency resolution at runtime
ARG CONTAINER_APP_VERSION=1.0.0
FROM python:3.12-alpine3.22 AS base
FROM base AS builder
WORKDIR /app
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
COPY exporter.py .
RUN uv venv .venv && \
uv pip install --python .venv/bin/python \
"prometheus-client>=0.24,<1.0" \
"transmission-rpc>=7.0,<8.0" && \
find /app/.venv \( -type d -a -name test -o -name tests \) \
-o \( -type f -a -name '*.pyc' -o -name '*.pyo' \) -exec rm -rf '{}' \+
FROM base
FROM python:3.13-alpine3.23
LABEL org.opencontainers.image.title="Transmission Exporter"
LABEL org.opencontainers.image.description="Prometheus exporter for Transmission BitTorrent client"
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
ENV PYTHONUNBUFFERED=1
ENV UV_CACHE_DIR=/tmp/uv-cache
WORKDIR /app
COPY --from=builder /app /app
ENV PATH="/app/.venv/bin:$PATH"
COPY exporter.py .
EXPOSE 19091
USER 65534:65534
CMD ["python", "-u", "/app/exporter.py"]
CMD ["uv", "run", "--script", "/app/exporter.py"]

View file

@ -1,8 +1,9 @@
#!/usr/bin/env -S uv run --script
# /// script
# requires-python = ">=3.12"
# requires-python = ">=3.13"
# dependencies = [
# "prometheus-client>=0.24,<1.0",
# "transmission-rpc>=7.0,<8.0",
# "prometheus-client",
# "transmission-rpc",
# ]
# ///
"""Minimal Prometheus exporter for Transmission, using collect-on-scrape."""
@ -10,8 +11,9 @@
import os
import sys
import urllib.parse
from wsgiref.simple_server import make_server
from prometheus_client import start_http_server
from prometheus_client import make_wsgi_app
from prometheus_client.core import REGISTRY, GaugeMetricFamily
from transmission_rpc import Client
@ -48,7 +50,6 @@ class TransmissionCollector:
print(f"Error collecting metrics: {e}", file=sys.stderr)
return
# Session stats
yield _gauge(
"transmission_session_stats_download_speed_bytes",
"Current download speed in bytes/s",
@ -86,7 +87,6 @@ class TransmissionCollector:
uploaded.add_metric(["cumulative"], session.cumulative_stats.uploaded_bytes)
yield uploaded
# Per-torrent metrics
t_download = GaugeMetricFamily(
"transmission_torrent_download_bytes",
"Torrent total downloaded bytes",
@ -142,12 +142,8 @@ def main():
REGISTRY.register(TransmissionCollector(client_kwargs))
print(f"Listening on :{port}, scraping {addr}")
start_http_server(port)
# Block forever
import threading
threading.Event().wait()
httpd = make_server("", port, make_wsgi_app())
httpd.serve_forever()
if __name__ == "__main__":