nixos-config/modules/qbittorrent/exporter/qbittorrent_exporter.go

191 lines
5.8 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.

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"`
SeedingTime int `json:"seeding_time"`
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,
)
qbittorrentTorrentSeedingTime = prometheus.NewDesc(
"qbittorrent_torrent_seeding_time_seconds",
"Time this torrent has been seeding",
[]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 <- qbittorrentTorrentSeedingTime
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(qbittorrentTorrentSeedingTime, prometheus.CounterValue, float64(torrentInfo.SeedingTime), 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))
}