1 Commits

Author SHA1 Message Date
Esteban Sánchez
c873cda618 Added more unittests 2023-11-20 16:17:28 +01:00
15 changed files with 226 additions and 1587 deletions

View File

@@ -11,63 +11,52 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- name: 'Checkout GitHub Action' - name: 'Checkout GitHub Action'
uses: actions/checkout@v4 uses: actions/checkout@v2
- name: Set up QEMU - name: Set up QEMU
uses: docker/setup-qemu-action@v3 uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v1
- name: Docker hub meta - name: Extract branch name
id: meta shell: bash
uses: docker/metadata-action@v5 run: |
with: tag=${GITHUB_REF#refs/tags/}
flavor: | tag=${tag#refs/heads/}
latest=true if echo $tag | grep -q -E "[0-9]+\.[0-9]+\.[0-9]+"; then
tags: | tag="v$tag"
type=semver,pattern=v{{version}} fi
type=ref,event=branch echo "##[set-output name=tag;]$tag"
type=sha id: extract_branch
images: ${{ github.actor }}/prometheus-qbittorrent-exporter
- name: Login to DockerHub - name: Login to DockerHub
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
username: ${{ secrets.REGISTRY_USERNAME }} username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }} password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push docker to DockerHub - name: Build and push docker to DockerHub
uses: docker/build-push-action@v5 uses: docker/build-push-action@v2
with: with:
push: true push: true
platforms: linux/amd64,linux/arm64,linux/386 platforms: linux/amd64,linux/arm64,linux/386
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
- name: GHCR Docker meta
id: metaghcr
uses: docker/metadata-action@v5
with:
flavor: |
latest=true
tags: | tags: |
type=semver,pattern=v{{version}} ${{ secrets.REGISTRY_USERNAME }}/prometheus-qbittorrent-exporter:latest
type=ref,event=branch ${{ secrets.REGISTRY_USERNAME }}/prometheus-qbittorrent-exporter:${{ steps.extract_branch.outputs.tag }}
type=sha
images: ghcr.io/${{ github.actor }}/prometheus-qbittorrent-exporter
- name: Login to Github Container Registry - name: Login to Github Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v1
with: with:
registry: ghcr.io registry: ghcr.io
username: ${{ github.actor }} username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.GITHUB_TOKEN }} password: ${{ secrets.GHCR_PERSONAL_TOKEN }}
- name: Build and push docker to Github Container Registry - name: Build and push docker to Github Container Registry
uses: docker/build-push-action@v5 uses: docker/build-push-action@v2
with: with:
push: true push: true
platforms: linux/amd64,linux/arm64,linux/386 platforms: linux/amd64,linux/arm64,linux/386
tags: ${{ steps.metaghcr.outputs.tags }} tags: |
labels: ${{ steps.metaghcr.outputs.labels }} ghcr.io/${{ secrets.REGISTRY_USERNAME }}/prometheus-qbittorrent-exporter:latest
ghcr.io/${{ secrets.REGISTRY_USERNAME }}/prometheus-qbittorrent-exporter:${{ steps.extract_branch.outputs.tag }}

View File

