nixos-config/modules/qbittorrent/exporter/qbittorrent_exporter.go
Simon Bruder f945341668
Relicense
This applies the REUSE specification to the repository, so the licensing
information can be tracked for every file individually.
2024-01-13 14:35:31 +01:00

187 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// SPDX-FileCopyrightText: 2022 Simon Bruder <simon@sbruder.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later
package main
import (
"context"
"encoding/json"
"io/ioutil"
"log"
"net"
"net/http"
"net/url"
"os"
"strconv"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
type ApiClient struct {
apiSocket string
}
type ApiError struct {
ReturnCode int
}
type TorrentInfo struct {
Auto bool `json:"auto_tmm"`
DownloadLimit int `json:"dl_limit"`
Downloaded int `json:"completed"`
Hash string `json:"hash"`
LeechsConnected int `json:"num_leechs"`
LeechsSwarm int `json:"num_incomplete"`
Name string `json:"name"`
SeedsConnected int `json:"num_seeds"`
SeedsSwarm int `json:"num_complete"`
Size int `json:"size"`
TotalSize int `json:"total_size"`
UploadLimit int `json:"ul_limit"`
Uploaded int `json:"uploaded"`
}
type Exporter struct {
Api ApiClient
}
var (
qbittorrentTorrentDownloaded = prometheus.NewDesc(
"qbittorrent_torrent_downloaded_bytes_total",
"Amount of data downloaded",
[]string{"hash", "name"}, nil,
)
qbittorrentTorrentLeechsConnected = prometheus.NewDesc(
"qbittorrent_torrent_leechs_connected",
"Number of leechs connected to",
[]string{"hash", "name"}, nil,
)
qbittorrentTorrentLeechsSwarm = prometheus.NewDesc(
"qbittorrent_torrent_leechs_swarm",
"Number of leechs in the swarm",
[]string{"hash", "name"}, nil,
)
qbittorrentTorrentSeedsConnected = prometheus.NewDesc(
"qbittorrent_torrent_seeds_connected",
"Number of seeds connected to",
[]string{"hash", "name"}, nil,
)
qbittorrentTorrentSeedsSwarm = prometheus.NewDesc(
"qbittorrent_torrent_seeds_swarm",
"Number of seeds in the swarm",
[]string{"hash", "name"}, nil,
)
qbittorrentTorrentSize = prometheus.NewDesc(
"qbittorrent_torrent_size_bytes_total",
"Size of selected torrent data",
[]string{"hash", "name"}, nil,
)
qbittorrentTorrentUploaded = prometheus.NewDesc(
"qbittorrent_torrent_uploaded_bytes_total",
"Amount of data uploaded",
[]string{"hash", "name"}, nil,
)
)
func (e ApiError) Error() string {
return strconv.Itoa(e.ReturnCode)
}
func CreateApiClient(apiSocket string) (c ApiClient) {
return ApiClient{
apiSocket: apiSocket,
}
}
func (c ApiClient) doRequest(group string, method string, parameters url.Values) (body []byte, err error) {
destinationUrl, err := url.Parse("http://unix/api/v2/" + group + "/" + method)
if err != nil {
log.Println(err)
return []byte{}, err
}
req := http.Request{
Method: "GET",
URL: destinationUrl,
Header: http.Header{},
Form: parameters,
}
httpClient := http.Client{
Transport: &http.Transport{
DialContext: func(_ context.Context, _, _ string) (net.Conn, error) {
return net.Dial("unix", c.apiSocket)
},
},
}
res, err := httpClient.Do(&req)
if err != nil {
log.Println(err)
return []byte{}, err
}
bodyBytes, err := ioutil.ReadAll(res.Body)
if err != nil {
return []byte{}, err
}
body = bodyBytes
if res.StatusCode != 200 {
err = ApiError{ReturnCode: res.StatusCode}
}
return
}
func (c ApiClient) TorrentsInfo() (torrentsInfo []TorrentInfo, err error) {
torrentsInfo = []TorrentInfo{}
body, err := c.doRequest("torrents", "info", url.Values{})
if err != nil {
return []TorrentInfo{}, err
}
json.Unmarshal(body, &torrentsInfo)
return
}
func (e Exporter) Describe(ch chan<- *prometheus.Desc) {
ch <- qbittorrentTorrentDownloaded
ch <- qbittorrentTorrentLeechsConnected
ch <- qbittorrentTorrentLeechsSwarm
ch <- qbittorrentTorrentSeedsConnected
ch <- qbittorrentTorrentSeedsSwarm
ch <- qbittorrentTorrentSize
ch <- qbittorrentTorrentUploaded
}
func (e Exporter) Collect(ch chan<- prometheus.Metric) {
torrentsInfo, err := e.Api.TorrentsInfo()
if err != nil {
log.Println(err)
return
}
for _, torrentInfo := range torrentsInfo {
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentDownloaded, prometheus.CounterValue, float64(torrentInfo.Downloaded), torrentInfo.Hash, torrentInfo.Name)
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentLeechsConnected, prometheus.GaugeValue, float64(torrentInfo.LeechsConnected), torrentInfo.Hash, torrentInfo.Name)
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentLeechsSwarm, prometheus.GaugeValue, float64(torrentInfo.LeechsSwarm), torrentInfo.Hash, torrentInfo.Name)
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentSeedsConnected, prometheus.GaugeValue, float64(torrentInfo.SeedsConnected), torrentInfo.Hash, torrentInfo.Name)
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentSeedsSwarm, prometheus.GaugeValue, float64(torrentInfo.SeedsSwarm), torrentInfo.Hash, torrentInfo.Name)
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentSize, prometheus.GaugeValue, float64(torrentInfo.Size), torrentInfo.Hash, torrentInfo.Name)
ch <- prometheus.MustNewConstMetric(qbittorrentTorrentUploaded, prometheus.CounterValue, float64(torrentInfo.Uploaded), torrentInfo.Hash, torrentInfo.Name)
}
}
func main() {
e := Exporter{
Api: CreateApiClient(
os.Getenv("QBITTORRENT_API_SOCKET"),
),
}
prometheus.MustRegister(e)
http.Handle("/metrics", promhttp.Handler())
listenAddress := os.Getenv("QBITTORRENT_EXPORTER_LISTEN_ADDRESS")
if listenAddress == "" {
listenAddress = ":9561" // this reuses the port number of fru1tstands exporter
}
log.Printf("Starting HTTP server at at %s", listenAddress)
log.Fatalf("Failed to start http server: %v", http.ListenAndServe(listenAddress, nil))
}