183 lines
5.4 KiB
Go
183 lines
5.4 KiB
Go
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 fru1tstand’s exporter
|
||
}
|
||
log.Printf("Starting HTTP server at at %s", listenAddress)
|
||
|
||
log.Fatalf("Failed to start http server: %v", http.ListenAndServe(listenAddress, nil))
|
||
}
|