-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 206c804
Showing
5 changed files
with
238 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
name: Build and push Docker image | ||
|
||
on: | ||
push: | ||
branches: ["main"] | ||
|
||
env: | ||
REGISTRY: ghcr.io | ||
IMAGE_NAME: ${{ github.repository }} | ||
|
||
jobs: | ||
build-and-push-image: | ||
runs-on: ubuntu-latest | ||
permissions: | ||
contents: read | ||
packages: write | ||
|
||
steps: | ||
- name: Checkout repository | ||
uses: actions/checkout@v4 | ||
|
||
- name: Log in to the Container registry | ||
uses: docker/login-action@v3 | ||
with: | ||
registry: ${{ env.REGISTRY }} | ||
username: ${{ github.actor }} | ||
password: ${{ secrets.GITHUB_TOKEN }} | ||
|
||
- name: Extract metadata (tags, labels) for Docker | ||
id: meta | ||
uses: docker/metadata-action@v5 | ||
with: | ||
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} | ||
|
||
- name: Build and push Docker image | ||
uses: docker/build-push-action@v5 | ||
with: | ||
context: . | ||
push: true | ||
tags: ${{ steps.meta.outputs.tags }} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
FROM python:3.11-alpine | ||
|
||
COPY requirements.txt /app/requirements.txt | ||
RUN pip install -r /app/requirements.txt | ||
COPY main.py /app/main.py | ||
|
||
EXPOSE 8000 | ||
CMD /app/main.py |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# A simple Prometheus exporter for Transmission in Python | ||
|
||
This project aims at providing a [Prometheus](https://prometheus.io/) exporter for the [Transmission](https://transmissionbt.com/) client. | ||
|
||
## Configuration | ||
|
||
You can use environment variables when starting the container: | ||
|
||
| Variable | Value | | ||
| --------------------------- | -------------------------------------------------------------------- | | ||
| `TRANSMISSION_HOST` | the hostname where Transmission RPC is running (default `localhost`) | | ||
| `TRANSMISSION_PORT` | the port where Transmission RPC is listening (default `9091`) | | ||
| `TRANSMISSION_USERNAME` | the username to connect to Transmission RPC (optionnal) | | ||
| `TRANSMISSION_PASSWORD` | the password to connect to Transmission RPC (optionnal) | | ||
| `EXPORTER_COLLECT_INTERVAL` | seconds between 2 Transmission scraping (default `30`) | | ||
| `METRIC_NAMESPACE` | prefix for metrics name (default `transmission`) | | ||
|
||
The exporter is listenning on port 8000. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
#!/usr/bin/env python3 | ||
|
||
# Imports | ||
import os | ||
import time | ||
from transmission_rpc import Client | ||
from prometheus_client import start_http_server, Gauge, REGISTRY, PROCESS_COLLECTOR, PLATFORM_COLLECTOR, GC_COLLECTOR | ||
|
||
# Number of seconds between 2 metrics collection | ||
COLLECT_INTERVAL = os.getenv('EXPORTER_COLLECT_INTERVAL', 30) | ||
# Prefix for all metrics | ||
METRIC_NAMESPACE = os.getenv('METRIC_NAMESPACE', 'transmission') | ||
|
||
# Get Transmission information from env | ||
TRANSMISSION_HOST = os.getenv('TRANSMISSION_HOST', 'localhost') | ||
TRANSMISSION_PORT = os.getenv('TRANSMISSION_PORT', 9091) | ||
TRANSMISSION_USERNAME = os.getenv('TRANSMISSION_USERNAME') | ||
TRANSMISSION_PASSWORD = os.getenv('TRANSMISSION_PASSWORD') | ||
|
||
# Remove unwanted Prometheus metrics | ||
REGISTRY.unregister(GC_COLLECTOR) | ||
REGISTRY.unregister(PLATFORM_COLLECTOR) | ||
REGISTRY.unregister(PROCESS_COLLECTOR) | ||
|
||
# Start Prometheus exporter server | ||
start_http_server(8000) | ||
|
||
|
||
######################################### | ||
##### Initialize Prometheus metrics ##### | ||
######################################### | ||
|
||
# Speeds gauges | ||
download_speed_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_download_speed_bytes', 'Current download speed in bytes') | ||
upload_speed_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_upload_speed_bytes', 'Current upload speed in bytes') | ||
|
||
# Speed limit gauges | ||
speed_limit_down_gauge = Gauge(f'{METRIC_NAMESPACE}_speed_limit_down_bytes', 'Max global download speed', ["enabled"]) | ||
speed_limit_up_gauge = Gauge(f'{METRIC_NAMESPACE}_speed_limit_up_bytes', 'Max global upload speed', ["enabled"]) | ||
alt_speed_limit_down_gauge = Gauge(f'{METRIC_NAMESPACE}_alt_speed_down', 'Alternative max global download speed', ["enabled"]) | ||
alt_speed_limit_up_gauge = Gauge(f'{METRIC_NAMESPACE}_alt_speed_up', 'Alternative max global upload speed', ["enabled"]) | ||
|
||
# Torrents count gauges | ||
session_stats_torrents_active_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_torrents_active', 'The number of active torrents') | ||
session_stats_torrents_paused_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_torrents_paused', 'The number of paused torrents') | ||
session_stats_torrents_total_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_torrents_total', 'The total number of torrents') | ||
|
||
# Queue gauges | ||
down_queue_size_gauge = Gauge(f'{METRIC_NAMESPACE}_down_queue_size', 'Max number of torrents to download at once', ["enabled"]) | ||
up_queue_size_gauge = Gauge(f'{METRIC_NAMESPACE}_up_queue_size', 'Max number of torrents to upload at once', ["enabled"]) | ||
|
||
# Volume gauges | ||
session_stats_downloaded_bytes_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_downloaded_bytes', 'The number of downloaded bytes', ['type']) | ||
session_stats_uploaded_bytes_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_uploaded_bytes', 'The number of uploaded bytes', ['type']) | ||
|
||
# Peer limit gauges | ||
global_peer_limit_gauge = Gauge(f'{METRIC_NAMESPACE}_global_peer_limit', 'Maximum global number of peers') | ||
torrent_peer_limit_gauge = Gauge(f'{METRIC_NAMESPACE}_torrent_peer_limit', 'Maximum number of peers for a single torrent') | ||
|
||
# Transmission gauges | ||
session_stats_sessions_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_sessions', 'Count of the times transmission started', ['type']) | ||
session_stats_active_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_active', 'The time transmission is active since', ['type']) | ||
|
||
# Misc | ||
seed_ratio_limit_gauge = Gauge(f'{METRIC_NAMESPACE}_seed_ratio_limit', 'The default seed ratio for torrents to use', ["enabled"]) | ||
cache_size_bytes_gauge = Gauge(f'{METRIC_NAMESPACE}_cache_size_bytes', 'Maximum size of the disk cache') | ||
session_stats_files_added_gauge = Gauge(f'{METRIC_NAMESPACE}_session_stats_files_added', 'The number of files added', ['type']) | ||
|
||
|
||
def transmission_connect(): | ||
""" | ||
Connect to Transmission | ||
""" | ||
if TRANSMISSION_USERNAME is not None and TRANSMISSION_PASSWORD is not None: | ||
client = Client(host=TRANSMISSION_HOST, port=TRANSMISSION_PORT, username=TRANSMISSION_USERNAME, password=TRANSMISSION_PASSWORD) | ||
else: | ||
client = Client(host=TRANSMISSION_HOST, port=TRANSMISSION_PORT) | ||
|
||
return client | ||
|
||
def refresh_metrics(client): | ||
""" | ||
Refresh all Prometheus metrics from Transmission | ||
""" | ||
|
||
# Get session and session stats | ||
session = client.get_session() | ||
stats = client.session_stats() | ||
|
||
# Refresh regular gauges | ||
download_speed_gauge.set(stats.download_speed) | ||
upload_speed_gauge.set(stats.upload_speed) | ||
session_stats_torrents_active_gauge.set(stats.active_torrent_count) | ||
session_stats_torrents_paused_gauge.set(stats.paused_torrent_count) | ||
session_stats_torrents_total_gauge.set(stats.torrent_count) | ||
cache_size_bytes_gauge.set(session.cache_size_mb*1000*1000) | ||
global_peer_limit_gauge.set(session.peer_limit_global) | ||
torrent_peer_limit_gauge.set(session.peer_limit_per_torrent) | ||
|
||
# Refresh session stats | ||
session_stats_active_gauge.labels(type="cumulative").set(stats.cumulative_stats.seconds_active) | ||
session_stats_active_gauge.labels(type="current").set(stats.current_stats.seconds_active) | ||
|
||
session_stats_downloaded_bytes_gauge.labels(type="cumulative").set(stats.cumulative_stats.downloaded_bytes) | ||
session_stats_downloaded_bytes_gauge.labels(type="current").set(stats.current_stats.downloaded_bytes) | ||
|
||
session_stats_uploaded_bytes_gauge.labels(type="cumulative").set(stats.cumulative_stats.uploaded_bytes) | ||
session_stats_uploaded_bytes_gauge.labels(type="current").set(stats.current_stats.uploaded_bytes) | ||
|
||
session_stats_files_added_gauge.labels(type="cumulative").set(stats.cumulative_stats.files_added) | ||
session_stats_files_added_gauge.labels(type="current").set(stats.current_stats.files_added) | ||
|
||
session_stats_sessions_gauge.labels(type="cumulative").set(stats.cumulative_stats.session_count) | ||
session_stats_sessions_gauge.labels(type="current").set(stats.current_stats.session_count) | ||
|
||
# Refresh conditionnaly enable speed limits, queue size and ratio | ||
if session.speed_limit_down_enabled: | ||
speed_limit_down_gauge.clear() | ||
speed_limit_down_gauge.labels(enabled="1").set(session.speed_limit_down*1000) | ||
else: | ||
speed_limit_down_gauge.clear() | ||
speed_limit_down_gauge.labels(enabled="0").set(session.speed_limit_down*1000) | ||
|
||
if session.speed_limit_up_enabled: | ||
speed_limit_up_gauge.clear() | ||
speed_limit_up_gauge.labels(enabled="1").set(session.speed_limit_up*1000) | ||
else: | ||
speed_limit_up_gauge.clear() | ||
speed_limit_up_gauge.labels(enabled="0").set(session.speed_limit_up*1000) | ||
|
||
if session.alt_speed_time_enabled: | ||
alt_speed_limit_down_gauge.clear() | ||
alt_speed_limit_up_gauge.clear() | ||
alt_speed_limit_down_gauge.labels(enabled="1").set(session.alt_speed_down*1000) | ||
alt_speed_limit_up_gauge.labels(enabled="1").set(session.alt_speed_up*1000) | ||
else: | ||
alt_speed_limit_down_gauge.clear() | ||
alt_speed_limit_up_gauge.clear() | ||
alt_speed_limit_down_gauge.labels(enabled="0").set(session.alt_speed_down*1000) | ||
alt_speed_limit_up_gauge.labels(enabled="0").set(session.alt_speed_up*1000) | ||
|
||
if session.seed_ratio_limited: | ||
seed_ratio_limit_gauge.clear() | ||
seed_ratio_limit_gauge.labels(enabled="1").set(session.seed_ratio_limit) | ||
else: | ||
seed_ratio_limit_gauge.clear() | ||
seed_ratio_limit_gauge.labels(enabled="0").set(session.seed_ratio_limit) | ||
|
||
if session.download_queue_enabled: | ||
down_queue_size_gauge.clear() | ||
down_queue_size_gauge.labels(enabled="1").set(session.download_queue_size) | ||
else: | ||
down_queue_size_gauge.clear() | ||
down_queue_size_gauge.labels(enabled="0").set(session.download_queue_size) | ||
|
||
if session.seed_queue_enabled: | ||
up_queue_size_gauge.clear() | ||
up_queue_size_gauge.labels(enabled="1").set(session.seed_queue_size) | ||
else: | ||
up_queue_size_gauge.clear() | ||
up_queue_size_gauge.labels(enabled="0").set(session.seed_queue_size) | ||
|
||
# Connect | ||
client = transmission_connect() | ||
|
||
# Loop forever | ||
while True: | ||
refresh_metrics(client) | ||
# Wait before next metrics collection | ||
time.sleep(COLLECT_INTERVAL) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
transmission-rpc==6.0.0 | ||
prometheus-client==0.17.1 |