48 Commits
1.0.2 ... 1.2.0

Author SHA1 Message Date
Esteban Sánchez
6c885b5fa0 Version 1.2.0 2021-07-17 23:19:38 +02:00
Esteban Sanchez
039f7a7ef4 Merge pull request #9 from jhollowe/logging-and-signals
Fixed logging and exit errors
2021-07-17 23:18:00 +02:00
Esteban Sanchez
8bdaa48c48 Merge pull request #10 from jhollowe/file-configs
Allow files as config sources
2021-07-17 23:16:48 +02:00
Esteban Sanchez
e8dd24f731 Merge pull request #8 from jhollowe/master
Add multi-arch Docker build support
2021-07-17 23:15:00 +02:00
John Hollowell
12dcad10d5 Setup logging before config loading 2021-07-16 23:24:58 +00:00
John Hollowell
597307c230 Allow files as config source
Allow "FILE__"+config_name to point to a file containing the config value.
This format matches linuxserver.io containers and is comonly used in other containers as well.

e.g. FILE__QBITTORRENT_HOST contains "/run/secrets/q_host" and the contents of the "/run/secrets/q_host" is "1.2.3.4"
2021-07-16 23:24:58 +00:00
John Hollowell
a25005b6a0 Fixed logging and exit errors
* Create logger in module so it is always available (still configured in main())
* Allow signals sent multiple times to forcibly kill the exporter
2021-07-16 23:24:26 +00:00
John Hollowell
cfe62f8115 Remove hardcoded username in docker tags 2021-07-16 21:15:47 +00:00
John Hollowell
b5d20e3fe6 Add multiple architechture build support
Add auto builds for x86, x86_64, and arm64
2021-07-16 21:01:54 +00:00
Esteban Sánchez
0ff6a56f18 Version 1.1.2 2021-07-03 23:33:39 +02:00
Esteban Sánchez
2d968bc8e8 Added attrdict as requirement 2021-07-03 20:59:50 +02:00
Esteban Sánchez
8ab307a630 Version 1.1.1 2021-07-02 16:18:11 +02:00
Esteban Sánchez
de00864f11 Small fixes 2021-07-02 16:07:53 +02:00
Esteban Sanchez
03d332979b Merge pull request #4 from angristan/update-api-dep
Update qbittorrent-api to 2021.3.18
2021-07-02 16:03:54 +02:00
Esteban Sanchez
1f6e8aa380 Merge pull request #6 from ndragon798/patch-1
Added info for prometheus.yml
2021-07-02 16:03:39 +02:00
Nathan Easton
ef00d90e2f Added info for prometheus.yml
As requested by #5
2021-07-01 14:05:50 -04:00
Stanislas
7f783f0b0a Update qbittorrent-api to 2021.3.18
To include 72687ca614 
in order to fix https://github.com/esanchezm/prometheus-qbittorrent-exporter/issues/3
2021-03-14 10:45:08 +01:00
Esteban Sánchez
345888ef68 Fixed category variable 2020-10-26 18:23:09 +01:00
Esteban Sánchez
0e4c4a97ea Added screenshot 2020-10-26 18:15:22 +01:00
Esteban Sánchez
d144f412ca Added screenshot and instructions 2020-10-26 18:02:40 +01:00
Esteban Sanchez
9002a709b7 Added grafana dashboard 2020-10-26 17:51:39 +01:00
Esteban Sánchez
b91bff0f0c Fixed output reference 2020-10-24 11:36:24 +02:00
Esteban Sánchez
ed44d88c5b Debug 2020-10-24 11:33:14 +02:00
Esteban Sánchez
8f0a0381d3 Get tag when it's a version release 2020-10-24 11:30:51 +02:00
Esteban Sanchez
b3e5e69f1f Get branch name to push 2020-10-24 10:52:22 +02:00
Esteban Sánchez
ef12d46431 Create docker image on release 2020-10-24 10:40:20 +02:00
Esteban Sánchez
9a19f46b7e Release 1.1.0 2020-10-24 10:39:49 +02:00
Esteban Sánchez
a53c7ae08b Install from src and not pip package 2020-10-24 10:39:40 +02:00
Esteban Sanchez
17ebcb0834 Setup buildx 2020-10-24 10:30:21 +02:00
Esteban Sanchez
7016e3de2b Set tags from a list 2020-10-24 10:13:39 +02:00
Esteban Sanchez
af51fd5b67 Set labels 2020-10-23 22:07:01 +02:00
Esteban Sanchez
82151b1d82 Setting tag 2020-10-23 22:04:55 +02:00
Esteban Sanchez
b6e3131929 Push to docker hub 2020-10-23 22:04:10 +02:00
Esteban Sanchez
688fcc54cb Testing 2020-10-23 22:02:24 +02:00
Esteban Sanchez
bc79a0dd52 Create pythonpublish.yml 2020-10-23 21:56:46 +02:00
Esteban Sanchez
af732087e8 Fixed version 2020-10-23 21:51:55 +02:00
Esteban Sanchez
15130f1c95 Added action to push to dockerhub 2020-10-23 21:49:29 +02:00
Esteban Sanchez
53dc38f796 Merge pull request #1 from Gabisonfire/master
Added the ability to include uncategorized torrents.
2020-10-22 23:11:35 +02:00
Gab
3bfc3f0673 Removed unused config 2020-10-21 18:58:17 -04:00
Esteban Sánchez
eee65ec84e Allow setting a metrics prefix 2020-10-21 08:05:51 +02:00
Gab
b4aefff06d Changed readme 2020-10-20 11:57:33 -04:00
Gab
baf14d495c Removed now useless logging message 2020-10-20 11:46:50 -04:00
Gab
003297ecfd Removed uncategorized from config 2020-10-20 11:45:48 -04:00
Gab
2497249b8c Fixed indentation 2020-10-20 02:10:39 -04:00
Gab
9e9f4a9ce4 Added scraping for uncategorized torrents 2020-10-20 02:10:04 -04:00
Gab
40d9bb964e Added scraping for uncategorized torrents 2020-10-20 02:05:17 -04:00
Esteban Sánchez
0b3fb04728 Removed unneded code and return earlier 2020-10-01 10:14:56 +02:00
Esteban Sánchez
13a52c40f2 New version 2020-09-29 13:47:32 +02:00
9 changed files with 1087 additions and 46 deletions

