Remove unneeded attridict dependency, add comments

This commit is contained in:
Joel Heaps 2023-09-29 09:46:51 -05:00
parent 0ac406c476
commit ea4bdecdcf
3 changed files with 49 additions and 34 deletions

11
pdm.lock generated
View File

@ -6,16 +6,7 @@ groups = ["default"]
cross_platform = true cross_platform = true
static_urls = false static_urls = false
lock_version = "4.3" lock_version = "4.3"
content_hash = "sha256:637d322b802d007082b71cf7d27382f3b6b5618434faf6b346358abe81fc2f7f" content_hash = "sha256:ec00e4f386c7e3cac870ab101184e565ee290c82a8b3d759fb7ad2762d0366ba"
[[package]]
name = "attridict"
version = "0.0.8"
summary = "A dict implementation with support for easy and clean access of its values through attributes"
files = [
{file = "attridict-0.0.8-py3-none-any.whl", hash = "sha256:8ee65af81f7762354e4514c443bbc04786a924c8e3e610c7883d2efbf323df6d"},
{file = "attridict-0.0.8.tar.gz", hash = "sha256:23a17671b9439d36e2bdb0a69c09f033abab0900a9df178e0f89aa1b2c42c5cd"},
]
[[package]] [[package]]
name = "certifi" name = "certifi"

View File

@ -9,12 +9,11 @@ dependencies = [
"prometheus-client>=0.17.1", "prometheus-client>=0.17.1",
"python-json-logger>=2.0.7", "python-json-logger>=2.0.7",
"qbittorrent-api>=2023.9.53", "qbittorrent-api>=2023.9.53",
"attridict>=0.0.8",
] ]
requires-python = ">=3.11" requires-python = ">=3.11"
readme = "README.md" readme = "README.md"
keywords = ["prometheus", "qbittorrent"] keywords = ["prometheus", "qbittorrent"]
license = "GPL-3.0" license = {text = "GPL-3.0"}
classifiers = [] classifiers = []
[project.urls] [project.urls]

View File

@ -3,7 +3,6 @@ import os
import sys import sys
import signal import signal
import faulthandler import faulthandler
import attridict
from qbittorrentapi import Client, TorrentStates from qbittorrentapi import Client, TorrentStates
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
@ -18,7 +17,11 @@ faulthandler.enable()
logger = logging.getLogger() logger = logging.getLogger()
class TorrentStatus(StrEnum): class TorrentStatuses(StrEnum):
"""
Represents possible torrent states.
"""
CHECKING = auto() CHECKING = auto()
COMPLETE = auto() COMPLETE = auto()
ERRORED = auto() ERRORED = auto()
@ -27,21 +30,29 @@ class TorrentStatus(StrEnum):
class MetricType(StrEnum): class MetricType(StrEnum):
"""
Represents possible metric types (used in this project).
"""
GAUGE = auto() GAUGE = auto()
COUNTER = auto() COUNTER = auto()
@dataclass @dataclass
class Metric: class Metric:
"""
Contains data and metadata about a single counter or gauge.
"""
name: str name: str
value: int | float | bool value: Any
labels: dict[str, str] = field(default_factory={}) labels: dict[str, str] = field(default_factory=lambda: {}) # Default to empty dict
help_text: str = "" help_text: str = ""
metric_type: MetricType = MetricType.GAUGE metric_type: MetricType = MetricType.GAUGE
class QbittorrentMetricsCollector: class QbittorrentMetricsCollector:
def __init__(self, config: dict[str, str | int | bool]) -> None: def __init__(self, config: dict) -> None:
self.config = config self.config = config
self.client = Client( self.client = Client(
host=config["host"], host=config["host"],
@ -52,28 +63,40 @@ class QbittorrentMetricsCollector:
) )
def collect(self) -> Iterable[GaugeMetricFamily | CounterMetricFamily]: def collect(self) -> Iterable[GaugeMetricFamily | CounterMetricFamily]:
"""
Gets Metric objects representing the current state of qbittorrent and yields
Prometheus gauges.
"""
metrics: list[Metric] = self.get_qbittorrent_metrics() metrics: list[Metric] = self.get_qbittorrent_metrics()
for metric in metrics: for metric in metrics:
if metric.metric_type == MetricType.COUNTER: if metric.metric_type == MetricType.COUNTER:
prom_metric = CounterMetricFamily( prom_metric = CounterMetricFamily(
metric.name, metric.help_text, labels=metric.labels.keys() metric.name, metric.help_text, labels=list(metric.labels.keys())
) )
else: else:
prom_metric = GaugeMetricFamily( prom_metric = GaugeMetricFamily(
metric.name, metric.help_text, labels=metric.labels.keys() metric.name, metric.help_text, labels=list(metric.labels.keys())
) )
prom_metric.add_metric(value=metric.value, labels=metric.labels.values()) prom_metric.add_metric(
value=metric.value, labels=list(metric.labels.values())
)
yield prom_metric yield prom_metric
def get_qbittorrent_metrics(self) -> list[Metric]: def get_qbittorrent_metrics(self) -> list[Metric]:
"""
Calls and combines qbittorrent state metrics with torrent metrics.
"""
metrics: list[Metric] = [] metrics: list[Metric] = []
metrics.extend(self._get_qbittorrent_status_metrics()) metrics.extend(self._get_qbittorrent_status_metrics())
metrics.extend(self._get_qbittorrent_torrent_tags_metrics()) metrics.extend(self._get_qbittorrent_torrent_tags_metrics())
return metrics return metrics
def _get_qbittorrent_status_metrics(self) -> list[dict]: def _get_qbittorrent_status_metrics(self) -> list[Metric]:
"""
Returns metrics about the state of the qbittorrent server.
"""
response: dict[str, Any] = {} response: dict[str, Any] = {}
version: str = "" version: str = ""
@ -147,22 +170,24 @@ class QbittorrentMetricsCollector:
return [] return []
metrics: list[Metric] = [] metrics: list[Metric] = []
categories.Uncategorized = attridict({"name": "Uncategorized", "savePath": ""})
# Match torrents to categories
for category in categories: for category in categories:
category_torrents = [ category_torrents: list = [
t torrent
for t in torrents for torrent in torrents
if t["category"] == category if torrent["category"] == category
or (category == "Uncategorized" and t["category"] == "") or (category == "Uncategorized" and torrent["category"] == "")
] ]
for status in TorrentStatus: # Match qbittorrentapi torrent state to local TorrenStatuses
status_prop = f"is_{status.value}" for status in TorrentStatuses:
proposed_status: str = f"is_{status.value}"
status_torrents = [ status_torrents = [
t torrent
for t in category_torrents for torrent in category_torrents
if getattr(TorrentStates, status_prop).fget( if getattr(TorrentStates, proposed_status).fget(
TorrentStates(t["state"]) TorrentStates(torrent["state"])
) )
] ]
metrics.append( metrics.append(
@ -255,7 +280,7 @@ def main():
# Register our custom collector # Register our custom collector
logger.info("Exporter is starting up") logger.info("Exporter is starting up")
REGISTRY.register(QbittorrentMetricsCollector(config)) REGISTRY.register(QbittorrentMetricsCollector(config)) # type: ignore
# Start server # Start server
start_http_server(config["exporter_port"]) start_http_server(config["exporter_port"])