@@ -15,7 +15,7 @@ jobs:
checks: write checks: write
strategy: strategy:
matrix: matrix:
python-version: ["3.12"] python-version: ["3.11"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3

View File

@@ -12,7 +12,7 @@ jobs:
- name: Set up Python - name: Set up Python
uses: actions/setup-python@v1 uses: actions/setup-python@v1
with: with:
python-version: '3.12' python-version: '3.6'
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip

1
.gitignore vendored
View File

@@ -136,4 +136,3 @@ config.env
# Ignore ruff files # Ignore ruff files
.ruff_cache .ruff_cache
.DS_Store

View File

@@ -1,4 +1,4 @@
FROM python:3.12-alpine FROM python:3.11-alpine
# Install package # Install package
WORKDIR /code WORKDIR /code

View File

@@ -6,7 +6,7 @@
A prometheus exporter for qBitorrent. Get metrics from a server and offers them in a prometheus format. A prometheus exporter for qBitorrent. Get metrics from a server and offers them in a prometheus format.
![](https://img.shields.io/github/license/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/maintenance/yes/2023?style=for-the-badge) ![](https://img.shields.io/docker/pulls/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/github/forks/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/github/stars/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/python/required-version-toml?tomlFilePath=https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/master/pyproject.toml&style=for-the-badge) [![Coverage badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/python-coverage-comment-action-data/endpoint.json&label=tests%20coverage&style=for-the-badge)](https://htmlpreview.github.io/?https://github.com/esanchezm/prometheus-qbittorrent-exporter/blob/python-coverage-comment-action-data/htmlcov/index.html) ![](https://img.shields.io/maintenance/yes/2023?style=for-the-badge) ![](https://img.shields.io/docker/pulls/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/github/forks/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/github/stars/esanchezm/prometheus-qbittorrent-exporter?style=for-the-badge) ![](https://img.shields.io/python/required-version-toml?tomlFilePath=https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/master/pyproject.toml&style=for-the-badge) [![Coverage badge](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/python-coverage-comment-action-data/endpoint.json&label=tests%20coverage&style=for-the-badge)](https://htmlpreview.github.io/?https://github.com/esanchezm/prometheus-qbittorrent-exporter/blob/python-coverage-comment-action-data/htmlcov/index.html)
## How to use it ## How to use it

View File

@@ -1,39 +0,0 @@
name: prom-qb-alltime
services:
esanchezm:
cpu_shares: 90
command: []
deploy:
resources:
limits:
memory: 7943M
environment:
- QBITTORRENT_HOST=192.168.1.58
- QBITTORRENT_PASS=Cp3mMdP!#
- QBITTORRENT_PORT=8188
- QBITTORRENT_USER=noham
image: prom-qb-alltime
labels:
icon: https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/master/logo.png
ports:
- target: 8000
published: "9101"
protocol: tcp
restart: unless-stopped
volumes: []
devices: []
cap_add: []
network_mode: bridge
privileged: false
container_name: ""
x-casaos:
author: self
category: self
hostname: ""
icon: https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/master/logo.png
index: /metrics
port_map: "9101"
scheme: http
store_app_id: relaxed_albert
title:
custom: prom-qb-alltime

View File

@@ -2,10 +2,8 @@
## Import ## Import
To import the dashboard into your grafana, download the [dashboard.json](https://raw.githubusercontent.com/nohamr/prometheus-qbittorrent-exporter/master/grafana/dashboard.json) file and import it into your server. Select your prometheus instance and that should be all. To import the dashboard into your grafana, download the [dashboard.json](https://raw.githubusercontent.com/esanchezm/prometheus-qbittorrent-exporter/master/grafana/dashboard.json) file and import it into your server. Select your prometheus instance and that should be all.
## Screenshot ## Screenshot
![](./screenshot1.png) ![](./screenshot.png)
![](./screenshot2.png)
![](./screenshot3.png)

File diff suppressed because it is too large Load Diff

BIN
grafana/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 343 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 701 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 420 KiB

View File

@@ -1,13 +1,11 @@
import faulthandler import faulthandler
import logging import logging
import os import os
# from dotenv import load_dotenv
# load_dotenv()
import signal import signal
import sys import sys
import time import time
from dataclasses import dataclass, field from dataclasses import dataclass, field
from enum import Enum, auto from enum import StrEnum, auto
from typing import Any, Iterable from typing import Any, Iterable
from prometheus_client import start_http_server from prometheus_client import start_http_server
@@ -19,7 +17,8 @@ from qbittorrentapi import Client, TorrentStates
faulthandler.enable() faulthandler.enable()
logger = logging.getLogger() logger = logging.getLogger()
class MetricType(Enum):
class MetricType(StrEnum):
""" """
Represents possible metric types (used in this project). Represents possible metric types (used in this project).
""" """
@@ -86,22 +85,20 @@ class QbittorrentMetricsCollector:
""" """
Returns metrics about the state of the qbittorrent server. Returns metrics about the state of the qbittorrent server.
""" """
maindata: dict[str, Any] = {} response: dict[str, Any] = {}
version: str = "" version: str = ""
# Fetch data from API # Fetch data from API
try: try:
maindata = self.client.sync_maindata() response = self.client.transfer.info
version = self.client.app.version version = self.client.app.version
except Exception as e: except Exception as e:
logger.error(f"Couldn't get server info: {e}") logger.error(f"Couldn't get server info: {e}")
server_state = maindata.get('server_state', {})
return [ return [
Metric( Metric(
name=f"{self.config['metrics_prefix']}_up", name=f"{self.config['metrics_prefix']}_up",
value=bool(server_state), value=bool(response),
labels={"version": version}, labels={"version": version},
help_text=( help_text=(
"Whether the qBittorrent server is answering requests from this" "Whether the qBittorrent server is answering requests from this"
@@ -110,7 +107,7 @@ class QbittorrentMetricsCollector:
), ),
Metric( Metric(
name=f"{self.config['metrics_prefix']}_connected", name=f"{self.config['metrics_prefix']}_connected",
value=server_state.get("connection_status", "") == "connected", value=response.get("connection_status", "") == "connected",
labels={}, # no labels in the example labels={}, # no labels in the example
help_text=( help_text=(
"Whether the qBittorrent server is connected to the Bittorrent" "Whether the qBittorrent server is connected to the Bittorrent"
@@ -119,7 +116,7 @@ class QbittorrentMetricsCollector:
), ),
Metric( Metric(
name=f"{self.config['metrics_prefix']}_firewalled", name=f"{self.config['metrics_prefix']}_firewalled",
value=server_state.get("connection_status", "") == "firewalled", value=response.get("connection_status", "") == "firewalled",
labels={}, # no labels in the example labels={}, # no labels in the example
help_text=( help_text=(
"Whether the qBittorrent server is connected to the Bittorrent" "Whether the qBittorrent server is connected to the Bittorrent"
@@ -128,91 +125,24 @@ class QbittorrentMetricsCollector:
), ),
Metric( Metric(
name=f"{self.config['metrics_prefix']}_dht_nodes", name=f"{self.config['metrics_prefix']}_dht_nodes",
value=server_state.get("dht_nodes", 0), value=response.get("dht_nodes", 0),
labels={}, # no labels in the example labels={}, # no labels in the example
help_text="Number of DHT nodes connected to.", help_text="Number of DHT nodes connected to.",
), ),
Metric( Metric(
name=f"{self.config['metrics_prefix']}_dl_info_data", name=f"{self.config['metrics_prefix']}_dl_info_data",
value=server_state.get("dl_info_data", 0), value=response.get("dl_info_data", 0),
labels={}, # no labels in the example labels={}, # no labels in the example
help_text="Data downloaded since the server started, in bytes.", help_text="Data downloaded since the server started, in bytes.",
metric_type=MetricType.COUNTER, metric_type=MetricType.COUNTER,
), ),
Metric( Metric(
name=f"{self.config['metrics_prefix']}_up_info_data", name=f"{self.config['metrics_prefix']}_up_info_data",
value=server_state.get("up_info_data", 0), value=response.get("up_info_data", 0),
labels={}, # no labels in the example labels={}, # no labels in the example
help_text="Data uploaded since the server started, in bytes.", help_text="Data uploaded since the server started, in bytes.",
metric_type=MetricType.COUNTER, metric_type=MetricType.COUNTER,
), ),
Metric(
name=f"{self.config['metrics_prefix']}_alltime_dl",
value=server_state.get("alltime_dl", 0),
labels={}, # no labels in the example
help_text="Total data downloaded, in bytes.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_alltime_ul",
value=server_state.get("alltime_ul", 0),
labels={}, # no labels in the example
help_text="Total data uploaded, in bytes.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_total_peer_connections",
value=server_state.get("total_peer_connections", 0),
labels={}, # no labels in the example
help_text="total_peer_connections.",
metric_type=MetricType.COUNTER,
),
#### Disk metrics
Metric(
name=f"{self.config['metrics_prefix']}_write_cache_overload",
value=server_state.get("write_cache_overload", 0),
labels={}, # no labels in the example
help_text="write_cache_overload.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_read_cache_overload",
value=server_state.get("read_cache_overload", 0),
labels={}, # no labels in the example
help_text="read_cache_overload.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_read_cache_hits",
value=server_state.get("read_cache_hits", 0),
labels={}, # no labels in the example
help_text="read_cache_hits.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_average_time_queue",
value=server_state.get("average_time_queue", 0),
labels={}, # no labels in the example
help_text="average_time_queue.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_free_space_on_disk",
value=server_state.get("free_space_on_disk", 0),
labels={}, # no labels in the example
help_text="free_space_on_disk.",
metric_type=MetricType.COUNTER,
),
Metric(
name=f"{self.config['metrics_prefix']}_queued_io_jobs",
value=server_state.get("queued_io_jobs", 0),
labels={}, # no labels in the example
help_text="queued_io_jobs.",
metric_type=MetricType.COUNTER,
),
] ]
def _fetch_categories(self) -> dict: def _fetch_categories(self) -> dict:

View File

@@ -270,20 +270,6 @@ class TestQbittorrentMetricsCollector(unittest.TestCase):
help_text="Data uploaded since the server started, in bytes.", help_text="Data uploaded since the server started, in bytes.",
metric_type=MetricType.COUNTER, metric_type=MetricType.COUNTER,
), ),
Metric(
name="qbittorrent_alltime_dl",
value=0,
labels={}, # no labels in the example
help_text="Total data downloaded, in bytes.",
metric_type=MetricType.COUNTER,
),
Metric(
name="qbittorrent_alltime_ul",
value=0,
labels={}, # no labels in the example
help_text="Total data uploaded, in bytes.",
metric_type=MetricType.COUNTER,
),
] ]
metrics = self.collector._get_qbittorrent_status_metrics() metrics = self.collector._get_qbittorrent_status_metrics()