44
.github/workflows/docker.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Docker
on:
push:
branches: [ master ]
release:
types: [ created ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@v2
- name: Set up QEMU
uses: docker/setup-qemu-action@v1
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v1
- name: Extract branch name
shell: bash
run: |
tag=${GITHUB_REF#refs/tags/}
tag=${tag#refs/heads/}
if echo $tag | grep -q -E "[0-9]+\.[0-9]+\.[0-9]+"; then
tag="v$tag"
fi
echo "##[set-output name=tag;]$tag"
id: extract_branch
- name: Login to DockerHub
uses: docker/login-action@v1
with:
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}
- name: Build and push docker
uses: docker/build-push-action@v2
with:
push: true
platforms: linux/amd64,linux/arm64,linux/386
tags: ${{ secrets.REGISTRY_USERNAME }}/prometheus-qbittorrent-exporter:latest,${{ secrets.REGISTRY_USERNAME }}/prometheus-qbittorrent-exporter:${{ steps.extract_branch.outputs.tag }}

26
.github/workflows/pythonpublish.yml vendored Normal file
View File

@@ -0,0 +1,26 @@
name: Upload Python Package
on:
release:
types: [created]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: '3.6'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install setuptools wheel twine
- name: Build and publish
env:
TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
run: |
python setup.py sdist
twine upload dist/*

View File

@@ -5,7 +5,9 @@ RUN apk add --update --no-cache \
python3 python3
# Install package # Install package
RUN pip3 install prometheus-qbittorrent-exporter==1.0.1 WORKDIR /code
COPY . .
RUN pip3 install .
ENV QBITTORRENT_HOST="" ENV QBITTORRENT_HOST=""
ENV QBITTORRENT_PORT="" ENV QBITTORRENT_PORT=""

View File

@@ -22,7 +22,12 @@ Another option is run it in a docker container.
``` ```
docker run -e QBITTORRENT_PORT=8080 -e QBITTORRENT_HOST=myserver.local -p 8000:8000 esanchezm/prometheus-qbittorrent-exporter docker run -e QBITTORRENT_PORT=8080 -e QBITTORRENT_HOST=myserver.local -p 8000:8000 esanchezm/prometheus-qbittorrent-exporter
``` ```
Add this to your prometheus.yml
```
- job_name: "qbittorrent_exporter"
static_configs:
- targets: ['yourqbittorrentexporter:port']
```
The application reads configuration using environment variables: The application reads configuration using environment variables:
| Environment variable | Default | Description | | Environment variable | Default | Description |
@@ -33,22 +38,29 @@ The application reads configuration using environment variables:
| `QBITTORRENT_PASS` | `""` | qbittorrent password | | `QBITTORRENT_PASS` | `""` | qbittorrent password |
| `EXPORTER_PORT` | `8000` | Exporter listening port | | `EXPORTER_PORT` | `8000` | Exporter listening port |
| `EXPORTER_LOG_LEVEL` | `INFO` | Log level. One of: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` | | `EXPORTER_LOG_LEVEL` | `INFO` | Log level. One of: `DEBUG`, `INFO`, `WARNING`, `ERROR`, `CRITICAL` |
| `METRICS_PREFIX` | `qbittorrent` | Prefix to add to all the metrics |
## Metrics ## Metrics
These are the metrics this program exports: These are the metrics this program exports, assuming the `METRICS_PREFIX` is `qbittorrent`:
| Metric name | Type | Description | | Metric name | Type | Description |
| --------------------------------------------------- | -------- | ---------------- | | --------------------------------------------------- | -------- | ---------------- |
| `qbittorrent_up` | gauge | Whether if the qBittorrent server is answering requests from this exporter. A `version` label with the server version is added | | `qbittorrent_up` | gauge | Whether if the qBittorrent server is answering requests from this exporter. A `version` label with the server version is added |
| `connected` | gauge | Whether if the qBittorrent server is connected to the Bittorrent network. | | `qbittorrent_connected` | gauge | Whether if the qBittorrent server is connected to the Bittorrent network. |
| `firewalled` | gauge | Whether if the qBittorrent server is connected to the Bittorrent network but is behind a firewall. | | `qbittorrent_firewalled` | gauge | Whether if the qBittorrent server is connected to the Bittorrent network but is behind a firewall. |
| `dht_nodes` | gauge | Number of DHT nodes connected to | | `qbittorrent_dht_nodes` | gauge | Number of DHT nodes connected to |
| `dl_info_data` | counter | Data downloaded since the server started, in bytes | | `qbittorrent_dl_info_data` | counter | Data downloaded since the server started, in bytes |
| `up_info_data` | counter | Data uploaded since the server started, in bytes | | `qbittorrent_up_info_data` | counter | Data uploaded since the server started, in bytes |
| `torrents_count` | gauge | Number of torrents for each `category` and `status`. Example: `torrents_count{category="movies",status="downloading"}`| | `qbittorrent_torrents_count` | gauge | Number of torrents for each `category` and `status`. Example: `qbittorrent_torrents_count{category="movies",status="downloading"}`|
## Screenshot
![](./grafana/screenshot.png)
[More info](./grafana/README.md)
## License ## License

9
grafana/README.md Normal file
View File

@@ -0,0 +1,9 @@
# Grafana dashboard
## Import
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.png)

927
grafana/dashboard.json Normal file
View File

@@ -0,0 +1,927 @@
{
"__inputs": [
{
"name": "DS_PROMETHEUS",
"label": "Prometheus",
"description": "",
"type": "datasource",
"pluginId": "prometheus",
"pluginName": "Prometheus"
}
],
"__requires": [
{
"type": "panel",
"id": "gauge",
"name": "Gauge",
"version": ""
},
{
"type": "grafana",
"id": "grafana",
"name": "Grafana",
"version": "7.2.2"
},
{
"type": "panel",
"id": "grafana-piechart-panel",
"name": "Pie Chart",
"version": "1.6.1"
},
{
"type": "panel",
"id": "graph",
"name": "Graph",
"version": ""
},
{
"type": "datasource",
"id": "prometheus",
"name": "Prometheus",
"version": "1.0.0"
},
{
"type": "panel",
"id": "stat",
"name": "Stat",
"version": ""
}
],
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": "-- Grafana --",
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "dashboard"
}
]
},
"editable": true,
"gnetId": null,
"graphTooltip": 0,
"id": null,
"iteration": 1603732792335,
"links": [],
"panels": [
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [
{
"from": "",
"id": 0,
"text": "Online",
"to": "",
"type": 1,
"value": "1"
},
{
"from": "",
"id": 1,
"text": "Offline",
"to": "",
"type": 1,
"value": "0"
},
{
"from": "",
"id": 2,
"text": "Unknown",
"to": "",
"type": 1,
"value": "null"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "yellow",
"value": null
},
{
"color": "semi-dark-red",
"value": 0
},
{
"color": "semi-dark-green",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 0,
"y": 0
},
"id": 2,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "qbittorrent_up",
"format": "time_series",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Qbittorrent Status",
"transformations": [],
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [
{
"from": "",
"id": 0,
"text": "Connected",
"to": "",
"type": 1,
"value": "1"
},
{
"from": "",
"id": 1,
"text": "Disconnected",
"to": "",
"type": 1,
"value": "0"
},
{
"from": "",
"id": 2,
"text": "Firewalled",
"to": "",
"type": 1,
"value": "-1"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "yellow",
"value": null
},
{
"color": "dark-red",
"value": 0
},
{
"color": "semi-dark-green",
"value": 1
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 3,
"y": 0
},
"id": 3,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "qbittorrent_connected - qbittorrent_firewalled",
"format": "time_series",
"instant": true,
"interval": "",
"legendFormat": "Status",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Bittorrent network",
"transformations": [],
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [
{
"from": "",
"id": 1,
"text": "No DHT",
"to": "",
"type": 1,
"value": "0"
},
{
"from": "",
"id": 2,
"text": "Firewalled",
"to": "",
"type": 1,
"value": "-1"
}
],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "yellow",
"value": null
},
{
"color": "dark-red",
"value": 0
},
{
"color": "semi-dark-green",
"value": 100
}
]
}
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 6,
"y": 0
},
"id": 13,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"lastNotNull"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "qbittorrent_dht_nodes",
"instant": true,
"interval": "",
"legendFormat": "DHT nodes",
"refId": "B"
}
],
"timeFrom": null,
"timeShift": null,
"title": "DHT nodes",
"transformations": [],
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "decbytes"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 9,
"y": 0
},
"id": 5,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "increase(qbittorrent_dl_info_data_total[100y])",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Data downloaded",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "decbytes"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 12,
"y": 0
},
"id": 6,
"options": {
"colorMode": "value",
"graphMode": "none",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "increase(qbittorrent_up_info_data_total[100y])",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Data uploaded",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
},
"unit": "none"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 15,
"y": 0
},
"id": 7,
"options": {
"colorMode": "value",
"graphMode": "area",
"justifyMode": "auto",
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"textMode": "auto"
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "increase(qbittorrent_up_info_data_total[100y]) / increase(qbittorrent_dl_info_data_total[100y])",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Accumulated ratio",
"type": "stat"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"decimals": 1,
"mappings": [],
"max": 48000000,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "dark-red",
"value": null
},
{
"color": "dark-green",
"value": 8000000
}
]
},
"unit": "binBps"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 18,
"y": 0
},
"id": 16,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "rate(qbittorrent_dl_info_data_total[1m])",
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Download speed",
"type": "gauge"
},
{
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {},
"decimals": 1,
"mappings": [
{
"from": "",
"id": 1,
"text": "",
"to": "",
"type": 1
}
],
"max": 18000000,
"min": 0,
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "dark-red",
"value": null
},
{
"color": "dark-green",
"value": 4000000
}
]
},
"unit": "binBps"
},
"overrides": []
},
"gridPos": {
"h": 4,
"w": 3,
"x": 21,
"y": 0
},
"id": 17,
"options": {
"orientation": "auto",
"reduceOptions": {
"calcs": [
"last"
],
"fields": "",
"values": false
},
"showThresholdLabels": false,
"showThresholdMarkers": true
},
"pluginVersion": "7.2.2",
"targets": [
{
"expr": "rate(qbittorrent_up_info_data_total[1m])",
"hide": false,
"instant": false,
"interval": "",
"legendFormat": "",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Upload speed",
"type": "gauge"
},
{
"aliasColors": {},
"bars": false,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 10,
"w": 24,
"x": 0,
"y": 4
},
"hiddenSeries": false,
"id": 15,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 1,
"nullPointMode": "null",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"expr": "rate(qbittorrent_dl_info_data_total[1m])",
"interval": "",
"legendFormat": "Download",
"refId": "A"
},
{
"expr": "rate(qbittorrent_up_info_data_total[1m])",
"interval": "",
"legendFormat": "Upload",
"refId": "B"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Transfer ratio",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "binBps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "binBps",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"bars": true,
"cacheTimeout": null,
"dashLength": 10,
"dashes": false,
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {}
},
"overrides": []
},
"fill": 1,
"fillGradient": 0,
"gridPos": {
"h": 10,
"w": 14,
"x": 0,
"y": 14
},
"hiddenSeries": false,
"id": 11,
"interval": null,
"legend": {
"alignAsTable": true,
"avg": false,
"current": false,
"max": false,
"min": false,
"rightSide": true,
"show": true,
"total": false,
"values": false
},
"lines": false,
"linewidth": 0,
"links": [],
"nullPointMode": "null as zero",
"options": {
"alertThreshold": true
},
"percentage": false,
"pluginVersion": "7.2.2",
"pointradius": 2,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"spaceLength": 10,
"stack": true,
"steppedLine": true,
"targets": [
{
"expr": "sum(qbittorrent_torrents_count{category=~\"${categories}\", status!=\"complete\"}) by (status)",
"interval": "",
"legendFormat": "{{status}}",
"refId": "A"
}
],
"thresholds": [],
"timeFrom": null,
"timeRegions": [],
"timeShift": null,
"title": "Torrents by status",
"tooltip": {
"shared": true,
"sort": 0,
"value_type": "individual"
},
"type": "graph",
"xaxis": {
"buckets": null,
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"label": null,
"logBase": 1,
"max": null,
"min": null,
"show": true
}
],
"yaxis": {
"align": false,
"alignLevel": null
}
},
{
"aliasColors": {},
"breakPoint": "50%",
"cacheTimeout": null,
"combine": {
"label": "Others",
"threshold": 0
},
"datasource": "${DS_PROMETHEUS}",
"fieldConfig": {
"defaults": {
"custom": {
"align": null,
"filterable": false
},
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "green",
"value": null
}
]
}
},
"overrides": []
},
"fontSize": "80%",
"format": "short",
"gridPos": {
"h": 10,
"w": 10,
"x": 14,
"y": 14
},
"id": 12,
"interval": null,
"legend": {
"percentage": true,
"show": true,
"sideWidth": null,
"values": true
},
"legendType": "Right side",
"links": [],
"nullPointMode": "connected",
"pieType": "donut",
"pluginVersion": "7.2.0",
"strokeWidth": 1,
"targets": [
{
"expr": "sum(qbittorrent_torrents_count{category=~\"${categories}\",status!=\"complete\"}) by (category)",
"interval": "",
"legendFormat": "{{category}}",
"refId": "A"
}
],
"timeFrom": null,
"timeShift": null,
"title": "Torrents by categories",
"type": "grafana-piechart-panel",
"valueName": "current"
}
],
"refresh": "30s",
"schemaVersion": 26,
"style": "dark",
"tags": [],
"templating": {
"list": [
{
"allValue": ".*",
"current": {},
"datasource": "${DS_PROMETHEUS}",
"definition": "label_values(qbittorrent_torrents_count, category)",
"hide": 0,
"includeAll": true,
"label": "Categories",
"multi": true,
"name": "categories",
"options": [],
"query": "label_values(qbittorrent_torrents_count, category)",
"refresh": 1,
"regex": "",
"skipUrlSync": false,
"sort": 0,
"tagValuesQuery": "",
"tags": [],
"tagsQuery": "",
"type": "query",
"useTags": false
}
]
},
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {},
"timezone": "",
"title": "Qbittorrent",
"uid": "eKyTETFMk",
"version": 40
}

BIN
grafana/screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

View File

@@ -3,7 +3,9 @@ import os
import sys import sys
import signal import signal
import faulthandler import faulthandler
from attrdict import AttrDict
from qbittorrentapi import Client, TorrentStates from qbittorrentapi import Client, TorrentStates
from qbittorrentapi.exceptions import APIConnectionError
from prometheus_client import start_http_server from prometheus_client import start_http_server
from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY from prometheus_client.core import GaugeMetricFamily, CounterMetricFamily, REGISTRY
import logging import logging
@@ -12,7 +14,7 @@ from pythonjsonlogger import jsonlogger
# Enable dumps on stderr in case of segfault # Enable dumps on stderr in case of segfault
faulthandler.enable() faulthandler.enable()
logger = None logger = logging.getLogger()
class QbittorrentMetricsCollector(): class QbittorrentMetricsCollector():
@@ -40,6 +42,7 @@ class QbittorrentMetricsCollector():
self.torrents = self.client.torrents.info() self.torrents = self.client.torrents.info()
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}")
return None
metrics = self.get_qbittorrent_metrics() metrics = self.get_qbittorrent_metrics()
@@ -65,46 +68,49 @@ class QbittorrentMetricsCollector():
return metrics return metrics
def get_qbittorrent_status_metrics(self): def get_qbittorrent_status_metrics(self):
response = {}
version = ""
# Fetch data from API # Fetch data from API
try: try:
response = self.client.transfer.info response = self.client.transfer.info
version = self.client.app.version version = self.client.app.version
self.torrents = self.client.torrents.info() self.torrents = self.client.torrents.info()
except Exception as e: except APIConnectionError as e:
logger.error(f"Couldn't get server info: {e}") logger.error(f"Couldn't get server info: {e.error_message}")
response = None except Exception:
version = "" logger.error(f"Couldn't get server info")
return [ return [
{ {
"name": "qbittorrent_up", "name": f"{self.config['metrics_prefix']}_up",
"value": response is not None, "value": bool(response),
"labels": {"version": version}, "labels": {"version": version},
"help": "Whether if server is alive or not", "help": "Whether if server is alive or not",
}, },
{ {
"name": "connected", "name": f"{self.config['metrics_prefix']}_connected",
"value": response.get("connection_status", "") == "connected", "value": response.get("connection_status", "") == "connected",
"help": "Whether if server is connected or not", "help": "Whether if server is connected or not",
}, },
{ {
"name": "firewalled", "name": f"{self.config['metrics_prefix']}_firewalled",
"value": response.get("connection_status", "") == "firewalled", "value": response.get("connection_status", "") == "firewalled",
"help": "Whether if server is under a firewall or not", "help": "Whether if server is under a firewall or not",
}, },
{ {
"name": "dht_nodes", "name": f"{self.config['metrics_prefix']}_dht_nodes",
"value": response.get("dht_nodes", 0), "value": response.get("dht_nodes", 0),
"help": "DHT nodes connected to", "help": "DHT nodes connected to",
}, },
{ {
"name": "dl_info_data", "name": f"{self.config['metrics_prefix']}_dl_info_data",
"value": response.get("dl_info_data", 0), "value": response.get("dl_info_data", 0),
"help": "Data downloaded this session (bytes)", "help": "Data downloaded this session (bytes)",
"type": "counter" "type": "counter"
}, },
{ {
"name": "up_info_data", "name": f"{self.config['metrics_prefix']}_up_info_data",
"value": response.get("up_info_data", 0), "value": response.get("up_info_data", 0),
"help": "Data uploaded this session (bytes)", "help": "Data uploaded this session (bytes)",
"type": "counter" "type": "counter"
@@ -116,17 +122,15 @@ class QbittorrentMetricsCollector():
categories = self.client.torrent_categories.categories categories = self.client.torrent_categories.categories
except Exception as e: except Exception as e:
logger.error(f"Couldn't fetch categories: {e}") logger.error(f"Couldn't fetch categories: {e}")
categories = None
if not categories:
return [] return []
if not self.torrents: if not self.torrents:
return [] return []
metrics = [] metrics = []
categories.Uncategorized = AttrDict({'name': 'Uncategorized', 'savePath': ''})
for category in categories: for category in categories:
category_torrents = [t for t in self.torrents if t['category'] == category] category_torrents = [t for t in self.torrents if t['category'] == category or (category == "Uncategorized" and t['category'] == "")]
for status in self.TORRENT_STATUSES: for status in self.TORRENT_STATUSES:
status_prop = f"is_{status}" status_prop = f"is_{status}"
@@ -134,7 +138,7 @@ class QbittorrentMetricsCollector():
t for t in category_torrents if getattr(TorrentStates, status_prop).fget(TorrentStates(t['state'])) t for t in category_torrents if getattr(TorrentStates, status_prop).fget(TorrentStates(t['state']))
] ]
metrics.append({ metrics.append({
"name": "torrents_count", "name": f"{self.config['metrics_prefix']}_torrents_count",
"value": len(status_torrents), "value": len(status_torrents),
"labels": { "labels": {
"status": status, "status": status,
@@ -148,44 +152,61 @@ class QbittorrentMetricsCollector():
class SignalHandler(): class SignalHandler():
def __init__(self): def __init__(self):
self.shutdown = False self.shutdownCount = 0
# Register signal handler # Register signal handler
signal.signal(signal.SIGINT, self._on_signal_received) signal.signal(signal.SIGINT, self._on_signal_received)
signal.signal(signal.SIGTERM, self._on_signal_received) signal.signal(signal.SIGTERM, self._on_signal_received)
def is_shutting_down(self): def is_shutting_down(self):
return self.shutdown return self.shutdownCount > 0
def _on_signal_received(self, signal, frame): def _on_signal_received(self, signal, frame):
if self.shutdownCount > 1:
logger.warn("Forcibly killing exporter")
sys.exit(1)
logger.info("Exporter is shutting down") logger.info("Exporter is shutting down")
self.shutdown = True self.shutdownCount += 1
def get_config_value(key, default=""):
input_path = os.environ.get("FILE__" + key, None)
if input_path is not None:
try:
with open(input_path, "r") as input_file:
return input_file.read().strip()
except IOError as e:
logger.error(f"Unable to read value for {key} from {input_path}: {str(e)}")
return os.environ.get(key, default)
def main(): def main():
config = { # Init logger so it can be used
"host": os.environ.get("QBITTORRENT_HOST", ""),
"port": os.environ.get("QBITTORRENT_PORT", ""),
"username": os.environ.get("QBITTORRENT_USER", ""),
"password": os.environ.get("QBITTORRENT_PASS", ""),
"exporter_port": int(os.environ.get("EXPORTER_PORT", "8000")),
"log_level": os.environ.get("EXPORTER_LOG_LEVEL", "INFO")
}
# Register signal handler
signal_handler = SignalHandler()
# Init logger
logHandler = logging.StreamHandler() logHandler = logging.StreamHandler()
formatter = jsonlogger.JsonFormatter( formatter = jsonlogger.JsonFormatter(
"%(asctime) %(levelname) %(message)", "%(asctime) %(levelname) %(message)",
datefmt="%Y-%m-%d %H:%M:%S" datefmt="%Y-%m-%d %H:%M:%S"
) )
logHandler.setFormatter(formatter) logHandler.setFormatter(formatter)
logger = logging.getLogger()
logger.addHandler(logHandler) logger.addHandler(logHandler)
logger.setLevel("INFO") # default until config is loaded
config = {
"host": get_config_value("QBITTORRENT_HOST", ""),
"port": get_config_value("QBITTORRENT_PORT", ""),
"username": get_config_value("QBITTORRENT_USER", ""),
"password": get_config_value("QBITTORRENT_PASS", ""),
"exporter_port": int(get_config_value("EXPORTER_PORT", "8000")),
"log_level": get_config_value("EXPORTER_LOG_LEVEL", "INFO"),
"metrics_prefix": get_config_value("METRICS_PREFIX", "qbittorrent"),
}
# set level once config has been loaded
logger.setLevel(config["log_level"]) logger.setLevel(config["log_level"])
# Register signal handler
signal_handler = SignalHandler()
if not config["host"]: if not config["host"]:
logger.error("No host specified, please set QBITTORRENT_HOST environment variable") logger.error("No host specified, please set QBITTORRENT_HOST environment variable")
sys.exit(1) sys.exit(1)

View File

@@ -6,18 +6,18 @@ with open("README.md", "r") as fh:
setup( setup(
name='prometheus-qbittorrent-exporter', name='prometheus-qbittorrent-exporter',
packages=['qbittorrent_exporter'], packages=['qbittorrent_exporter'],
version='1.0.1', version='1.2.0',
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
description='Prometheus exporter for qbittorrent', description='Prometheus exporter for qbittorrent',
author='Esteban Sanchez', author='Esteban Sanchez',
author_email='esteban.sanchez@gmail.com', author_email='esteban.sanchez@gmail.com',
url='https://github.com/esanchezm/prometheus-qbittorrent-exporter', url='https://github.com/esanchezm/prometheus-qbittorrent-exporter',
download_url='https://github.com/esanchezm/prometheus-qbittorrent-exporter/archive/1.0.1.tar.gz', download_url='https://github.com/esanchezm/prometheus-qbittorrent-exporter/archive/1.1.0.tar.gz',
keywords=['prometheus', 'qbittorrent'], keywords=['prometheus', 'qbittorrent'],
classifiers=[], classifiers=[],
python_requires='>=3', python_requires='>=3',
install_requires=['qbittorrent-api==2020.9.9', 'prometheus_client==0.8.0', 'python-json-logger==0.1.5'], install_requires=['attrdict==2.0.1', 'qbittorrent-api==2021.3.18', 'prometheus_client==0.8.0', 'python-json-logger==0.1.5'],
entry_points={ entry_points={
'console_scripts': [ 'console_scripts': [
'qbittorrent-exporter=qbittorrent_exporter.exporter:main', 'qbittorrent-exporter=qbittorrent_exporter.exporter:main',