├── .drone.yml ├── .env.example ├── .gitignore ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── cmd └── transmission-exporter │ ├── main.go │ ├── session_collector.go │ ├── session_stats_collector.go │ └── torrent_collector.go ├── dashboards ├── dashboard.json ├── jsonnetfile.json ├── transmission.json ├── transmission.jsonnet └── transmission2024.json ├── examples ├── docker-compose.yml ├── kubernetes │ └── transmission.yml └── prometheus │ └── prometheus.yml ├── go.mod ├── go.sum ├── session.go ├── session_stats.go ├── torrent.go └── transmission.go /.drone.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: pipeline 3 | name: default 4 | 5 | platform: 6 | os: linux 7 | arch: amd64 8 | 9 | workspace: 10 | base: /go 11 | path: src/github.com/metalmatze/transmission-exporter 12 | 13 | steps: 14 | - name: build 15 | pull: default 16 | image: golang:1.13-alpine 17 | environment: 18 | GOPROXY: https://proxy.golang.org 19 | commands: 20 | - apk add -U git make 21 | - make fmt 22 | - make vet 23 | - make lint 24 | - make build 25 | 26 | - name: docker-master 27 | pull: always 28 | image: plugins/docker 29 | settings: 30 | username: 31 | from_secret: docker_username 32 | password: 33 | from_secret: docker_password 34 | repo: metalmatze/transmission-exporter 35 | tags: 36 | - master 37 | when: 38 | branch: 39 | - master 40 | event: 41 | - push 42 | 43 | - name: docker-tag 44 | pull: always 45 | image: plugins/docker 46 | settings: 47 | username: 48 | from_secret: docker_username 49 | password: 50 | from_secret: docker_password 51 | repo: metalmatze/transmission-exporter 52 | tag: 53 | - 0.3 54 | - 0.3.0 55 | - latest 56 | when: 57 | event: 58 | - tag 59 | 60 | ... 61 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | TRANSMISSION_ADDR=http://localhost:9091 2 | TRANSMISSION_PASSWORD= 3 | TRANSMISSION_USERNAME= 4 | WEB_ADDR=:19091 5 | WEB_PATH=/metrics 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.env 2 | /dashboards/jsonnetfile.lock.json 3 | /dashboards/vendor/ 4 | /transmission-exporter 5 | 6 | *.o 7 | *.a 8 | *.so 9 | _obj 10 | _test 11 | *.[568vq] 12 | [568vq].out 13 | *.cgo1.go 14 | *.cgo2.c 15 | _cgo_defun.c 16 | _cgo_gotypes.go 17 | _cgo_export.* 18 | _testmain.go 19 | *.exe 20 | *.test 21 | *.prof 22 | *.out 23 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM alpine:latest 2 | RUN apk add --update ca-certificates 3 | 4 | ADD ./transmission-exporter /usr/bin/transmission-exporter 5 | 6 | EXPOSE 19091 7 | 8 | ENTRYPOINT ["/usr/bin/transmission-exporter"] 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Matthias Loibl 4 | Copyright (c) 2014 Long Nguyen, Tobias Blom 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | GO ?= GO111MODULE=on CGO_ENABLED=0 go 2 | PACKAGES = $(shell go list ./... | grep -v /vendor/) 3 | 4 | .PHONY: all 5 | all: install 6 | 7 | .PHONY: clean 8 | clean: 9 | $(GO) clean -i ./... 10 | 11 | .PHONY: install 12 | install: 13 | $(GO) install -v ./cmd/transmission-exporter 14 | 15 | .PHONY: build 16 | build: 17 | $(GO) build -v ./cmd/transmission-exporter 18 | 19 | .PHONY: fmt 20 | fmt: 21 | $(GO) fmt $(PACKAGES) 22 | 23 | .PHONY: vet 24 | vet: 25 | $(GO) vet $(PACKAGES) 26 | 27 | .PHONY: lint 28 | lint: 29 | @which golint > /dev/null; if [ $$? -ne 0 ]; then \ 30 | $(GO) get -u golang.org/x/lint/golint; \ 31 | fi 32 | for PKG in $(PACKAGES); do golint -set_exit_status $$PKG || exit 1; done; 33 | 34 | .PHONY: dashboards 35 | dashboards: 36 | jsonnet fmt -i dashboards/transmission.jsonnet 37 | jsonnet -J dashboards/vendor -m dashboards -e "(import 'dashboards/transmission.jsonnet').grafanaDashboards" 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Transmission Exporter for Prometheus [![Build Status](https://cloud.drone.io/api/badges/metalmatze/transmission-exporter/status.svg)](https://cloud.drone.io/metalmatze/transmission-exporter) 2 | 3 | [![Docker Pulls](https://img.shields.io/docker/pulls/metalmatze/transmission-exporter.svg?maxAge=604800)](https://hub.docker.com/r/metalmatze/transmission-exporter) 4 | [![Go Report Card](https://goreportcard.com/badge/github.com/metalmatze/transmission-exporter)](https://goreportcard.com/report/github.com/metalmatze/transmission-exporter) 5 | 6 | Prometheus exporter for [Transmission](https://transmissionbt.com/) metrics, written in Go. 7 | 8 | # LOOKING FOR MAINTAINERS 9 | I don't use this exporter anymore and I'd be happy if others would want to take over and maintain it in the future! 10 | Write me a DM via [Twitter](https://twitter.com/metalmatze)! 11 | 12 | ### Installation 13 | 14 | $ go get github.com/metalmatze/transmission-exporter 15 | 16 | ### Configuration 17 | 18 | ENV Variable | Description 19 | |----------|-----| 20 | | WEB_PATH | Path for metrics, default: `/metrics` | 21 | | WEB_ADDR | Address for this exporter to run, default: `:19091` | 22 | | TRANSMISSION_ADDR | Transmission address to connect with, default: `http://localhost:9091` | 23 | | TRANSMISSION_USERNAME | Transmission username, no default | 24 | | TRANSMISSION_PASSWORD | Transmission password, no default | 25 | 26 | ### Docker 27 | 28 | docker pull metalmatze/transmission-exporter 29 | docker run -d -p 19091:19091 metalmatze/transmission-exporter 30 | 31 | ### Kubernetes (Prometheus) 32 | 33 | A sample kubernetes manifest is available in [example/kubernetes](https://github.com/metalmatze/transmission-exporter/blob/master/examples/kubernetes/docker-compose.yml) 34 | 35 | Please run: `kubectl apply -f examples/kubernetes/transmission.yml` 36 | 37 | You should: 38 | * Attach the config and downloads volume 39 | * Configure the password for the exporter 40 | 41 | Your prometheus instance will start scraping the metrics automatically. (if configured with annotation based discovery). [more info](https://www.weave.works/docs/cloud/latest/tasks/monitor/configuration-k8s/) 42 | 43 | ### Docker Compose 44 | 45 | Example `docker-compose.yml` with Transmission also running in docker. 46 | 47 | transmission: 48 | image: linuxserver/transmission 49 | restart: always 50 | ports: 51 | - "127.0.0.1:9091:9091" 52 | - "51413:51413" 53 | - "51413:51413/udp" 54 | transmission-exporter: 55 | image: metalmatze/transmission-exporter 56 | restart: always 57 | links: 58 | - transmission 59 | ports: 60 | - "127.0.0.1:19091:19091" 61 | environment: 62 | TRANSMISSION_ADDR: http://transmission:9091 63 | 64 | ### Development 65 | 66 | make 67 | 68 | For development we encourage you to use `make install` instead, it's faster. 69 | 70 | Now simply copy the `.env.example` to `.env`, like `cp .env.example .env` and set your preferences. 71 | Now you're good to go. 72 | 73 | ### Original authors of the Transmission package 74 | Tobias Blom (https://github.com/tubbebubbe/transmission) 75 | Long Nguyen (https://github.com/longnguyen11288/go-transmission) 76 | -------------------------------------------------------------------------------- /cmd/transmission-exporter/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "net/http" 6 | 7 | arg "github.com/alexflint/go-arg" 8 | "github.com/joho/godotenv" 9 | transmission "github.com/metalmatze/transmission-exporter" 10 | "github.com/prometheus/client_golang/prometheus" 11 | ) 12 | 13 | // Config gets its content from env and passes it on to different packages 14 | type Config struct { 15 | TransmissionAddr string `arg:"env:TRANSMISSION_ADDR"` 16 | TransmissionPassword string `arg:"env:TRANSMISSION_PASSWORD"` 17 | TransmissionUsername string `arg:"env:TRANSMISSION_USERNAME"` 18 | WebAddr string `arg:"env:WEB_ADDR"` 19 | WebPath string `arg:"env:WEB_PATH"` 20 | } 21 | 22 | func main() { 23 | log.Println("starting transmission-exporter") 24 | 25 | err := godotenv.Load() 26 | if err != nil { 27 | log.Println("no .env present") 28 | } 29 | 30 | c := Config{ 31 | WebPath: "/metrics", 32 | WebAddr: ":19091", 33 | TransmissionAddr: "http://localhost:9091", 34 | } 35 | 36 | arg.MustParse(&c) 37 | 38 | var user *transmission.User 39 | if c.TransmissionUsername != "" && c.TransmissionPassword != "" { 40 | user = &transmission.User{ 41 | Username: c.TransmissionUsername, 42 | Password: c.TransmissionPassword, 43 | } 44 | } 45 | 46 | client := transmission.New(c.TransmissionAddr, user) 47 | 48 | prometheus.MustRegister(NewTorrentCollector(client)) 49 | prometheus.MustRegister(NewSessionCollector(client)) 50 | prometheus.MustRegister(NewSessionStatsCollector(client)) 51 | 52 | http.Handle(c.WebPath, prometheus.Handler()) 53 | 54 | http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { 55 | w.Write([]byte(` 56 | Node Exporter 57 | 58 |

Transmission Exporter

59 |

Metrics

60 | 61 | `)) 62 | }) 63 | 64 | log.Fatal(http.ListenAndServe(c.WebAddr, nil)) 65 | } 66 | 67 | func boolToString(true bool) string { 68 | if true { 69 | return "1" 70 | } 71 | return "0" 72 | } 73 | -------------------------------------------------------------------------------- /cmd/transmission-exporter/session_collector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | 6 | "github.com/metalmatze/transmission-exporter" 7 | "github.com/prometheus/client_golang/prometheus" 8 | ) 9 | 10 | // SessionCollector exposes session metrics 11 | type SessionCollector struct { 12 | client *transmission.Client 13 | 14 | AltSpeedDown *prometheus.Desc 15 | AltSpeedUp *prometheus.Desc 16 | CacheSize *prometheus.Desc 17 | FreeSpace *prometheus.Desc 18 | QueueDown *prometheus.Desc 19 | QueueUp *prometheus.Desc 20 | PeerLimitGlobal *prometheus.Desc 21 | PeerLimitTorrent *prometheus.Desc 22 | SeedRatioLimit *prometheus.Desc 23 | SpeedLimitDown *prometheus.Desc 24 | SpeedLimitUp *prometheus.Desc 25 | Version *prometheus.Desc 26 | } 27 | 28 | // NewSessionCollector takes a transmission.Client and returns a SessionCollector 29 | func NewSessionCollector(client *transmission.Client) *SessionCollector { 30 | return &SessionCollector{ 31 | client: client, 32 | 33 | AltSpeedDown: prometheus.NewDesc( 34 | namespace+"alt_speed_down", 35 | "Alternative max global download speed", 36 | []string{"enabled"}, 37 | nil, 38 | ), 39 | AltSpeedUp: prometheus.NewDesc( 40 | namespace+"alt_speed_up", 41 | "Alternative max global upload speed", 42 | []string{"enabled"}, 43 | nil, 44 | ), 45 | CacheSize: prometheus.NewDesc( 46 | namespace+"cache_size_bytes", 47 | "Maximum size of the disk cache", 48 | nil, 49 | nil, 50 | ), 51 | FreeSpace: prometheus.NewDesc( 52 | namespace+"free_space", 53 | "Free space left on device to download to", 54 | []string{"download_dir", "incomplete_dir"}, 55 | nil, 56 | ), 57 | QueueDown: prometheus.NewDesc( 58 | namespace+"queue_down", 59 | "Max number of torrents to download at once", 60 | []string{"enabled"}, 61 | nil, 62 | ), 63 | QueueUp: prometheus.NewDesc( 64 | namespace+"queue_up", 65 | "Max number of torrents to upload at once", 66 | []string{"enabled"}, 67 | nil, 68 | ), 69 | PeerLimitGlobal: prometheus.NewDesc( 70 | namespace+"global_peer_limit", 71 | "Maximum global number of peers", 72 | nil, 73 | nil, 74 | ), 75 | PeerLimitTorrent: prometheus.NewDesc( 76 | namespace+"torrent_peer_limit", 77 | "Maximum number of peers for a single torrent", 78 | nil, 79 | nil, 80 | ), 81 | SeedRatioLimit: prometheus.NewDesc( 82 | namespace+"seed_ratio_limit", 83 | "The default seed ratio for torrents to use", 84 | []string{"enabled"}, 85 | nil, 86 | ), 87 | SpeedLimitDown: prometheus.NewDesc( 88 | namespace+"speed_limit_down_bytes", 89 | "Max global download speed", 90 | []string{"enabled"}, 91 | nil, 92 | ), 93 | SpeedLimitUp: prometheus.NewDesc( 94 | namespace+"speed_limit_up_bytes", 95 | "Max global upload speed", 96 | []string{"enabled"}, 97 | nil, 98 | ), 99 | Version: prometheus.NewDesc( 100 | namespace+"version", 101 | "Transmission version as label", 102 | []string{"version"}, 103 | nil, 104 | ), 105 | } 106 | } 107 | 108 | // Describe implements the prometheus.Collector interface 109 | func (sc *SessionCollector) Describe(ch chan<- *prometheus.Desc) { 110 | ch <- sc.AltSpeedDown 111 | ch <- sc.AltSpeedUp 112 | ch <- sc.CacheSize 113 | ch <- sc.FreeSpace 114 | ch <- sc.QueueDown 115 | ch <- sc.QueueUp 116 | ch <- sc.PeerLimitGlobal 117 | ch <- sc.PeerLimitTorrent 118 | ch <- sc.SeedRatioLimit 119 | ch <- sc.SpeedLimitDown 120 | ch <- sc.SpeedLimitUp 121 | ch <- sc.Version 122 | } 123 | 124 | // Collect implements the prometheus.Collector interface 125 | func (sc *SessionCollector) Collect(ch chan<- prometheus.Metric) { 126 | session, err := sc.client.GetSession() 127 | if err != nil { 128 | log.Printf("failed to get session: %v", err) 129 | return 130 | } 131 | 132 | ch <- prometheus.MustNewConstMetric( 133 | sc.AltSpeedDown, 134 | prometheus.GaugeValue, 135 | float64(session.AltSpeedDown), 136 | boolToString(session.AltSpeedEnabled), 137 | ) 138 | ch <- prometheus.MustNewConstMetric( 139 | sc.AltSpeedUp, 140 | prometheus.GaugeValue, 141 | float64(session.AltSpeedUp), 142 | boolToString(session.AltSpeedEnabled), 143 | ) 144 | ch <- prometheus.MustNewConstMetric( 145 | sc.CacheSize, 146 | prometheus.GaugeValue, 147 | float64(session.CacheSizeMB*1024*1024), 148 | ) 149 | ch <- prometheus.MustNewConstMetric( 150 | sc.FreeSpace, 151 | prometheus.GaugeValue, 152 | float64(session.DownloadDirFreeSpace), 153 | session.DownloadDir, session.IncompleteDir, 154 | ) 155 | ch <- prometheus.MustNewConstMetric( 156 | sc.QueueDown, 157 | prometheus.GaugeValue, 158 | float64(session.DownloadQueueSize), 159 | boolToString(session.DownloadQueueEnabled), 160 | ) 161 | ch <- prometheus.MustNewConstMetric( 162 | sc.QueueUp, 163 | prometheus.GaugeValue, 164 | float64(session.SeedQueueSize), 165 | boolToString(session.SeedQueueEnabled), 166 | ) 167 | ch <- prometheus.MustNewConstMetric( 168 | sc.PeerLimitGlobal, 169 | prometheus.GaugeValue, 170 | float64(session.PeerLimitGlobal), 171 | ) 172 | ch <- prometheus.MustNewConstMetric( 173 | sc.PeerLimitTorrent, 174 | prometheus.GaugeValue, 175 | float64(session.PeerLimitPerTorrent), 176 | ) 177 | ch <- prometheus.MustNewConstMetric( 178 | sc.SeedRatioLimit, 179 | prometheus.GaugeValue, 180 | float64(session.SeedRatioLimit), 181 | boolToString(session.SeedRatioLimited), 182 | ) 183 | ch <- prometheus.MustNewConstMetric( 184 | sc.SpeedLimitDown, 185 | prometheus.GaugeValue, 186 | float64(session.SpeedLimitDown), 187 | boolToString(session.SpeedLimitDownEnabled), 188 | ) 189 | ch <- prometheus.MustNewConstMetric( 190 | sc.SpeedLimitUp, 191 | prometheus.GaugeValue, 192 | float64(session.SpeedLimitUp), 193 | boolToString(session.SpeedLimitUpEnabled), 194 | ) 195 | ch <- prometheus.MustNewConstMetric( 196 | sc.Version, 197 | prometheus.GaugeValue, 198 | float64(1), 199 | session.Version, 200 | ) 201 | } 202 | -------------------------------------------------------------------------------- /cmd/transmission-exporter/session_stats_collector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "time" 6 | 7 | "github.com/metalmatze/transmission-exporter" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | // SessionStatsCollector exposes SessionStats as metrics 12 | type SessionStatsCollector struct { 13 | client *transmission.Client 14 | 15 | DownloadSpeed *prometheus.Desc 16 | UploadSpeed *prometheus.Desc 17 | TorrentsTotal *prometheus.Desc 18 | TorrentsActive *prometheus.Desc 19 | TorrentsPaused *prometheus.Desc 20 | 21 | Downloaded *prometheus.Desc 22 | Uploaded *prometheus.Desc 23 | FilesAdded *prometheus.Desc 24 | ActiveTime *prometheus.Desc 25 | SessionCount *prometheus.Desc 26 | } 27 | 28 | // NewSessionStatsCollector takes a transmission.Client and returns a SessionStatsCollector 29 | func NewSessionStatsCollector(client *transmission.Client) *SessionStatsCollector { 30 | const collectorNamespace = "session_stats_" 31 | 32 | return &SessionStatsCollector{ 33 | client: client, 34 | 35 | DownloadSpeed: prometheus.NewDesc( 36 | namespace+collectorNamespace+"download_speed_bytes", 37 | "Current download speed in bytes", 38 | nil, 39 | nil, 40 | ), 41 | UploadSpeed: prometheus.NewDesc( 42 | namespace+collectorNamespace+"upload_speed_bytes", 43 | "Current download speed in bytes", 44 | nil, 45 | nil, 46 | ), 47 | TorrentsTotal: prometheus.NewDesc( 48 | namespace+collectorNamespace+"torrents_total", 49 | "The total number of torrents", 50 | nil, 51 | nil, 52 | ), 53 | TorrentsActive: prometheus.NewDesc( 54 | namespace+collectorNamespace+"torrents_active", 55 | "The number of active torrents", 56 | nil, 57 | nil, 58 | ), 59 | TorrentsPaused: prometheus.NewDesc( 60 | namespace+collectorNamespace+"torrents_paused", 61 | "The number of paused torrents", 62 | nil, 63 | nil, 64 | ), 65 | 66 | Downloaded: prometheus.NewDesc( 67 | namespace+collectorNamespace+"downloaded_bytes", 68 | "The number of downloaded bytes", 69 | []string{"type"}, 70 | nil, 71 | ), 72 | Uploaded: prometheus.NewDesc( 73 | namespace+collectorNamespace+"uploaded_bytes", 74 | "The number of uploaded bytes", 75 | []string{"type"}, 76 | nil, 77 | ), 78 | FilesAdded: prometheus.NewDesc( 79 | namespace+collectorNamespace+"files_added", 80 | "The number of files added", 81 | []string{"type"}, 82 | nil, 83 | ), 84 | ActiveTime: prometheus.NewDesc( 85 | namespace+collectorNamespace+"active", 86 | "The time transmission is active since", 87 | []string{"type"}, 88 | nil, 89 | ), 90 | SessionCount: prometheus.NewDesc( 91 | namespace+collectorNamespace+"sessions", 92 | "Count of the times transmission started", 93 | []string{"type"}, 94 | nil, 95 | ), 96 | } 97 | } 98 | 99 | // Describe implements the prometheus.Collector interface 100 | func (sc *SessionStatsCollector) Describe(ch chan<- *prometheus.Desc) { 101 | ch <- sc.DownloadSpeed 102 | ch <- sc.UploadSpeed 103 | ch <- sc.TorrentsTotal 104 | ch <- sc.TorrentsActive 105 | ch <- sc.TorrentsPaused 106 | } 107 | 108 | // Collect implements the prometheus.Collector interface 109 | func (sc *SessionStatsCollector) Collect(ch chan<- prometheus.Metric) { 110 | stats, err := sc.client.GetSessionStats() 111 | if err != nil { 112 | log.Printf("failed to get session stats: %v", err) 113 | return 114 | } 115 | 116 | ch <- prometheus.MustNewConstMetric( 117 | sc.DownloadSpeed, 118 | prometheus.GaugeValue, 119 | float64(stats.DownloadSpeed), 120 | ) 121 | ch <- prometheus.MustNewConstMetric( 122 | sc.UploadSpeed, 123 | prometheus.GaugeValue, 124 | float64(stats.UploadSpeed), 125 | ) 126 | ch <- prometheus.MustNewConstMetric( 127 | sc.TorrentsTotal, 128 | prometheus.GaugeValue, 129 | float64(stats.TorrentCount), 130 | ) 131 | ch <- prometheus.MustNewConstMetric( 132 | sc.TorrentsActive, 133 | prometheus.GaugeValue, 134 | float64(stats.ActiveTorrentCount), 135 | ) 136 | ch <- prometheus.MustNewConstMetric( 137 | sc.TorrentsPaused, 138 | prometheus.GaugeValue, 139 | float64(stats.PausedTorrentCount), 140 | ) 141 | 142 | types := []string{"current", "cumulative"} 143 | for _, t := range types { 144 | var stateStats transmission.SessionStateStats 145 | if t == types[0] { 146 | stateStats = stats.CurrentStats 147 | } else { 148 | stateStats = stats.CumulativeStats 149 | } 150 | 151 | ch <- prometheus.MustNewConstMetric( 152 | sc.Downloaded, 153 | prometheus.GaugeValue, 154 | float64(stateStats.DownloadedBytes), 155 | t, 156 | ) 157 | ch <- prometheus.MustNewConstMetric( 158 | sc.Uploaded, 159 | prometheus.GaugeValue, 160 | float64(stateStats.UploadedBytes), 161 | t, 162 | ) 163 | ch <- prometheus.MustNewConstMetric( 164 | sc.FilesAdded, 165 | prometheus.GaugeValue, 166 | float64(stateStats.FilesAdded), 167 | t, 168 | ) 169 | 170 | dur := time.Duration(stateStats.SecondsActive) * time.Second 171 | timestamp := time.Now().Add(-1 * dur).Unix() 172 | 173 | ch <- prometheus.MustNewConstMetric( 174 | sc.ActiveTime, 175 | prometheus.GaugeValue, 176 | float64(timestamp), 177 | t, 178 | ) 179 | ch <- prometheus.MustNewConstMetric( 180 | sc.SessionCount, 181 | prometheus.GaugeValue, 182 | float64(stateStats.SessionCount), 183 | t, 184 | ) 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /cmd/transmission-exporter/torrent_collector.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "log" 5 | "strconv" 6 | 7 | transmission "github.com/metalmatze/transmission-exporter" 8 | "github.com/prometheus/client_golang/prometheus" 9 | ) 10 | 11 | const ( 12 | namespace string = "transmission_" 13 | ) 14 | 15 | // TorrentCollector has a transmission.Client to create torrent metrics 16 | type TorrentCollector struct { 17 | client *transmission.Client 18 | 19 | Status *prometheus.Desc 20 | Added *prometheus.Desc 21 | Files *prometheus.Desc 22 | Finished *prometheus.Desc 23 | Done *prometheus.Desc 24 | Ratio *prometheus.Desc 25 | Download *prometheus.Desc 26 | Upload *prometheus.Desc 27 | PeersConnected *prometheus.Desc 28 | PeersGettingFromUs *prometheus.Desc 29 | TotalSize *prometheus.Desc 30 | UploadedEver *prometheus.Desc 31 | 32 | // TrackerStats 33 | Downloads *prometheus.Desc 34 | Leechers *prometheus.Desc 35 | Seeders *prometheus.Desc 36 | } 37 | 38 | // NewTorrentCollector creates a new torrent collector with the transmission.Client 39 | func NewTorrentCollector(client *transmission.Client) *TorrentCollector { 40 | const collectorNamespace = "torrent_" 41 | 42 | return &TorrentCollector{ 43 | client: client, 44 | 45 | Status: prometheus.NewDesc( 46 | namespace+collectorNamespace+"status", 47 | "Status of a torrent", 48 | []string{"id", "name"}, 49 | nil, 50 | ), 51 | Added: prometheus.NewDesc( 52 | namespace+collectorNamespace+"added", 53 | "The unixtime time a torrent was added", 54 | []string{"id", "name"}, 55 | nil, 56 | ), 57 | Files: prometheus.NewDesc( 58 | namespace+collectorNamespace+"files_total", 59 | "The total number of files in a torrent", 60 | []string{"id", "name"}, 61 | nil, 62 | ), 63 | Finished: prometheus.NewDesc( 64 | namespace+collectorNamespace+"finished", 65 | "Indicates if a torrent is finished (1) or not (0)", 66 | []string{"id", "name"}, 67 | nil, 68 | ), 69 | Done: prometheus.NewDesc( 70 | namespace+collectorNamespace+"done", 71 | "The percent of a torrent being done", 72 | []string{"id", "name"}, 73 | nil, 74 | ), 75 | Ratio: prometheus.NewDesc( 76 | namespace+collectorNamespace+"ratio", 77 | "The upload ratio of a torrent", 78 | []string{"id", "name"}, 79 | nil, 80 | ), 81 | Download: prometheus.NewDesc( 82 | namespace+collectorNamespace+"download_bytes", 83 | "The current download rate of a torrent in bytes", 84 | []string{"id", "name"}, 85 | nil, 86 | ), 87 | Upload: prometheus.NewDesc( 88 | namespace+collectorNamespace+"upload_bytes", 89 | "The current upload rate of a torrent in bytes", 90 | []string{"id", "name"}, 91 | nil, 92 | ), 93 | PeersConnected: prometheus.NewDesc( 94 | namespace+collectorNamespace+"peers_connected", 95 | "The current number of peers connected to us", 96 | []string{"id", "name"}, 97 | nil, 98 | ), 99 | PeersGettingFromUs: prometheus.NewDesc( 100 | namespace+collectorNamespace+"peers_getting_from_us", 101 | "The current number of peers downloading from us", 102 | []string{"id", "name"}, 103 | nil, 104 | ), 105 | TotalSize: prometheus.NewDesc( 106 | namespace+collectorNamespace+"total_size", 107 | "The total size of the torrent", 108 | []string{"id", "name"}, 109 | nil, 110 | ), 111 | UploadedEver: prometheus.NewDesc( 112 | namespace+collectorNamespace+"uploaded_ever", 113 | "The total uploaded of the torrent", 114 | []string{"id", "name"}, 115 | nil, 116 | ), 117 | 118 | // TrackerStats 119 | Downloads: prometheus.NewDesc( 120 | namespace+collectorNamespace+"downloads_total", 121 | "How often this torrent was downloaded", 122 | []string{"id", "name", "tracker"}, 123 | nil, 124 | ), 125 | Leechers: prometheus.NewDesc( 126 | namespace+collectorNamespace+"leechers", 127 | "The number of peers downloading this torrent", 128 | []string{"id", "name", "tracker"}, 129 | nil, 130 | ), 131 | Seeders: prometheus.NewDesc( 132 | namespace+collectorNamespace+"seeders", 133 | "The number of peers uploading this torrent", 134 | []string{"id", "name", "tracker"}, 135 | nil, 136 | ), 137 | } 138 | } 139 | 140 | // Describe implements the prometheus.Collector interface 141 | func (tc *TorrentCollector) Describe(ch chan<- *prometheus.Desc) { 142 | ch <- tc.Status 143 | ch <- tc.Added 144 | ch <- tc.Files 145 | ch <- tc.Finished 146 | ch <- tc.Done 147 | ch <- tc.Ratio 148 | ch <- tc.Download 149 | ch <- tc.Upload 150 | ch <- tc.Downloads 151 | ch <- tc.Leechers 152 | ch <- tc.Seeders 153 | ch <- tc.PeersConnected 154 | ch <- tc.PeersGettingFromUs 155 | ch <- tc.TotalSize 156 | ch <- tc.UploadedEver 157 | } 158 | 159 | // Collect implements the prometheus.Collector interface 160 | func (tc *TorrentCollector) Collect(ch chan<- prometheus.Metric) { 161 | torrents, err := tc.client.GetTorrents() 162 | if err != nil { 163 | log.Printf("failed to get torrents: %v", err) 164 | return 165 | } 166 | 167 | for _, t := range torrents { 168 | var finished float64 169 | 170 | id := strconv.Itoa(t.ID) 171 | 172 | if t.IsFinished { 173 | finished = 1 174 | } 175 | 176 | ch <- prometheus.MustNewConstMetric( 177 | tc.Status, 178 | prometheus.GaugeValue, 179 | float64(t.Status), 180 | id, t.Name, 181 | ) 182 | ch <- prometheus.MustNewConstMetric( 183 | tc.Added, 184 | prometheus.GaugeValue, 185 | float64(t.Added), 186 | id, t.Name, 187 | ) 188 | ch <- prometheus.MustNewConstMetric( 189 | tc.Files, 190 | prometheus.GaugeValue, 191 | float64(len(t.Files)), 192 | id, t.Name, 193 | ) 194 | ch <- prometheus.MustNewConstMetric( 195 | tc.Finished, 196 | prometheus.GaugeValue, 197 | finished, 198 | id, t.Name, 199 | ) 200 | ch <- prometheus.MustNewConstMetric( 201 | tc.Done, 202 | prometheus.GaugeValue, 203 | t.PercentDone, 204 | id, t.Name, 205 | ) 206 | ch <- prometheus.MustNewConstMetric( 207 | tc.Ratio, 208 | prometheus.GaugeValue, 209 | t.UploadRatio, 210 | id, t.Name, 211 | ) 212 | ch <- prometheus.MustNewConstMetric( 213 | tc.Download, 214 | prometheus.GaugeValue, 215 | float64(t.RateDownload), 216 | id, t.Name, 217 | ) 218 | ch <- prometheus.MustNewConstMetric( 219 | tc.Upload, 220 | prometheus.GaugeValue, 221 | float64(t.RateUpload), 222 | id, t.Name, 223 | ) 224 | ch <- prometheus.MustNewConstMetric( 225 | tc.PeersConnected, 226 | prometheus.GaugeValue, 227 | float64(t.PeersConnected), 228 | id, t.Name, 229 | ) 230 | ch <- prometheus.MustNewConstMetric( 231 | tc.PeersGettingFromUs, 232 | prometheus.GaugeValue, 233 | float64(t.PeersGettingFromUs), 234 | id, t.Name, 235 | ) 236 | ch <- prometheus.MustNewConstMetric( 237 | tc.TotalSize, 238 | prometheus.GaugeValue, 239 | float64(t.TotalSize), 240 | id, t.Name, 241 | ) 242 | ch <- prometheus.MustNewConstMetric( 243 | tc.UploadedEver, 244 | prometheus.GaugeValue, 245 | float64(t.UploadedEver), 246 | id, t.Name, 247 | ) 248 | 249 | tstats := make(map[string]transmission.TrackerStat) 250 | 251 | for _, tracker := range t.TrackerStats { 252 | if tr, exists := tstats[tracker.Host]; exists { 253 | tr.DownloadCount += tracker.DownloadCount 254 | } else { 255 | tstats[tracker.Host] = tracker 256 | } 257 | } 258 | 259 | for _, tracker := range tstats { 260 | ch <- prometheus.MustNewConstMetric( 261 | tc.Downloads, 262 | prometheus.GaugeValue, 263 | float64(tracker.DownloadCount), 264 | id, t.Name, tracker.Host, 265 | ) 266 | 267 | ch <- prometheus.MustNewConstMetric( 268 | tc.Leechers, 269 | prometheus.GaugeValue, 270 | float64(tracker.LeecherCount), 271 | id, t.Name, tracker.Host, 272 | ) 273 | 274 | ch <- prometheus.MustNewConstMetric( 275 | tc.Seeders, 276 | prometheus.GaugeValue, 277 | float64(tracker.SeederCount), 278 | id, t.Name, tracker.Host, 279 | ) 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /dashboards/dashboard.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ 4 | { 5 | "builtIn": 1, 6 | "datasource": { 7 | "type": "datasource", 8 | "uid": "grafana" 9 | }, 10 | "enable": true, 11 | "hide": true, 12 | "iconColor": "rgba(0, 211, 255, 1)", 13 | "name": "Annotations & Alerts", 14 | "target": { 15 | "limit": 100, 16 | "matchAny": false, 17 | "tags": [], 18 | "type": "dashboard" 19 | }, 20 | "type": "dashboard" 21 | } 22 | ] 23 | }, 24 | "editable": true, 25 | "fiscalYearStartMonth": 0, 26 | "gnetId": 15116, 27 | "graphTooltip": 0, 28 | "id": 3, 29 | "links": [], 30 | "liveNow": false, 31 | "panels": [ 32 | { 33 | "datasource": { 34 | "type": "prometheus", 35 | "uid": "PBFA97CFB590B2093" 36 | }, 37 | "fieldConfig": { 38 | "defaults": { 39 | "decimals": 2, 40 | "mappings": [ 41 | { 42 | "id": 0, 43 | "op": "=", 44 | "text": "N/A", 45 | "type": 1, 46 | "value": "null" 47 | } 48 | ], 49 | "thresholds": { 50 | "mode": "absolute", 51 | "steps": [ 52 | { 53 | "color": "green", 54 | "value": null 55 | } 56 | ] 57 | }, 58 | "unit": "decbytes" 59 | }, 60 | "overrides": [] 61 | }, 62 | "gridPos": { 63 | "h": 5, 64 | "w": 6, 65 | "x": 0, 66 | "y": 0 67 | }, 68 | "id": 34, 69 | "options": { 70 | "colorMode": "value", 71 | "graphMode": "area", 72 | "justifyMode": "auto", 73 | "orientation": "horizontal", 74 | "reduceOptions": { 75 | "calcs": [ 76 | "lastNotNull" 77 | ], 78 | "fields": "", 79 | "values": false 80 | }, 81 | "showPercentChange": false, 82 | "text": {}, 83 | "textMode": "auto", 84 | "wideLayout": true 85 | }, 86 | "pluginVersion": "10.4.3", 87 | "targets": [ 88 | { 89 | "datasource": { 90 | "type": "prometheus", 91 | "uid": "PBFA97CFB590B2093" 92 | }, 93 | "disableTextWrap": false, 94 | "editorMode": "builder", 95 | "expr": "transmission_session_stats_downloaded_bytes{type=\"current\"}", 96 | "fullMetaSearch": false, 97 | "includeNullMetadata": true, 98 | "legendFormat": "__auto", 99 | "range": true, 100 | "refId": "A", 101 | "useBackend": false 102 | } 103 | ], 104 | "title": "Session Downloaded", 105 | "type": "stat" 106 | }, 107 | { 108 | "datasource": { 109 | "type": "prometheus", 110 | "uid": "PBFA97CFB590B2093" 111 | }, 112 | "fieldConfig": { 113 | "defaults": { 114 | "decimals": 2, 115 | "mappings": [], 116 | "thresholds": { 117 | "mode": "absolute", 118 | "steps": [ 119 | { 120 | "color": "green", 121 | "value": null 122 | } 123 | ] 124 | }, 125 | "unit": "decbytes" 126 | }, 127 | "overrides": [] 128 | }, 129 | "gridPos": { 130 | "h": 5, 131 | "w": 6, 132 | "x": 6, 133 | "y": 0 134 | }, 135 | "id": 30, 136 | "options": { 137 | "colorMode": "value", 138 | "graphMode": "area", 139 | "justifyMode": "auto", 140 | "orientation": "auto", 141 | "reduceOptions": { 142 | "calcs": [ 143 | "lastNotNull" 144 | ], 145 | "fields": "", 146 | "values": false 147 | }, 148 | "showPercentChange": false, 149 | "text": {}, 150 | "textMode": "auto", 151 | "wideLayout": true 152 | }, 153 | "pluginVersion": "10.4.3", 154 | "targets": [ 155 | { 156 | "datasource": { 157 | "type": "prometheus", 158 | "uid": "PBFA97CFB590B2093" 159 | }, 160 | "disableTextWrap": false, 161 | "editorMode": "builder", 162 | "expr": "transmission_session_stats_downloaded_bytes{type=\"cumulative\"}", 163 | "fullMetaSearch": false, 164 | "includeNullMetadata": true, 165 | "legendFormat": "__auto", 166 | "range": true, 167 | "refId": "A", 168 | "useBackend": false 169 | } 170 | ], 171 | "title": "All Time Downloaded", 172 | "type": "stat" 173 | }, 174 | { 175 | "datasource": { 176 | "type": "prometheus", 177 | "uid": "PBFA97CFB590B2093" 178 | }, 179 | "fieldConfig": { 180 | "defaults": { 181 | "decimals": 2, 182 | "mappings": [], 183 | "thresholds": { 184 | "mode": "absolute", 185 | "steps": [ 186 | { 187 | "color": "rgb(255, 172, 10)", 188 | "value": null 189 | } 190 | ] 191 | }, 192 | "unit": "decbytes" 193 | }, 194 | "overrides": [] 195 | }, 196 | "gridPos": { 197 | "h": 5, 198 | "w": 6, 199 | "x": 12, 200 | "y": 0 201 | }, 202 | "id": 32, 203 | "options": { 204 | "colorMode": "value", 205 | "graphMode": "area", 206 | "justifyMode": "auto", 207 | "orientation": "auto", 208 | "reduceOptions": { 209 | "calcs": [ 210 | "lastNotNull" 211 | ], 212 | "fields": "", 213 | "values": false 214 | }, 215 | "showPercentChange": false, 216 | "text": {}, 217 | "textMode": "auto", 218 | "wideLayout": true 219 | }, 220 | "pluginVersion": "10.4.3", 221 | "targets": [ 222 | { 223 | "datasource": { 224 | "type": "prometheus", 225 | "uid": "PBFA97CFB590B2093" 226 | }, 227 | "disableTextWrap": false, 228 | "editorMode": "builder", 229 | "expr": "transmission_session_stats_uploaded_bytes{type=\"cumulative\"}", 230 | "fullMetaSearch": false, 231 | "includeNullMetadata": true, 232 | "instant": false, 233 | "legendFormat": "__auto", 234 | "refId": "A", 235 | "useBackend": false 236 | } 237 | ], 238 | "title": "All Time Uploaded", 239 | "type": "stat" 240 | }, 241 | { 242 | "datasource": { 243 | "type": "prometheus", 244 | "uid": "PBFA97CFB590B2093" 245 | }, 246 | "fieldConfig": { 247 | "defaults": { 248 | "decimals": 2, 249 | "mappings": [], 250 | "thresholds": { 251 | "mode": "absolute", 252 | "steps": [ 253 | { 254 | "color": "rgb(255, 172, 10)", 255 | "value": null 256 | } 257 | ] 258 | }, 259 | "unit": "decbytes" 260 | }, 261 | "overrides": [] 262 | }, 263 | "gridPos": { 264 | "h": 5, 265 | "w": 6, 266 | "x": 18, 267 | "y": 0 268 | }, 269 | "id": 35, 270 | "options": { 271 | "colorMode": "value", 272 | "graphMode": "area", 273 | "justifyMode": "auto", 274 | "orientation": "auto", 275 | "reduceOptions": { 276 | "calcs": [ 277 | "lastNotNull" 278 | ], 279 | "fields": "", 280 | "values": false 281 | }, 282 | "showPercentChange": false, 283 | "text": {}, 284 | "textMode": "auto", 285 | "wideLayout": true 286 | }, 287 | "pluginVersion": "10.4.3", 288 | "targets": [ 289 | { 290 | "datasource": { 291 | "type": "prometheus", 292 | "uid": "PBFA97CFB590B2093" 293 | }, 294 | "disableTextWrap": false, 295 | "editorMode": "builder", 296 | "expr": "transmission_session_stats_uploaded_bytes{type=\"current\"}", 297 | "fullMetaSearch": false, 298 | "includeNullMetadata": true, 299 | "instant": false, 300 | "legendFormat": "__auto", 301 | "refId": "A", 302 | "useBackend": false 303 | } 304 | ], 305 | "title": "Session Uploaded", 306 | "type": "stat" 307 | }, 308 | { 309 | "datasource": { 310 | "type": "prometheus", 311 | "uid": "PBFA97CFB590B2093" 312 | }, 313 | "fieldConfig": { 314 | "defaults": { 315 | "color": { 316 | "mode": "thresholds" 317 | }, 318 | "mappings": [ 319 | { 320 | "options": { 321 | "match": "null", 322 | "result": { 323 | "text": "N/A" 324 | } 325 | }, 326 | "type": "special" 327 | } 328 | ], 329 | "thresholds": { 330 | "mode": "absolute", 331 | "steps": [ 332 | { 333 | "color": "green", 334 | "value": null 335 | }, 336 | { 337 | "color": "red", 338 | "value": 80 339 | } 340 | ] 341 | }, 342 | "unit": "none" 343 | }, 344 | "overrides": [] 345 | }, 346 | "gridPos": { 347 | "h": 4, 348 | "w": 3, 349 | "x": 0, 350 | "y": 5 351 | }, 352 | "id": 6, 353 | "maxDataPoints": 100, 354 | "options": { 355 | "colorMode": "none", 356 | "graphMode": "none", 357 | "justifyMode": "auto", 358 | "orientation": "horizontal", 359 | "reduceOptions": { 360 | "calcs": [ 361 | "mean" 362 | ], 363 | "fields": "", 364 | "values": false 365 | }, 366 | "showPercentChange": false, 367 | "text": {}, 368 | "textMode": "auto", 369 | "wideLayout": true 370 | }, 371 | "pluginVersion": "10.4.3", 372 | "targets": [ 373 | { 374 | "datasource": { 375 | "type": "prometheus", 376 | "uid": "PBFA97CFB590B2093" 377 | }, 378 | "editorMode": "code", 379 | "expr": "count(transmission_torrent_status != -1)", 380 | "instant": true, 381 | "interval": "", 382 | "legendFormat": "__auto", 383 | "refId": "A" 384 | } 385 | ], 386 | "title": "Total Torrents", 387 | "type": "stat" 388 | }, 389 | { 390 | "datasource": { 391 | "type": "prometheus", 392 | "uid": "PBFA97CFB590B2093" 393 | }, 394 | "fieldConfig": { 395 | "defaults": { 396 | "color": { 397 | "mode": "thresholds" 398 | }, 399 | "mappings": [ 400 | { 401 | "options": { 402 | "match": "null", 403 | "result": { 404 | "color": "rgb(33, 33, 35)", 405 | "text": "0" 406 | } 407 | }, 408 | "type": "special" 409 | } 410 | ], 411 | "thresholds": { 412 | "mode": "absolute", 413 | "steps": [ 414 | { 415 | "color": "rgb(33, 33, 35)", 416 | "value": null 417 | }, 418 | { 419 | "color": "#37872D", 420 | "value": 1 421 | }, 422 | { 423 | "color": "#d44a3a" 424 | } 425 | ] 426 | }, 427 | "unit": "none" 428 | }, 429 | "overrides": [] 430 | }, 431 | "gridPos": { 432 | "h": 4, 433 | "w": 3, 434 | "x": 3, 435 | "y": 5 436 | }, 437 | "id": 8, 438 | "maxDataPoints": 100, 439 | "options": { 440 | "colorMode": "background", 441 | "graphMode": "none", 442 | "justifyMode": "auto", 443 | "orientation": "horizontal", 444 | "reduceOptions": { 445 | "calcs": [ 446 | "mean" 447 | ], 448 | "fields": "", 449 | "values": false 450 | }, 451 | "showPercentChange": false, 452 | "text": {}, 453 | "textMode": "auto", 454 | "wideLayout": true 455 | }, 456 | "pluginVersion": "10.4.3", 457 | "targets": [ 458 | { 459 | "datasource": { 460 | "type": "prometheus", 461 | "uid": "PBFA97CFB590B2093" 462 | }, 463 | "editorMode": "code", 464 | "expr": "sum(transmission_torrent_peers_sending_to_us)", 465 | "instant": true, 466 | "legendFormat": "__auto", 467 | "refId": "A" 468 | } 469 | ], 470 | "title": "Downloading From", 471 | "type": "stat" 472 | }, 473 | { 474 | "datasource": { 475 | "type": "prometheus", 476 | "uid": "PBFA97CFB590B2093" 477 | }, 478 | "fieldConfig": { 479 | "defaults": { 480 | "color": { 481 | "mode": "thresholds" 482 | }, 483 | "decimals": 2, 484 | "mappings": [ 485 | { 486 | "options": { 487 | "match": "null", 488 | "result": { 489 | "text": "N/A" 490 | } 491 | }, 492 | "type": "special" 493 | } 494 | ], 495 | "thresholds": { 496 | "mode": "absolute", 497 | "steps": [ 498 | { 499 | "color": "transparent", 500 | "value": null 501 | }, 502 | { 503 | "color": "#37872D", 504 | "value": 1 505 | } 506 | ] 507 | }, 508 | "unit": "Bps" 509 | }, 510 | "overrides": [] 511 | }, 512 | "gridPos": { 513 | "h": 4, 514 | "w": 6, 515 | "x": 6, 516 | "y": 5 517 | }, 518 | "id": 12, 519 | "maxDataPoints": 100, 520 | "options": { 521 | "colorMode": "background", 522 | "graphMode": "none", 523 | "justifyMode": "auto", 524 | "orientation": "horizontal", 525 | "reduceOptions": { 526 | "calcs": [ 527 | "mean" 528 | ], 529 | "fields": "", 530 | "values": false 531 | }, 532 | "showPercentChange": false, 533 | "text": {}, 534 | "textMode": "auto", 535 | "wideLayout": true 536 | }, 537 | "pluginVersion": "10.4.3", 538 | "targets": [ 539 | { 540 | "datasource": { 541 | "type": "prometheus", 542 | "uid": "PBFA97CFB590B2093" 543 | }, 544 | "editorMode": "code", 545 | "expr": "transmission_session_stats_download_speed_bytes", 546 | "instant": true, 547 | "legendFormat": "__auto", 548 | "refId": "A" 549 | } 550 | ], 551 | "title": "Global Download Speed", 552 | "type": "stat" 553 | }, 554 | { 555 | "datasource": { 556 | "type": "prometheus", 557 | "uid": "PBFA97CFB590B2093" 558 | }, 559 | "fieldConfig": { 560 | "defaults": { 561 | "color": { 562 | "mode": "thresholds" 563 | }, 564 | "decimals": 2, 565 | "mappings": [ 566 | { 567 | "options": { 568 | "match": "null", 569 | "result": { 570 | "text": "N/A" 571 | } 572 | }, 573 | "type": "special" 574 | } 575 | ], 576 | "thresholds": { 577 | "mode": "absolute", 578 | "steps": [ 579 | { 580 | "color": "rgb(33, 33, 35)", 581 | "value": null 582 | }, 583 | { 584 | "color": "rgba(237, 158, 40, 0.89)", 585 | "value": 1 586 | }, 587 | { 588 | "color": "#d44a3a" 589 | } 590 | ] 591 | }, 592 | "unit": "Bps" 593 | }, 594 | "overrides": [] 595 | }, 596 | "gridPos": { 597 | "h": 4, 598 | "w": 6, 599 | "x": 12, 600 | "y": 5 601 | }, 602 | "id": 14, 603 | "maxDataPoints": 100, 604 | "options": { 605 | "colorMode": "background", 606 | "graphMode": "none", 607 | "justifyMode": "auto", 608 | "orientation": "horizontal", 609 | "reduceOptions": { 610 | "calcs": [ 611 | "mean" 612 | ], 613 | "fields": "", 614 | "values": false 615 | }, 616 | "showPercentChange": false, 617 | "text": {}, 618 | "textMode": "auto", 619 | "wideLayout": true 620 | }, 621 | "pluginVersion": "10.4.3", 622 | "targets": [ 623 | { 624 | "datasource": { 625 | "type": "prometheus", 626 | "uid": "PBFA97CFB590B2093" 627 | }, 628 | "editorMode": "code", 629 | "expr": "transmission_session_stats_upload_speed_bytes", 630 | "instant": true, 631 | "refId": "A" 632 | } 633 | ], 634 | "title": "Global Upload Speed", 635 | "type": "stat" 636 | }, 637 | { 638 | "datasource": { 639 | "type": "prometheus", 640 | "uid": "PBFA97CFB590B2093" 641 | }, 642 | "fieldConfig": { 643 | "defaults": { 644 | "color": { 645 | "mode": "thresholds" 646 | }, 647 | "mappings": [ 648 | { 649 | "options": { 650 | "match": "null", 651 | "result": { 652 | "color": "rgb(33, 33, 35)", 653 | "text": "0" 654 | } 655 | }, 656 | "type": "special" 657 | } 658 | ], 659 | "thresholds": { 660 | "mode": "absolute", 661 | "steps": [ 662 | { 663 | "color": "rgb(33, 33, 35)", 664 | "value": null 665 | }, 666 | { 667 | "color": "red", 668 | "value": 0 669 | }, 670 | { 671 | "color": "orange", 672 | "value": 7 673 | }, 674 | { 675 | "color": "yellow", 676 | "value": 15 677 | }, 678 | { 679 | "color": "green", 680 | "value": 25 681 | } 682 | ] 683 | }, 684 | "unit": "none" 685 | }, 686 | "overrides": [] 687 | }, 688 | "gridPos": { 689 | "h": 4, 690 | "w": 3, 691 | "x": 18, 692 | "y": 5 693 | }, 694 | "id": 10, 695 | "maxDataPoints": 100, 696 | "options": { 697 | "colorMode": "background", 698 | "graphMode": "none", 699 | "justifyMode": "auto", 700 | "orientation": "horizontal", 701 | "reduceOptions": { 702 | "calcs": [ 703 | "mean" 704 | ], 705 | "fields": "", 706 | "values": false 707 | }, 708 | "showPercentChange": false, 709 | "text": {}, 710 | "textMode": "auto", 711 | "wideLayout": true 712 | }, 713 | "pluginVersion": "10.4.3", 714 | "targets": [ 715 | { 716 | "datasource": { 717 | "type": "prometheus", 718 | "uid": "PBFA97CFB590B2093" 719 | }, 720 | "editorMode": "code", 721 | "expr": "sum(transmission_torrent_peers_getting_from_us)", 722 | "instant": true, 723 | "refId": "A" 724 | } 725 | ], 726 | "title": "Seeding To", 727 | "type": "stat" 728 | }, 729 | { 730 | "datasource": { 731 | "type": "prometheus", 732 | "uid": "PBFA97CFB590B2093" 733 | }, 734 | "fieldConfig": { 735 | "defaults": { 736 | "color": { 737 | "mode": "thresholds" 738 | }, 739 | "decimals": 2, 740 | "mappings": [], 741 | "max": 1, 742 | "min": 0, 743 | "thresholds": { 744 | "mode": "absolute", 745 | "steps": [ 746 | { 747 | "color": "green", 748 | "value": null 749 | } 750 | ] 751 | } 752 | }, 753 | "overrides": [] 754 | }, 755 | "gridPos": { 756 | "h": 4, 757 | "w": 3, 758 | "x": 21, 759 | "y": 5 760 | }, 761 | "id": 37, 762 | "options": { 763 | "minVizHeight": 75, 764 | "minVizWidth": 75, 765 | "orientation": "auto", 766 | "reduceOptions": { 767 | "calcs": [ 768 | "lastNotNull" 769 | ], 770 | "fields": "", 771 | "values": false 772 | }, 773 | "showThresholdLabels": false, 774 | "showThresholdMarkers": true, 775 | "sizing": "auto", 776 | "text": {} 777 | }, 778 | "pluginVersion": "10.4.3", 779 | "targets": [ 780 | { 781 | "datasource": { 782 | "type": "prometheus", 783 | "uid": "PBFA97CFB590B2093" 784 | }, 785 | "disableTextWrap": false, 786 | "editorMode": "code", 787 | "exemplar": true, 788 | "expr": "transmission_session_stats_uploaded_bytes{type=\"cumulative\"} / transmission_session_stats_downloaded_bytes{type=\"cumulative\"}", 789 | "fullMetaSearch": false, 790 | "includeNullMetadata": true, 791 | "interval": "", 792 | "legendFormat": "__auto", 793 | "range": true, 794 | "refId": "A", 795 | "useBackend": false 796 | } 797 | ], 798 | "title": "Global Ratio", 799 | "type": "gauge" 800 | }, 801 | { 802 | "datasource": { 803 | "type": "prometheus", 804 | "uid": "PBFA97CFB590B2093" 805 | }, 806 | "fieldConfig": { 807 | "defaults": { 808 | "color": { 809 | "mode": "palette-classic" 810 | }, 811 | "custom": { 812 | "axisBorderShow": false, 813 | "axisCenteredZero": false, 814 | "axisColorMode": "text", 815 | "axisLabel": "", 816 | "axisPlacement": "auto", 817 | "barAlignment": 0, 818 | "drawStyle": "line", 819 | "fillOpacity": 10, 820 | "gradientMode": "none", 821 | "hideFrom": { 822 | "legend": false, 823 | "tooltip": false, 824 | "viz": false 825 | }, 826 | "insertNulls": false, 827 | "lineInterpolation": "linear", 828 | "lineWidth": 1, 829 | "pointSize": 5, 830 | "scaleDistribution": { 831 | "type": "linear" 832 | }, 833 | "showPoints": "never", 834 | "spanNulls": false, 835 | "stacking": { 836 | "group": "A", 837 | "mode": "none" 838 | }, 839 | "thresholdsStyle": { 840 | "mode": "off" 841 | } 842 | }, 843 | "decimals": 2, 844 | "links": [], 845 | "mappings": [], 846 | "min": 0, 847 | "thresholds": { 848 | "mode": "absolute", 849 | "steps": [ 850 | { 851 | "color": "green", 852 | "value": null 853 | }, 854 | { 855 | "color": "orange", 856 | "value": 50 857 | }, 858 | { 859 | "color": "red", 860 | "value": 70 861 | } 862 | ] 863 | }, 864 | "unit": "Bps" 865 | }, 866 | "overrides": [] 867 | }, 868 | "gridPos": { 869 | "h": 7, 870 | "w": 24, 871 | "x": 0, 872 | "y": 9 873 | }, 874 | "id": 2, 875 | "interval": "5", 876 | "options": { 877 | "legend": { 878 | "calcs": [], 879 | "displayMode": "list", 880 | "placement": "right", 881 | "showLegend": true 882 | }, 883 | "tooltip": { 884 | "mode": "multi", 885 | "sort": "none" 886 | } 887 | }, 888 | "pluginVersion": "10.4.3", 889 | "targets": [ 890 | { 891 | "datasource": { 892 | "type": "prometheus", 893 | "uid": "PBFA97CFB590B2093" 894 | }, 895 | "editorMode": "code", 896 | "expr": "transmission_session_stats_download_speed_bytes", 897 | "interval": "", 898 | "legendFormat": "Downloaded", 899 | "range": true, 900 | "refId": "A" 901 | }, 902 | { 903 | "datasource": { 904 | "type": "prometheus", 905 | "uid": "PBFA97CFB590B2093" 906 | }, 907 | "editorMode": "code", 908 | "expr": "transmission_session_stats_upload_speed_bytes", 909 | "interval": "", 910 | "legendFormat": "Upload", 911 | "range": true, 912 | "refId": "B" 913 | } 914 | ], 915 | "title": "Upload / Download Speeds", 916 | "type": "timeseries" 917 | }, 918 | { 919 | "datasource": { 920 | "type": "prometheus", 921 | "uid": "PBFA97CFB590B2093" 922 | }, 923 | "fieldConfig": { 924 | "defaults": { 925 | "color": { 926 | "mode": "palette-classic" 927 | }, 928 | "custom": { 929 | "axisBorderShow": false, 930 | "axisCenteredZero": false, 931 | "axisColorMode": "text", 932 | "axisGridShow": true, 933 | "axisLabel": "", 934 | "axisPlacement": "left", 935 | "barAlignment": 0, 936 | "drawStyle": "line", 937 | "fillOpacity": 11, 938 | "gradientMode": "none", 939 | "hideFrom": { 940 | "legend": false, 941 | "tooltip": false, 942 | "viz": false 943 | }, 944 | "insertNulls": false, 945 | "lineInterpolation": "smooth", 946 | "lineStyle": { 947 | "fill": "solid" 948 | }, 949 | "lineWidth": 1.4, 950 | "pointSize": 1, 951 | "scaleDistribution": { 952 | "type": "linear" 953 | }, 954 | "showPoints": "never", 955 | "spanNulls": false, 956 | "stacking": { 957 | "group": "A", 958 | "mode": "none" 959 | }, 960 | "thresholdsStyle": { 961 | "mode": "off" 962 | } 963 | }, 964 | "decimals": 2, 965 | "fieldMinMax": false, 966 | "links": [], 967 | "mappings": [], 968 | "thresholds": { 969 | "mode": "absolute", 970 | "steps": [ 971 | { 972 | "color": "green", 973 | "value": null 974 | }, 975 | { 976 | "color": "red", 977 | "value": 80 978 | } 979 | ] 980 | }, 981 | "unit": "Bps" 982 | }, 983 | "overrides": [] 984 | }, 985 | "gridPos": { 986 | "h": 7, 987 | "w": 24, 988 | "x": 0, 989 | "y": 16 990 | }, 991 | "id": 26, 992 | "interval": "5", 993 | "options": { 994 | "legend": { 995 | "calcs": [], 996 | "displayMode": "list", 997 | "placement": "bottom", 998 | "showLegend": false, 999 | "width": 200 1000 | }, 1001 | "tooltip": { 1002 | "hoverProximity": 1, 1003 | "mode": "multi", 1004 | "sort": "desc" 1005 | } 1006 | }, 1007 | "pluginVersion": "10.4.3", 1008 | "targets": [ 1009 | { 1010 | "datasource": { 1011 | "type": "prometheus", 1012 | "uid": "PBFA97CFB590B2093" 1013 | }, 1014 | "disableTextWrap": false, 1015 | "editorMode": "builder", 1016 | "exemplar": false, 1017 | "expr": "topk(5, transmission_torrent_upload_bytes)", 1018 | "format": "time_series", 1019 | "fullMetaSearch": false, 1020 | "includeNullMetadata": true, 1021 | "instant": false, 1022 | "legendFormat": "{{name}}", 1023 | "range": true, 1024 | "refId": "A", 1025 | "useBackend": false 1026 | } 1027 | ], 1028 | "title": "Upload Speed by Torrent", 1029 | "type": "timeseries" 1030 | }, 1031 | { 1032 | "datasource": { 1033 | "type": "prometheus", 1034 | "uid": "PBFA97CFB590B2093" 1035 | }, 1036 | "fieldConfig": { 1037 | "defaults": { 1038 | "color": { 1039 | "mode": "thresholds" 1040 | }, 1041 | "custom": { 1042 | "align": "auto", 1043 | "cellOptions": { 1044 | "type": "auto" 1045 | }, 1046 | "inspect": false 1047 | }, 1048 | "mappings": [], 1049 | "thresholds": { 1050 | "mode": "absolute", 1051 | "steps": [ 1052 | { 1053 | "color": "green", 1054 | "value": null 1055 | }, 1056 | { 1057 | "color": "red", 1058 | "value": 80 1059 | } 1060 | ] 1061 | } 1062 | }, 1063 | "overrides": [ 1064 | { 1065 | "matcher": { 1066 | "id": "byName", 1067 | "options": "Progress" 1068 | }, 1069 | "properties": [ 1070 | { 1071 | "id": "unit", 1072 | "value": "percentunit" 1073 | }, 1074 | { 1075 | "id": "decimals", 1076 | "value": 2 1077 | } 1078 | ] 1079 | }, 1080 | { 1081 | "matcher": { 1082 | "id": "byName", 1083 | "options": "Download Speed" 1084 | }, 1085 | "properties": [ 1086 | { 1087 | "id": "unit", 1088 | "value": "Bps" 1089 | }, 1090 | { 1091 | "id": "decimals", 1092 | "value": 2 1093 | } 1094 | ] 1095 | }, 1096 | { 1097 | "matcher": { 1098 | "id": "byName", 1099 | "options": "Upload Speed" 1100 | }, 1101 | "properties": [ 1102 | { 1103 | "id": "unit", 1104 | "value": "Bps" 1105 | }, 1106 | { 1107 | "id": "decimals", 1108 | "value": 2 1109 | } 1110 | ] 1111 | }, 1112 | { 1113 | "matcher": { 1114 | "id": "byName", 1115 | "options": "Bytes Downloaded" 1116 | }, 1117 | "properties": [ 1118 | { 1119 | "id": "unit", 1120 | "value": "decbytes" 1121 | }, 1122 | { 1123 | "id": "decimals", 1124 | "value": 2 1125 | } 1126 | ] 1127 | }, 1128 | { 1129 | "matcher": { 1130 | "id": "byName", 1131 | "options": "Bytes Uploaded" 1132 | }, 1133 | "properties": [ 1134 | { 1135 | "id": "unit", 1136 | "value": "decbytes" 1137 | }, 1138 | { 1139 | "id": "decimals", 1140 | "value": 2 1141 | } 1142 | ] 1143 | }, 1144 | { 1145 | "matcher": { 1146 | "id": "byName", 1147 | "options": "Date Added" 1148 | }, 1149 | "properties": [ 1150 | { 1151 | "id": "unit", 1152 | "value": "dateTimeAsSystem" 1153 | } 1154 | ] 1155 | }, 1156 | { 1157 | "matcher": { 1158 | "id": "byName", 1159 | "options": "Ratio" 1160 | }, 1161 | "properties": [ 1162 | { 1163 | "id": "decimals", 1164 | "value": 2 1165 | } 1166 | ] 1167 | }, 1168 | { 1169 | "matcher": { 1170 | "id": "byName", 1171 | "options": "Status" 1172 | }, 1173 | "properties": [ 1174 | { 1175 | "id": "mappings", 1176 | "value": [ 1177 | { 1178 | "options": { 1179 | "0": { 1180 | "color": "red", 1181 | "index": 0, 1182 | "text": "Stopped" 1183 | }, 1184 | "1": { 1185 | "color": "orange", 1186 | "index": 1, 1187 | "text": "Checking" 1188 | }, 1189 | "2": { 1190 | "color": "orange", 1191 | "index": 2, 1192 | "text": "Checking" 1193 | }, 1194 | "3": { 1195 | "color": "blue", 1196 | "index": 3, 1197 | "text": "Downloading" 1198 | }, 1199 | "4": { 1200 | "color": "blue", 1201 | "index": 4, 1202 | "text": "Downloading" 1203 | }, 1204 | "5": { 1205 | "color": "green", 1206 | "index": 5, 1207 | "text": "Seeding" 1208 | }, 1209 | "6": { 1210 | "color": "green", 1211 | "index": 6, 1212 | "text": "Seeding" 1213 | } 1214 | }, 1215 | "type": "value" 1216 | } 1217 | ] 1218 | } 1219 | ] 1220 | } 1221 | ] 1222 | }, 1223 | "gridPos": { 1224 | "h": 7, 1225 | "w": 24, 1226 | "x": 0, 1227 | "y": 23 1228 | }, 1229 | "id": 24, 1230 | "options": { 1231 | "cellHeight": "sm", 1232 | "footer": { 1233 | "countRows": false, 1234 | "fields": "", 1235 | "reducer": [ 1236 | "sum" 1237 | ], 1238 | "show": false 1239 | }, 1240 | "frameIndex": 0, 1241 | "showHeader": true, 1242 | "sortBy": [ 1243 | { 1244 | "desc": true, 1245 | "displayName": "Upload Speed" 1246 | } 1247 | ] 1248 | }, 1249 | "pluginVersion": "10.4.3", 1250 | "targets": [ 1251 | { 1252 | "datasource": { 1253 | "type": "prometheus", 1254 | "uid": "PBFA97CFB590B2093" 1255 | }, 1256 | "disableTextWrap": false, 1257 | "editorMode": "builder", 1258 | "exemplar": false, 1259 | "expr": "transmission_torrent_added * 1000", 1260 | "format": "table", 1261 | "fullMetaSearch": false, 1262 | "hide": false, 1263 | "includeNullMetadata": false, 1264 | "instant": true, 1265 | "legendFormat": "Added", 1266 | "range": false, 1267 | "refId": "A", 1268 | "useBackend": false 1269 | }, 1270 | { 1271 | "datasource": { 1272 | "type": "prometheus", 1273 | "uid": "PBFA97CFB590B2093" 1274 | }, 1275 | "editorMode": "code", 1276 | "exemplar": false, 1277 | "expr": "transmission_torrent_done", 1278 | "format": "table", 1279 | "hide": false, 1280 | "instant": true, 1281 | "legendFormat": "Done", 1282 | "range": false, 1283 | "refId": "B" 1284 | }, 1285 | { 1286 | "datasource": { 1287 | "type": "prometheus", 1288 | "uid": "PBFA97CFB590B2093" 1289 | }, 1290 | "editorMode": "code", 1291 | "exemplar": false, 1292 | "expr": "transmission_torrent_downloaded_ever_bytes", 1293 | "format": "table", 1294 | "hide": false, 1295 | "instant": true, 1296 | "legendFormat": "Bytes Downloaded", 1297 | "range": false, 1298 | "refId": "C" 1299 | }, 1300 | { 1301 | "datasource": { 1302 | "type": "prometheus", 1303 | "uid": "PBFA97CFB590B2093" 1304 | }, 1305 | "editorMode": "code", 1306 | "exemplar": false, 1307 | "expr": "transmission_torrent_peers_getting_from_us", 1308 | "format": "table", 1309 | "hide": false, 1310 | "instant": true, 1311 | "legendFormat": "Leechers", 1312 | "range": false, 1313 | "refId": "D" 1314 | }, 1315 | { 1316 | "datasource": { 1317 | "type": "prometheus", 1318 | "uid": "PBFA97CFB590B2093" 1319 | }, 1320 | "editorMode": "code", 1321 | "exemplar": false, 1322 | "expr": "transmission_torrent_peers_sending_to_us", 1323 | "format": "table", 1324 | "hide": false, 1325 | "instant": true, 1326 | "legendFormat": "Seeders", 1327 | "range": false, 1328 | "refId": "E" 1329 | }, 1330 | { 1331 | "datasource": { 1332 | "type": "prometheus", 1333 | "uid": "PBFA97CFB590B2093" 1334 | }, 1335 | "editorMode": "code", 1336 | "exemplar": false, 1337 | "expr": "transmission_torrent_ratio", 1338 | "format": "table", 1339 | "hide": false, 1340 | "instant": true, 1341 | "legendFormat": "Ratio", 1342 | "range": false, 1343 | "refId": "F" 1344 | }, 1345 | { 1346 | "datasource": { 1347 | "type": "prometheus", 1348 | "uid": "PBFA97CFB590B2093" 1349 | }, 1350 | "editorMode": "code", 1351 | "exemplar": false, 1352 | "expr": "transmission_torrent_uploaded_ever_bytes", 1353 | "format": "table", 1354 | "hide": false, 1355 | "instant": true, 1356 | "legendFormat": "Bytes Uploaded", 1357 | "range": false, 1358 | "refId": "G" 1359 | }, 1360 | { 1361 | "datasource": { 1362 | "type": "prometheus", 1363 | "uid": "PBFA97CFB590B2093" 1364 | }, 1365 | "editorMode": "code", 1366 | "exemplar": false, 1367 | "expr": "transmission_torrent_status", 1368 | "format": "table", 1369 | "hide": false, 1370 | "instant": true, 1371 | "legendFormat": "Status", 1372 | "range": false, 1373 | "refId": "H" 1374 | }, 1375 | { 1376 | "datasource": { 1377 | "type": "prometheus", 1378 | "uid": "PBFA97CFB590B2093" 1379 | }, 1380 | "editorMode": "code", 1381 | "exemplar": false, 1382 | "expr": "transmission_torrent_upload_bytes", 1383 | "format": "table", 1384 | "hide": false, 1385 | "instant": true, 1386 | "legendFormat": "Upload Speed", 1387 | "range": false, 1388 | "refId": "I" 1389 | }, 1390 | { 1391 | "datasource": { 1392 | "type": "prometheus", 1393 | "uid": "PBFA97CFB590B2093" 1394 | }, 1395 | "editorMode": "code", 1396 | "exemplar": false, 1397 | "expr": "transmission_torrent_download_bytes", 1398 | "format": "table", 1399 | "hide": false, 1400 | "instant": true, 1401 | "legendFormat": "Download Speed", 1402 | "range": false, 1403 | "refId": "J" 1404 | } 1405 | ], 1406 | "title": "Torrents", 1407 | "transformations": [ 1408 | { 1409 | "id": "concatenate", 1410 | "options": { 1411 | "frameNameLabel": "", 1412 | "frameNameMode": "drop" 1413 | } 1414 | }, 1415 | { 1416 | "id": "filterFieldsByName", 1417 | "options": { 1418 | "byVariable": false, 1419 | "include": { 1420 | "names": [ 1421 | "name 1", 1422 | "Value #A", 1423 | "Value #B", 1424 | "Value #C", 1425 | "Value #D", 1426 | "Value #E", 1427 | "Value #F", 1428 | "Value #G", 1429 | "Value #H", 1430 | "Value #J", 1431 | "Value #I" 1432 | ] 1433 | } 1434 | } 1435 | }, 1436 | { 1437 | "id": "organize", 1438 | "options": { 1439 | "excludeByName": {}, 1440 | "includeByName": {}, 1441 | "indexByName": { 1442 | "Value #A": 8, 1443 | "Value #B": 1, 1444 | "Value #C": 4, 1445 | "Value #D": 7, 1446 | "Value #E": 6, 1447 | "Value #F": 9, 1448 | "Value #G": 5, 1449 | "Value #H": 10, 1450 | "Value #I": 3, 1451 | "Value #J": 2, 1452 | "name 1": 0 1453 | }, 1454 | "renameByName": { 1455 | "Value #A": "Date Added", 1456 | "Value #B": "Progress", 1457 | "Value #C": "Bytes Downloaded", 1458 | "Value #D": "Leechers", 1459 | "Value #E": "Seeders", 1460 | "Value #F": "Ratio", 1461 | "Value #G": "Bytes Uploaded", 1462 | "Value #H": "Status", 1463 | "Value #I": "Upload Speed", 1464 | "Value #J": "Download Speed", 1465 | "name 1": "Name" 1466 | } 1467 | } 1468 | } 1469 | ], 1470 | "type": "table" 1471 | }, 1472 | { 1473 | "datasource": { 1474 | "type": "prometheus", 1475 | "uid": "PBFA97CFB590B2093" 1476 | }, 1477 | "fieldConfig": { 1478 | "defaults": { 1479 | "decimals": 2, 1480 | "mappings": [ 1481 | { 1482 | "from": "", 1483 | "id": 2, 1484 | "operator": "", 1485 | "text": "N/A", 1486 | "to": "", 1487 | "type": 1, 1488 | "value": "null" 1489 | } 1490 | ], 1491 | "max": 1, 1492 | "min": 0, 1493 | "thresholds": { 1494 | "mode": "absolute", 1495 | "steps": [ 1496 | { 1497 | "color": "dark-red", 1498 | "value": null 1499 | }, 1500 | { 1501 | "color": "dark-orange", 1502 | "value": 0.25 1503 | }, 1504 | { 1505 | "color": "#EAB839", 1506 | "value": 0.5 1507 | }, 1508 | { 1509 | "color": "semi-dark-green", 1510 | "value": 0.75 1511 | }, 1512 | { 1513 | "color": "rgb(206, 206, 206)", 1514 | "value": 1 1515 | } 1516 | ] 1517 | }, 1518 | "unit": "percentunit" 1519 | }, 1520 | "overrides": [] 1521 | }, 1522 | "gridPos": { 1523 | "h": 10, 1524 | "w": 24, 1525 | "x": 0, 1526 | "y": 30 1527 | }, 1528 | "id": 22, 1529 | "options": { 1530 | "displayMode": "lcd", 1531 | "maxVizHeight": 300, 1532 | "minVizHeight": 16, 1533 | "minVizWidth": 8, 1534 | "namePlacement": "auto", 1535 | "orientation": "horizontal", 1536 | "reduceOptions": { 1537 | "calcs": [ 1538 | "lastNotNull" 1539 | ], 1540 | "fields": "", 1541 | "values": false 1542 | }, 1543 | "showUnfilled": true, 1544 | "sizing": "auto", 1545 | "text": {}, 1546 | "valueMode": "color" 1547 | }, 1548 | "pluginVersion": "10.4.3", 1549 | "targets": [ 1550 | { 1551 | "datasource": { 1552 | "type": "prometheus", 1553 | "uid": "PBFA97CFB590B2093" 1554 | }, 1555 | "editorMode": "code", 1556 | "expr": "transmission_torrent_done < 1", 1557 | "instant": true, 1558 | "legendFormat": "{{name}}", 1559 | "refId": "A" 1560 | } 1561 | ], 1562 | "title": "Incomplete Torrents", 1563 | "type": "bargauge" 1564 | } 1565 | ], 1566 | "schemaVersion": 39, 1567 | "tags": [], 1568 | "templating": { 1569 | "list": [] 1570 | }, 1571 | "time": { 1572 | "from": "now-1h", 1573 | "to": "now" 1574 | }, 1575 | "timepicker": { 1576 | "refresh_intervals": [ 1577 | "5s", 1578 | "10s", 1579 | "30s", 1580 | "1m", 1581 | "5m", 1582 | "15m", 1583 | "30m", 1584 | "1h", 1585 | "2h", 1586 | "1d" 1587 | ] 1588 | }, 1589 | "timezone": "", 1590 | "title": "Transmission", 1591 | "uid": "OEyH9tQZk", 1592 | "version": 52, 1593 | "weekStart": "" 1594 | } 1595 | -------------------------------------------------------------------------------- /dashboards/jsonnetfile.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": [ 3 | { 4 | "name": "grafana-builder", 5 | "source": { 6 | "git": { 7 | "remote": "https://github.com/grafana/jsonnet-libs", 8 | "subdir": "grafana-builder" 9 | } 10 | }, 11 | "version": "master" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /dashboards/transmission.json: -------------------------------------------------------------------------------- 1 | { 2 | "annotations": { 3 | "list": [ ] 4 | }, 5 | "editable": true, 6 | "gnetId": null, 7 | "graphTooltip": 0, 8 | "hideControls": false, 9 | "links": [ ], 10 | "refresh": "10s", 11 | "rows": [ 12 | { 13 | "collapse": false, 14 | "height": "250px", 15 | "panels": [ 16 | { 17 | "aliasColors": { }, 18 | "bars": false, 19 | "dashLength": 10, 20 | "dashes": false, 21 | "datasource": "$datasource", 22 | "fill": 10, 23 | "id": 0, 24 | "legend": { 25 | "avg": false, 26 | "current": false, 27 | "max": false, 28 | "min": false, 29 | "show": true, 30 | "total": false, 31 | "values": false 32 | }, 33 | "lines": true, 34 | "linewidth": 0, 35 | "links": [ ], 36 | "nullPointMode": "null as zero", 37 | "percentage": false, 38 | "pointradius": 5, 39 | "points": false, 40 | "renderer": "flot", 41 | "seriesOverrides": [ ], 42 | "spaceLength": 10, 43 | "span": 12, 44 | "stack": true, 45 | "steppedLine": false, 46 | "targets": [ 47 | { 48 | "expr": "max(transmission_torrent_download_bytes{name=~\"$torrent\"}) by (name) > 0", 49 | "format": "time_series", 50 | "intervalFactor": 2, 51 | "legendFormat": "{{name}}", 52 | "legendLink": null, 53 | "step": 10 54 | } 55 | ], 56 | "thresholds": [ ], 57 | "timeFrom": null, 58 | "timeShift": null, 59 | "title": "Downloads (bytes/second)", 60 | "tooltip": { 61 | "shared": true, 62 | "sort": 0, 63 | "value_type": "individual" 64 | }, 65 | "type": "graph", 66 | "xaxis": { 67 | "buckets": null, 68 | "mode": "time", 69 | "name": null, 70 | "show": true, 71 | "values": [ ] 72 | }, 73 | "yaxes": [ 74 | { 75 | "format": "bytes", 76 | "label": null, 77 | "logBase": 1, 78 | "max": null, 79 | "min": 0, 80 | "show": true 81 | }, 82 | { 83 | "format": "short", 84 | "label": null, 85 | "logBase": 1, 86 | "max": null, 87 | "min": null, 88 | "show": false 89 | } 90 | ] 91 | } 92 | ], 93 | "repeat": null, 94 | "repeatIteration": null, 95 | "repeatRowId": null, 96 | "showTitle": true, 97 | "title": "Downloads", 98 | "titleSize": "h6" 99 | }, 100 | { 101 | "collapse": false, 102 | "height": "250px", 103 | "panels": [ 104 | { 105 | "aliasColors": { }, 106 | "bars": false, 107 | "dashLength": 10, 108 | "dashes": false, 109 | "datasource": "$datasource", 110 | "fill": 10, 111 | "id": 1, 112 | "legend": { 113 | "avg": false, 114 | "current": false, 115 | "max": false, 116 | "min": false, 117 | "show": true, 118 | "total": false, 119 | "values": false 120 | }, 121 | "lines": true, 122 | "linewidth": 0, 123 | "links": [ ], 124 | "nullPointMode": "null as zero", 125 | "percentage": false, 126 | "pointradius": 5, 127 | "points": false, 128 | "renderer": "flot", 129 | "seriesOverrides": [ ], 130 | "spaceLength": 10, 131 | "span": 12, 132 | "stack": true, 133 | "steppedLine": false, 134 | "targets": [ 135 | { 136 | "expr": "max(transmission_torrent_upload_bytes{name=~\"$torrent\"}) by (name) > 0", 137 | "format": "time_series", 138 | "intervalFactor": 2, 139 | "legendFormat": "{{name}}", 140 | "legendLink": null, 141 | "step": 10 142 | } 143 | ], 144 | "thresholds": [ ], 145 | "timeFrom": null, 146 | "timeShift": null, 147 | "title": "Uploads (bytes/second)", 148 | "tooltip": { 149 | "shared": true, 150 | "sort": 0, 151 | "value_type": "individual" 152 | }, 153 | "type": "graph", 154 | "xaxis": { 155 | "buckets": null, 156 | "mode": "time", 157 | "name": null, 158 | "show": true, 159 | "values": [ ] 160 | }, 161 | "yaxes": [ 162 | { 163 | "format": "bytes", 164 | "label": null, 165 | "logBase": 1, 166 | "max": null, 167 | "min": 0, 168 | "show": true 169 | }, 170 | { 171 | "format": "short", 172 | "label": null, 173 | "logBase": 1, 174 | "max": null, 175 | "min": null, 176 | "show": false 177 | } 178 | ] 179 | } 180 | ], 181 | "repeat": null, 182 | "repeatIteration": null, 183 | "repeatRowId": null, 184 | "showTitle": true, 185 | "title": "Upload", 186 | "titleSize": "h6" 187 | }, 188 | { 189 | "collapse": false, 190 | "height": "250px", 191 | "panels": [ 192 | { 193 | "aliasColors": { }, 194 | "bars": false, 195 | "dashLength": 10, 196 | "dashes": false, 197 | "datasource": "$datasource", 198 | "fill": 1, 199 | "id": 2, 200 | "legend": { 201 | "avg": false, 202 | "current": false, 203 | "max": false, 204 | "min": false, 205 | "show": true, 206 | "total": false, 207 | "values": false 208 | }, 209 | "lines": true, 210 | "linewidth": 1, 211 | "links": [ ], 212 | "nullPointMode": "null as zero", 213 | "percentage": false, 214 | "pointradius": 5, 215 | "points": false, 216 | "renderer": "flot", 217 | "seriesOverrides": [ ], 218 | "spaceLength": 10, 219 | "span": 12, 220 | "stack": false, 221 | "steppedLine": false, 222 | "styles": [ 223 | { 224 | "alias": "Time", 225 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 226 | "pattern": "Time", 227 | "type": "hidden" 228 | }, 229 | { 230 | "alias": "Progress", 231 | "colorMode": null, 232 | "colors": [ ], 233 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 234 | "decimals": 2, 235 | "link": false, 236 | "linkTooltip": "Drill down", 237 | "linkUrl": "", 238 | "pattern": "Value #A", 239 | "thresholds": [ ], 240 | "type": "number", 241 | "unit": "percentunit" 242 | }, 243 | { 244 | "alias": "Ratio", 245 | "colorMode": null, 246 | "colors": [ ], 247 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 248 | "decimals": 2, 249 | "link": false, 250 | "linkTooltip": "Drill down", 251 | "linkUrl": "", 252 | "pattern": "Value #B", 253 | "thresholds": [ ], 254 | "type": "number", 255 | "unit": "short" 256 | }, 257 | { 258 | "alias": "Leechers", 259 | "colorMode": null, 260 | "colors": [ ], 261 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 262 | "decimals": 2, 263 | "link": false, 264 | "linkTooltip": "Drill down", 265 | "linkUrl": "", 266 | "pattern": "Value #C", 267 | "thresholds": [ ], 268 | "type": "number", 269 | "unit": "short" 270 | }, 271 | { 272 | "alias": "Seeders", 273 | "colorMode": null, 274 | "colors": [ ], 275 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 276 | "decimals": 2, 277 | "link": false, 278 | "linkTooltip": "Drill down", 279 | "linkUrl": "", 280 | "pattern": "Value #D", 281 | "thresholds": [ ], 282 | "type": "number", 283 | "unit": "short" 284 | }, 285 | { 286 | "alias": "", 287 | "colorMode": null, 288 | "colors": [ ], 289 | "dateFormat": "YYYY-MM-DD HH:mm:ss", 290 | "decimals": 2, 291 | "pattern": "/.*/", 292 | "thresholds": [ ], 293 | "type": "string", 294 | "unit": "short" 295 | } 296 | ], 297 | "targets": [ 298 | { 299 | "expr": "max(transmission_torrent_done{name=~\"$torrent\"}) by (name)", 300 | "format": "table", 301 | "instant": true, 302 | "intervalFactor": 2, 303 | "legendFormat": "", 304 | "refId": "A", 305 | "step": 10 306 | }, 307 | { 308 | "expr": "max(transmission_torrent_ratio{name=~\"$torrent\"}) by (name)", 309 | "format": "table", 310 | "instant": true, 311 | "intervalFactor": 2, 312 | "legendFormat": "", 313 | "refId": "B", 314 | "step": 10 315 | }, 316 | { 317 | "expr": "max(transmission_torrent_leechers{name=~\"$torrent\"}) by (name)", 318 | "format": "table", 319 | "instant": true, 320 | "intervalFactor": 2, 321 | "legendFormat": "", 322 | "refId": "C", 323 | "step": 10 324 | }, 325 | { 326 | "expr": "max(transmission_torrent_seeders{name=~\"$torrent\"}) by (name)", 327 | "format": "table", 328 | "instant": true, 329 | "intervalFactor": 2, 330 | "legendFormat": "", 331 | "refId": "D", 332 | "step": 10 333 | } 334 | ], 335 | "thresholds": [ ], 336 | "timeFrom": null, 337 | "timeShift": null, 338 | "title": "Overview", 339 | "tooltip": { 340 | "shared": true, 341 | "sort": 0, 342 | "value_type": "individual" 343 | }, 344 | "transform": "table", 345 | "type": "table", 346 | "xaxis": { 347 | "buckets": null, 348 | "mode": "time", 349 | "name": null, 350 | "show": true, 351 | "values": [ ] 352 | }, 353 | "yaxes": [ 354 | { 355 | "format": "short", 356 | "label": null, 357 | "logBase": 1, 358 | "max": null, 359 | "min": 0, 360 | "show": true 361 | }, 362 | { 363 | "format": "short", 364 | "label": null, 365 | "logBase": 1, 366 | "max": null, 367 | "min": null, 368 | "show": false 369 | } 370 | ] 371 | } 372 | ], 373 | "repeat": null, 374 | "repeatIteration": null, 375 | "repeatRowId": null, 376 | "showTitle": true, 377 | "title": "Torrents", 378 | "titleSize": "h6" 379 | } 380 | ], 381 | "schemaVersion": 14, 382 | "style": "dark", 383 | "tags": [ ], 384 | "templating": { 385 | "list": [ 386 | { 387 | "current": { 388 | "text": "Prometheus", 389 | "value": "Prometheus" 390 | }, 391 | "hide": 0, 392 | "label": null, 393 | "name": "datasource", 394 | "options": [ ], 395 | "query": "prometheus", 396 | "refresh": 1, 397 | "regex": "", 398 | "type": "datasource" 399 | }, 400 | { 401 | "allValue": null, 402 | "current": { 403 | "selected": true, 404 | "text": "All", 405 | "value": "$__all" 406 | }, 407 | "datasource": "$datasource", 408 | "hide": 0, 409 | "includeAll": true, 410 | "label": "torrent", 411 | "multi": true, 412 | "name": "torrent", 413 | "options": [ ], 414 | "query": "label_values(transmission_torrent_added, name)", 415 | "refresh": 1, 416 | "regex": "", 417 | "sort": 2, 418 | "tagValuesQuery": "", 419 | "tags": [ ], 420 | "tagsQuery": "", 421 | "type": "query", 422 | "useTags": false 423 | } 424 | ] 425 | }, 426 | "time": { 427 | "from": "now-1h", 428 | "to": "now" 429 | }, 430 | "timepicker": { 431 | "refresh_intervals": [ 432 | "5s", 433 | "10s", 434 | "30s", 435 | "1m", 436 | "5m", 437 | "15m", 438 | "30m", 439 | "1h", 440 | "2h", 441 | "1d" 442 | ], 443 | "time_options": [ 444 | "5m", 445 | "15m", 446 | "1h", 447 | "6h", 448 | "12h", 449 | "24h", 450 | "2d", 451 | "7d", 452 | "30d" 453 | ] 454 | }, 455 | "timezone": "utc", 456 | "title": "Transmission", 457 | "uid": "", 458 | "version": 0 459 | } 460 | -------------------------------------------------------------------------------- /dashboards/transmission.jsonnet: -------------------------------------------------------------------------------- 1 | local g = import 'grafana-builder/grafana.libsonnet'; 2 | 3 | { 4 | grafanaDashboards+:: { 5 | 'transmission.json': 6 | g.dashboard( 7 | 'Transmission', 8 | ) 9 | .addMultiTemplate('torrent', 'transmission_torrent_added', 'name') 10 | .addRow( 11 | g.row('Downloads') 12 | .addPanel( 13 | g.panel('Downloads (bytes/second)') + 14 | g.queryPanel('max(transmission_torrent_download_bytes{name=~"$torrent"}) by (name) > 0', '{{name}}') + 15 | g.stack + 16 | { yaxes: g.yaxes('bytes') }, 17 | ) 18 | ) 19 | .addRow( 20 | g.row('Upload') 21 | .addPanel( 22 | g.panel('Uploads (bytes/second)') + 23 | g.queryPanel('max(transmission_torrent_upload_bytes{name=~"$torrent"}) by (name) > 0', '{{name}}') + 24 | g.stack + 25 | { yaxes: g.yaxes('bytes') }, 26 | ) 27 | ) 28 | .addRow( 29 | g.row('Torrents') 30 | .addPanel( 31 | g.panel('Overview') + 32 | g.tablePanel([ 33 | 'max(transmission_torrent_done{name=~"$torrent"}) by (name)', 34 | 'max(transmission_torrent_ratio{name=~"$torrent"}) by (name)', 35 | 'max(transmission_torrent_leechers{name=~"$torrent"}) by (name)', 36 | 'max(transmission_torrent_seeders{name=~"$torrent"}) by (name)', 37 | ], { 38 | 'Value #A': { alias: 'Progress', unit: 'percentunit' }, 39 | 'Value #B': { alias: 'Ratio' }, 40 | 'Value #C': { alias: 'Leechers' }, 41 | 'Value #D': { alias: 'Seeders' }, 42 | }) 43 | ) 44 | ), 45 | }, 46 | } 47 | -------------------------------------------------------------------------------- /dashboards/transmission2024.json: -------------------------------------------------------------------------------- 1 | { 2 | "__inputs": [ 3 | { 4 | "name": "DS_PROMETHEUS", 5 | "label": "Prometheus", 6 | "description": "", 7 | "type": "datasource", 8 | "pluginId": "prometheus", 9 | "pluginName": "Prometheus" 10 | } 11 | ], 12 | "__elements": {}, 13 | "__requires": [ 14 | { 15 | "type": "grafana", 16 | "id": "grafana", 17 | "name": "Grafana", 18 | "version": "11.2.0" 19 | }, 20 | { 21 | "type": "datasource", 22 | "id": "prometheus", 23 | "name": "Prometheus", 24 | "version": "1.0.0" 25 | }, 26 | { 27 | "type": "panel", 28 | "id": "stat", 29 | "name": "Stat", 30 | "version": "" 31 | }, 32 | { 33 | "type": "panel", 34 | "id": "table", 35 | "name": "Table", 36 | "version": "" 37 | }, 38 | { 39 | "type": "panel", 40 | "id": "timeseries", 41 | "name": "Time series", 42 | "version": "" 43 | } 44 | ], 45 | "annotations": { 46 | "list": [ 47 | { 48 | "builtIn": 1, 49 | "datasource": { 50 | "type": "datasource", 51 | "uid": "grafana" 52 | }, 53 | "enable": true, 54 | "hide": true, 55 | "iconColor": "rgba(0, 211, 255, 1)", 56 | "name": "Annotations & Alerts", 57 | "type": "dashboard" 58 | } 59 | ] 60 | }, 61 | "description": "Transmission statistics prometheus dashboard ", 62 | "editable": true, 63 | "fiscalYearStartMonth": 0, 64 | "gnetId": 8259, 65 | "graphTooltip": 0, 66 | "id": null, 67 | "links": [], 68 | "panels": [ 69 | { 70 | "datasource": { 71 | "type": "prometheus", 72 | "uid": "${DS_PROMETHEUS}" 73 | }, 74 | "fieldConfig": { 75 | "defaults": { 76 | "mappings": [ 77 | { 78 | "options": { 79 | "match": "null", 80 | "result": { 81 | "text": "N/A" 82 | } 83 | }, 84 | "type": "special" 85 | } 86 | ], 87 | "thresholds": { 88 | "mode": "absolute", 89 | "steps": [ 90 | { 91 | "color": "green", 92 | "value": null 93 | }, 94 | { 95 | "color": "red", 96 | "value": 80 97 | } 98 | ] 99 | }, 100 | "unit": "none" 101 | }, 102 | "overrides": [] 103 | }, 104 | "gridPos": { 105 | "h": 4, 106 | "w": 5, 107 | "x": 0, 108 | "y": 0 109 | }, 110 | "id": 13, 111 | "maxDataPoints": 100, 112 | "options": { 113 | "colorMode": "none", 114 | "graphMode": "none", 115 | "justifyMode": "auto", 116 | "orientation": "horizontal", 117 | "percentChangeColorMode": "standard", 118 | "reduceOptions": { 119 | "calcs": [ 120 | "mean" 121 | ], 122 | "fields": "", 123 | "values": false 124 | }, 125 | "showPercentChange": false, 126 | "textMode": "auto", 127 | "wideLayout": true 128 | }, 129 | "pluginVersion": "11.2.0", 130 | "targets": [ 131 | { 132 | "datasource": { 133 | "type": "prometheus", 134 | "uid": "${DS_PROMETHEUS}" 135 | }, 136 | "editorMode": "code", 137 | "exemplar": false, 138 | "expr": "count(transmission_torrent_done)", 139 | "format": "time_series", 140 | "instant": true, 141 | "intervalFactor": 1, 142 | "range": false, 143 | "refId": "A" 144 | } 145 | ], 146 | "title": "Number of torrents", 147 | "type": "stat" 148 | }, 149 | { 150 | "datasource": { 151 | "type": "prometheus", 152 | "uid": "${DS_PROMETHEUS}" 153 | }, 154 | "fieldConfig": { 155 | "defaults": { 156 | "color": { 157 | "fixedColor": "blue", 158 | "mode": "shades" 159 | }, 160 | "mappings": [ 161 | { 162 | "options": { 163 | "match": "null", 164 | "result": { 165 | "text": "N/A" 166 | } 167 | }, 168 | "type": "special" 169 | } 170 | ], 171 | "thresholds": { 172 | "mode": "absolute", 173 | "steps": [ 174 | { 175 | "color": "green", 176 | "value": null 177 | }, 178 | { 179 | "color": "red", 180 | "value": 80 181 | } 182 | ] 183 | }, 184 | "unit": "bytes" 185 | }, 186 | "overrides": [] 187 | }, 188 | "gridPos": { 189 | "h": 4, 190 | "w": 2, 191 | "x": 5, 192 | "y": 0 193 | }, 194 | "id": 6, 195 | "maxDataPoints": 100, 196 | "options": { 197 | "colorMode": "value", 198 | "graphMode": "none", 199 | "justifyMode": "auto", 200 | "orientation": "horizontal", 201 | "percentChangeColorMode": "standard", 202 | "reduceOptions": { 203 | "calcs": [ 204 | "mean" 205 | ], 206 | "fields": "", 207 | "values": false 208 | }, 209 | "showPercentChange": false, 210 | "textMode": "auto", 211 | "wideLayout": true 212 | }, 213 | "pluginVersion": "11.2.0", 214 | "targets": [ 215 | { 216 | "datasource": { 217 | "type": "prometheus", 218 | "uid": "${DS_PROMETHEUS}" 219 | }, 220 | "editorMode": "code", 221 | "expr": "transmission_session_stats_uploaded_bytes{type=\"current\"}", 222 | "format": "time_series", 223 | "intervalFactor": 1, 224 | "range": true, 225 | "refId": "A" 226 | } 227 | ], 228 | "title": "Current upload", 229 | "type": "stat" 230 | }, 231 | { 232 | "datasource": { 233 | "type": "prometheus", 234 | "uid": "${DS_PROMETHEUS}" 235 | }, 236 | "fieldConfig": { 237 | "defaults": { 238 | "color": { 239 | "fixedColor": "blue", 240 | "mode": "fixed" 241 | }, 242 | "mappings": [ 243 | { 244 | "options": { 245 | "match": "null", 246 | "result": { 247 | "text": "N/A" 248 | } 249 | }, 250 | "type": "special" 251 | } 252 | ], 253 | "thresholds": { 254 | "mode": "absolute", 255 | "steps": [ 256 | { 257 | "color": "green", 258 | "value": null 259 | }, 260 | { 261 | "color": "red", 262 | "value": 80 263 | } 264 | ] 265 | }, 266 | "unit": "bytes" 267 | }, 268 | "overrides": [] 269 | }, 270 | "gridPos": { 271 | "h": 4, 272 | "w": 3, 273 | "x": 7, 274 | "y": 0 275 | }, 276 | "id": 22, 277 | "maxDataPoints": 100, 278 | "options": { 279 | "colorMode": "value", 280 | "graphMode": "area", 281 | "justifyMode": "auto", 282 | "orientation": "horizontal", 283 | "percentChangeColorMode": "standard", 284 | "reduceOptions": { 285 | "calcs": [ 286 | "lastNotNull" 287 | ], 288 | "fields": "", 289 | "values": false 290 | }, 291 | "showPercentChange": false, 292 | "textMode": "auto", 293 | "wideLayout": true 294 | }, 295 | "pluginVersion": "11.2.0", 296 | "targets": [ 297 | { 298 | "datasource": { 299 | "type": "prometheus", 300 | "uid": "${DS_PROMETHEUS}" 301 | }, 302 | "editorMode": "code", 303 | "expr": "sum (increase(transmission_session_stats_uploaded_bytes{type=\"cumulative\"}[1d]))", 304 | "format": "time_series", 305 | "intervalFactor": 1, 306 | "legendFormat": "__auto", 307 | "range": true, 308 | "refId": "A" 309 | } 310 | ], 311 | "title": "1 day upload", 312 | "type": "stat" 313 | }, 314 | { 315 | "datasource": { 316 | "type": "prometheus", 317 | "uid": "${DS_PROMETHEUS}" 318 | }, 319 | "fieldConfig": { 320 | "defaults": { 321 | "color": { 322 | "fixedColor": "blue", 323 | "mode": "fixed" 324 | }, 325 | "mappings": [ 326 | { 327 | "options": { 328 | "match": "null", 329 | "result": { 330 | "text": "N/A" 331 | } 332 | }, 333 | "type": "special" 334 | } 335 | ], 336 | "thresholds": { 337 | "mode": "absolute", 338 | "steps": [ 339 | { 340 | "color": "green", 341 | "value": null 342 | }, 343 | { 344 | "color": "red", 345 | "value": 80 346 | } 347 | ] 348 | }, 349 | "unit": "bytes" 350 | }, 351 | "overrides": [] 352 | }, 353 | "gridPos": { 354 | "h": 4, 355 | "w": 3, 356 | "x": 10, 357 | "y": 0 358 | }, 359 | "id": 23, 360 | "maxDataPoints": 100, 361 | "options": { 362 | "colorMode": "value", 363 | "graphMode": "area", 364 | "justifyMode": "auto", 365 | "orientation": "horizontal", 366 | "percentChangeColorMode": "standard", 367 | "reduceOptions": { 368 | "calcs": [ 369 | "lastNotNull" 370 | ], 371 | "fields": "", 372 | "values": false 373 | }, 374 | "showPercentChange": false, 375 | "textMode": "auto", 376 | "wideLayout": true 377 | }, 378 | "pluginVersion": "11.2.0", 379 | "targets": [ 380 | { 381 | "datasource": { 382 | "type": "prometheus", 383 | "uid": "${DS_PROMETHEUS}" 384 | }, 385 | "editorMode": "code", 386 | "expr": "sum (increase(transmission_session_stats_uploaded_bytes{type=\"cumulative\"}[7d]))", 387 | "format": "time_series", 388 | "intervalFactor": 1, 389 | "legendFormat": "__auto", 390 | "range": true, 391 | "refId": "A" 392 | } 393 | ], 394 | "title": "7 days upload", 395 | "type": "stat" 396 | }, 397 | { 398 | "datasource": { 399 | "type": "prometheus", 400 | "uid": "${DS_PROMETHEUS}" 401 | }, 402 | "fieldConfig": { 403 | "defaults": { 404 | "color": { 405 | "fixedColor": "blue", 406 | "mode": "fixed" 407 | }, 408 | "mappings": [ 409 | { 410 | "options": { 411 | "match": "null", 412 | "result": { 413 | "text": "N/A" 414 | } 415 | }, 416 | "type": "special" 417 | } 418 | ], 419 | "thresholds": { 420 | "mode": "absolute", 421 | "steps": [ 422 | { 423 | "color": "green", 424 | "value": null 425 | }, 426 | { 427 | "color": "red", 428 | "value": 80 429 | } 430 | ] 431 | }, 432 | "unit": "bytes" 433 | }, 434 | "overrides": [] 435 | }, 436 | "gridPos": { 437 | "h": 4, 438 | "w": 5, 439 | "x": 13, 440 | "y": 0 441 | }, 442 | "id": 5, 443 | "maxDataPoints": 100, 444 | "options": { 445 | "colorMode": "value", 446 | "graphMode": "area", 447 | "justifyMode": "auto", 448 | "orientation": "horizontal", 449 | "percentChangeColorMode": "standard", 450 | "reduceOptions": { 451 | "calcs": [ 452 | "lastNotNull" 453 | ], 454 | "fields": "", 455 | "values": false 456 | }, 457 | "showPercentChange": false, 458 | "textMode": "auto", 459 | "wideLayout": true 460 | }, 461 | "pluginVersion": "11.2.0", 462 | "targets": [ 463 | { 464 | "datasource": { 465 | "type": "prometheus", 466 | "uid": "${DS_PROMETHEUS}" 467 | }, 468 | "editorMode": "code", 469 | "expr": "transmission_session_stats_uploaded_bytes{type=\"cumulative\"}", 470 | "format": "time_series", 471 | "intervalFactor": 1, 472 | "range": true, 473 | "refId": "A" 474 | } 475 | ], 476 | "title": "Total upload", 477 | "type": "stat" 478 | }, 479 | { 480 | "datasource": { 481 | "type": "prometheus", 482 | "uid": "${DS_PROMETHEUS}" 483 | }, 484 | "description": "", 485 | "fieldConfig": { 486 | "defaults": { 487 | "color": { 488 | "fixedColor": "blue", 489 | "mode": "fixed" 490 | }, 491 | "mappings": [], 492 | "thresholds": { 493 | "mode": "absolute", 494 | "steps": [ 495 | { 496 | "color": "green", 497 | "value": null 498 | }, 499 | { 500 | "color": "red", 501 | "value": 80 502 | } 503 | ] 504 | }, 505 | "unit": "binBps" 506 | }, 507 | "overrides": [] 508 | }, 509 | "gridPos": { 510 | "h": 4, 511 | "w": 6, 512 | "x": 18, 513 | "y": 0 514 | }, 515 | "id": 18, 516 | "options": { 517 | "colorMode": "value", 518 | "graphMode": "area", 519 | "justifyMode": "auto", 520 | "orientation": "auto", 521 | "percentChangeColorMode": "standard", 522 | "reduceOptions": { 523 | "calcs": [ 524 | "lastNotNull" 525 | ], 526 | "fields": "", 527 | "values": false 528 | }, 529 | "showPercentChange": false, 530 | "textMode": "auto", 531 | "wideLayout": true 532 | }, 533 | "pluginVersion": "11.2.0", 534 | "targets": [ 535 | { 536 | "datasource": { 537 | "type": "prometheus", 538 | "uid": "${DS_PROMETHEUS}" 539 | }, 540 | "editorMode": "code", 541 | "exemplar": false, 542 | "expr": "sum (transmission_torrent_upload_bytes{name=~\"$Torrent\"})", 543 | "instant": false, 544 | "legendFormat": "__auto", 545 | "range": true, 546 | "refId": "A" 547 | } 548 | ], 549 | "title": "Current upload speed", 550 | "type": "stat" 551 | }, 552 | { 553 | "datasource": { 554 | "type": "prometheus", 555 | "uid": "${DS_PROMETHEUS}" 556 | }, 557 | "fieldConfig": { 558 | "defaults": { 559 | "color": { 560 | "fixedColor": "rgb(31, 120, 193)", 561 | "mode": "thresholds" 562 | }, 563 | "mappings": [ 564 | { 565 | "options": { 566 | "match": "null", 567 | "result": { 568 | "text": "N/A" 569 | } 570 | }, 571 | "type": "special" 572 | } 573 | ], 574 | "thresholds": { 575 | "mode": "absolute", 576 | "steps": [ 577 | { 578 | "color": "red", 579 | "value": null 580 | }, 581 | { 582 | "color": "green", 583 | "value": 1 584 | } 585 | ] 586 | }, 587 | "unit": "none" 588 | }, 589 | "overrides": [] 590 | }, 591 | "gridPos": { 592 | "h": 4, 593 | "w": 2, 594 | "x": 0, 595 | "y": 4 596 | }, 597 | "id": 10, 598 | "maxDataPoints": 100, 599 | "options": { 600 | "colorMode": "value", 601 | "graphMode": "area", 602 | "justifyMode": "auto", 603 | "orientation": "horizontal", 604 | "percentChangeColorMode": "standard", 605 | "reduceOptions": { 606 | "calcs": [ 607 | "lastNotNull" 608 | ], 609 | "fields": "", 610 | "values": false 611 | }, 612 | "showPercentChange": false, 613 | "textMode": "auto", 614 | "wideLayout": true 615 | }, 616 | "pluginVersion": "11.2.0", 617 | "targets": [ 618 | { 619 | "datasource": { 620 | "type": "prometheus", 621 | "uid": "${DS_PROMETHEUS}" 622 | }, 623 | "expr": "transmission_session_stats_uploaded_bytes{type=\"current\"}/transmission_session_stats_downloaded_bytes{type=\"current\"}", 624 | "format": "time_series", 625 | "intervalFactor": 1, 626 | "refId": "A" 627 | } 628 | ], 629 | "title": "Current ratio", 630 | "type": "stat" 631 | }, 632 | { 633 | "datasource": { 634 | "type": "prometheus", 635 | "uid": "${DS_PROMETHEUS}" 636 | }, 637 | "description": "", 638 | "fieldConfig": { 639 | "defaults": { 640 | "mappings": [ 641 | { 642 | "options": { 643 | "match": "null", 644 | "result": { 645 | "text": "N/A" 646 | } 647 | }, 648 | "type": "special" 649 | } 650 | ], 651 | "thresholds": { 652 | "mode": "absolute", 653 | "steps": [ 654 | { 655 | "color": "red", 656 | "value": null 657 | }, 658 | { 659 | "color": "green", 660 | "value": 1 661 | } 662 | ] 663 | }, 664 | "unit": "none" 665 | }, 666 | "overrides": [] 667 | }, 668 | "gridPos": { 669 | "h": 4, 670 | "w": 3, 671 | "x": 2, 672 | "y": 4 673 | }, 674 | "id": 11, 675 | "maxDataPoints": 100, 676 | "options": { 677 | "colorMode": "value", 678 | "graphMode": "area", 679 | "justifyMode": "auto", 680 | "orientation": "horizontal", 681 | "percentChangeColorMode": "standard", 682 | "reduceOptions": { 683 | "calcs": [ 684 | "lastNotNull" 685 | ], 686 | "fields": "", 687 | "values": false 688 | }, 689 | "showPercentChange": false, 690 | "textMode": "auto", 691 | "wideLayout": true 692 | }, 693 | "pluginVersion": "11.2.0", 694 | "targets": [ 695 | { 696 | "datasource": { 697 | "type": "prometheus", 698 | "uid": "${DS_PROMETHEUS}" 699 | }, 700 | "expr": "transmission_session_stats_uploaded_bytes{type=\"cumulative\"}/transmission_session_stats_downloaded_bytes{type=\"cumulative\"}", 701 | "format": "time_series", 702 | "intervalFactor": 1, 703 | "refId": "A" 704 | } 705 | ], 706 | "title": "Cumulative ratio", 707 | "type": "stat" 708 | }, 709 | { 710 | "datasource": { 711 | "type": "prometheus", 712 | "uid": "${DS_PROMETHEUS}" 713 | }, 714 | "fieldConfig": { 715 | "defaults": { 716 | "color": { 717 | "fixedColor": "purple", 718 | "mode": "fixed" 719 | }, 720 | "mappings": [ 721 | { 722 | "options": { 723 | "match": "null", 724 | "result": { 725 | "text": "N/A" 726 | } 727 | }, 728 | "type": "special" 729 | } 730 | ], 731 | "thresholds": { 732 | "mode": "absolute", 733 | "steps": [ 734 | { 735 | "color": "green", 736 | "value": null 737 | }, 738 | { 739 | "color": "red", 740 | "value": 80 741 | } 742 | ] 743 | }, 744 | "unit": "bytes" 745 | }, 746 | "overrides": [] 747 | }, 748 | "gridPos": { 749 | "h": 4, 750 | "w": 2, 751 | "x": 5, 752 | "y": 4 753 | }, 754 | "id": 3, 755 | "maxDataPoints": 100, 756 | "options": { 757 | "colorMode": "value", 758 | "graphMode": "none", 759 | "justifyMode": "auto", 760 | "orientation": "horizontal", 761 | "percentChangeColorMode": "standard", 762 | "reduceOptions": { 763 | "calcs": [ 764 | "mean" 765 | ], 766 | "fields": "", 767 | "values": false 768 | }, 769 | "showPercentChange": false, 770 | "textMode": "auto", 771 | "wideLayout": true 772 | }, 773 | "pluginVersion": "11.2.0", 774 | "targets": [ 775 | { 776 | "datasource": { 777 | "type": "prometheus", 778 | "uid": "${DS_PROMETHEUS}" 779 | }, 780 | "editorMode": "code", 781 | "expr": "transmission_session_stats_downloaded_bytes{type=\"current\"}", 782 | "format": "time_series", 783 | "intervalFactor": 1, 784 | "range": true, 785 | "refId": "A" 786 | } 787 | ], 788 | "title": "Current download", 789 | "type": "stat" 790 | }, 791 | { 792 | "datasource": { 793 | "type": "prometheus", 794 | "uid": "${DS_PROMETHEUS}" 795 | }, 796 | "fieldConfig": { 797 | "defaults": { 798 | "color": { 799 | "fixedColor": "purple", 800 | "mode": "fixed" 801 | }, 802 | "mappings": [ 803 | { 804 | "options": { 805 | "match": "null", 806 | "result": { 807 | "text": "N/A" 808 | } 809 | }, 810 | "type": "special" 811 | } 812 | ], 813 | "thresholds": { 814 | "mode": "absolute", 815 | "steps": [ 816 | { 817 | "color": "green", 818 | "value": null 819 | }, 820 | { 821 | "color": "red", 822 | "value": 80 823 | } 824 | ] 825 | }, 826 | "unit": "bytes" 827 | }, 828 | "overrides": [] 829 | }, 830 | "gridPos": { 831 | "h": 4, 832 | "w": 3, 833 | "x": 7, 834 | "y": 4 835 | }, 836 | "id": 24, 837 | "maxDataPoints": 100, 838 | "options": { 839 | "colorMode": "value", 840 | "graphMode": "area", 841 | "justifyMode": "auto", 842 | "orientation": "horizontal", 843 | "percentChangeColorMode": "standard", 844 | "reduceOptions": { 845 | "calcs": [ 846 | "lastNotNull" 847 | ], 848 | "fields": "", 849 | "values": false 850 | }, 851 | "showPercentChange": false, 852 | "textMode": "auto", 853 | "wideLayout": true 854 | }, 855 | "pluginVersion": "11.2.0", 856 | "targets": [ 857 | { 858 | "datasource": { 859 | "type": "prometheus", 860 | "uid": "${DS_PROMETHEUS}" 861 | }, 862 | "editorMode": "code", 863 | "expr": "sum (increase(transmission_session_stats_downloaded_bytes{type=\"cumulative\"}[1d]))", 864 | "format": "time_series", 865 | "intervalFactor": 1, 866 | "legendFormat": "__auto", 867 | "range": true, 868 | "refId": "A" 869 | } 870 | ], 871 | "title": "1 day download", 872 | "type": "stat" 873 | }, 874 | { 875 | "datasource": { 876 | "type": "prometheus", 877 | "uid": "${DS_PROMETHEUS}" 878 | }, 879 | "description": "", 880 | "fieldConfig": { 881 | "defaults": { 882 | "color": { 883 | "fixedColor": "purple", 884 | "mode": "fixed" 885 | }, 886 | "mappings": [ 887 | { 888 | "options": { 889 | "match": "null", 890 | "result": { 891 | "text": "N/A" 892 | } 893 | }, 894 | "type": "special" 895 | } 896 | ], 897 | "thresholds": { 898 | "mode": "absolute", 899 | "steps": [ 900 | { 901 | "color": "green", 902 | "value": null 903 | }, 904 | { 905 | "color": "red", 906 | "value": 80 907 | } 908 | ] 909 | }, 910 | "unit": "bytes" 911 | }, 912 | "overrides": [] 913 | }, 914 | "gridPos": { 915 | "h": 4, 916 | "w": 3, 917 | "x": 10, 918 | "y": 4 919 | }, 920 | "id": 25, 921 | "maxDataPoints": 100, 922 | "options": { 923 | "colorMode": "value", 924 | "graphMode": "area", 925 | "justifyMode": "auto", 926 | "orientation": "horizontal", 927 | "percentChangeColorMode": "standard", 928 | "reduceOptions": { 929 | "calcs": [ 930 | "lastNotNull" 931 | ], 932 | "fields": "", 933 | "values": false 934 | }, 935 | "showPercentChange": false, 936 | "textMode": "auto", 937 | "wideLayout": true 938 | }, 939 | "pluginVersion": "11.2.0", 940 | "targets": [ 941 | { 942 | "datasource": { 943 | "type": "prometheus", 944 | "uid": "${DS_PROMETHEUS}" 945 | }, 946 | "editorMode": "code", 947 | "expr": "sum (increase(transmission_session_stats_downloaded_bytes{type=\"cumulative\"}[7d]))", 948 | "format": "time_series", 949 | "intervalFactor": 1, 950 | "legendFormat": "__auto", 951 | "range": true, 952 | "refId": "A" 953 | } 954 | ], 955 | "title": "7 days download", 956 | "type": "stat" 957 | }, 958 | { 959 | "datasource": { 960 | "type": "prometheus", 961 | "uid": "${DS_PROMETHEUS}" 962 | }, 963 | "fieldConfig": { 964 | "defaults": { 965 | "color": { 966 | "fixedColor": "purple", 967 | "mode": "fixed" 968 | }, 969 | "mappings": [ 970 | { 971 | "options": { 972 | "match": "null", 973 | "result": { 974 | "text": "N/A" 975 | } 976 | }, 977 | "type": "special" 978 | } 979 | ], 980 | "thresholds": { 981 | "mode": "absolute", 982 | "steps": [ 983 | { 984 | "color": "green", 985 | "value": null 986 | }, 987 | { 988 | "color": "red", 989 | "value": 80 990 | } 991 | ] 992 | }, 993 | "unit": "bytes" 994 | }, 995 | "overrides": [] 996 | }, 997 | "gridPos": { 998 | "h": 4, 999 | "w": 5, 1000 | "x": 13, 1001 | "y": 4 1002 | }, 1003 | "id": 4, 1004 | "maxDataPoints": 100, 1005 | "options": { 1006 | "colorMode": "value", 1007 | "graphMode": "area", 1008 | "justifyMode": "auto", 1009 | "orientation": "horizontal", 1010 | "percentChangeColorMode": "standard", 1011 | "reduceOptions": { 1012 | "calcs": [ 1013 | "lastNotNull" 1014 | ], 1015 | "fields": "", 1016 | "values": false 1017 | }, 1018 | "showPercentChange": false, 1019 | "textMode": "auto", 1020 | "wideLayout": true 1021 | }, 1022 | "pluginVersion": "11.2.0", 1023 | "targets": [ 1024 | { 1025 | "datasource": { 1026 | "type": "prometheus", 1027 | "uid": "${DS_PROMETHEUS}" 1028 | }, 1029 | "expr": "transmission_session_stats_downloaded_bytes{type=\"cumulative\"}", 1030 | "format": "time_series", 1031 | "intervalFactor": 1, 1032 | "refId": "A" 1033 | } 1034 | ], 1035 | "title": "Total download", 1036 | "type": "stat" 1037 | }, 1038 | { 1039 | "datasource": { 1040 | "type": "prometheus", 1041 | "uid": "${DS_PROMETHEUS}" 1042 | }, 1043 | "description": "", 1044 | "fieldConfig": { 1045 | "defaults": { 1046 | "color": { 1047 | "fixedColor": "purple", 1048 | "mode": "fixed" 1049 | }, 1050 | "mappings": [], 1051 | "thresholds": { 1052 | "mode": "absolute", 1053 | "steps": [ 1054 | { 1055 | "color": "green", 1056 | "value": null 1057 | }, 1058 | { 1059 | "color": "red", 1060 | "value": 80 1061 | } 1062 | ] 1063 | }, 1064 | "unit": "binBps" 1065 | }, 1066 | "overrides": [] 1067 | }, 1068 | "gridPos": { 1069 | "h": 4, 1070 | "w": 6, 1071 | "x": 18, 1072 | "y": 4 1073 | }, 1074 | "id": 21, 1075 | "options": { 1076 | "colorMode": "value", 1077 | "graphMode": "area", 1078 | "justifyMode": "auto", 1079 | "orientation": "auto", 1080 | "percentChangeColorMode": "standard", 1081 | "reduceOptions": { 1082 | "calcs": [ 1083 | "lastNotNull" 1084 | ], 1085 | "fields": "", 1086 | "values": false 1087 | }, 1088 | "showPercentChange": false, 1089 | "textMode": "auto", 1090 | "wideLayout": true 1091 | }, 1092 | "pluginVersion": "11.2.0", 1093 | "targets": [ 1094 | { 1095 | "datasource": { 1096 | "type": "prometheus", 1097 | "uid": "${DS_PROMETHEUS}" 1098 | }, 1099 | "editorMode": "code", 1100 | "exemplar": false, 1101 | "expr": "sum (transmission_torrent_download_bytes{name=~\"$Torrent\"})", 1102 | "instant": false, 1103 | "legendFormat": "__auto", 1104 | "range": true, 1105 | "refId": "A" 1106 | } 1107 | ], 1108 | "title": "Current download speed", 1109 | "type": "stat" 1110 | }, 1111 | { 1112 | "datasource": { 1113 | "type": "prometheus", 1114 | "uid": "${DS_PROMETHEUS}" 1115 | }, 1116 | "fieldConfig": { 1117 | "defaults": { 1118 | "color": { 1119 | "mode": "palette-classic" 1120 | }, 1121 | "custom": { 1122 | "axisBorderShow": false, 1123 | "axisCenteredZero": false, 1124 | "axisColorMode": "text", 1125 | "axisLabel": "", 1126 | "axisPlacement": "auto", 1127 | "barAlignment": 0, 1128 | "barWidthFactor": 0.6, 1129 | "drawStyle": "line", 1130 | "fillOpacity": 72, 1131 | "gradientMode": "none", 1132 | "hideFrom": { 1133 | "legend": false, 1134 | "tooltip": false, 1135 | "viz": false 1136 | }, 1137 | "insertNulls": false, 1138 | "lineInterpolation": "linear", 1139 | "lineWidth": 1, 1140 | "pointSize": 5, 1141 | "scaleDistribution": { 1142 | "type": "linear" 1143 | }, 1144 | "showPoints": "auto", 1145 | "spanNulls": false, 1146 | "stacking": { 1147 | "group": "A", 1148 | "mode": "normal" 1149 | }, 1150 | "thresholdsStyle": { 1151 | "mode": "off" 1152 | } 1153 | }, 1154 | "mappings": [], 1155 | "thresholds": { 1156 | "mode": "absolute", 1157 | "steps": [ 1158 | { 1159 | "color": "green", 1160 | "value": null 1161 | }, 1162 | { 1163 | "color": "red", 1164 | "value": 80 1165 | } 1166 | ] 1167 | }, 1168 | "unit": "binBps" 1169 | }, 1170 | "overrides": [] 1171 | }, 1172 | "gridPos": { 1173 | "h": 9, 1174 | "w": 24, 1175 | "x": 0, 1176 | "y": 8 1177 | }, 1178 | "id": 14, 1179 | "options": { 1180 | "legend": { 1181 | "calcs": [ 1182 | "lastNotNull", 1183 | "mean" 1184 | ], 1185 | "displayMode": "table", 1186 | "placement": "right", 1187 | "showLegend": true, 1188 | "sortBy": "Mean", 1189 | "sortDesc": true 1190 | }, 1191 | "tooltip": { 1192 | "mode": "single", 1193 | "sort": "none" 1194 | } 1195 | }, 1196 | "targets": [ 1197 | { 1198 | "datasource": { 1199 | "type": "prometheus", 1200 | "uid": "${DS_PROMETHEUS}" 1201 | }, 1202 | "editorMode": "code", 1203 | "expr": "transmission_torrent_upload_bytes{name=~\"$Torrent\"}", 1204 | "instant": false, 1205 | "legendFormat": "{{name}}", 1206 | "range": true, 1207 | "refId": "A" 1208 | } 1209 | ], 1210 | "title": "Upload rate / minute", 1211 | "type": "timeseries" 1212 | }, 1213 | { 1214 | "datasource": { 1215 | "type": "prometheus", 1216 | "uid": "${DS_PROMETHEUS}" 1217 | }, 1218 | "description": "", 1219 | "fieldConfig": { 1220 | "defaults": { 1221 | "color": { 1222 | "fixedColor": "text", 1223 | "mode": "fixed" 1224 | }, 1225 | "custom": { 1226 | "align": "auto", 1227 | "cellOptions": { 1228 | "type": "color-text" 1229 | }, 1230 | "inspect": false 1231 | }, 1232 | "fieldMinMax": true, 1233 | "mappings": [], 1234 | "thresholds": { 1235 | "mode": "absolute", 1236 | "steps": [ 1237 | { 1238 | "color": "red", 1239 | "value": null 1240 | }, 1241 | { 1242 | "color": "yellow", 1243 | "value": 10000 1244 | }, 1245 | { 1246 | "color": "green", 1247 | "value": 1000000 1248 | } 1249 | ] 1250 | } 1251 | }, 1252 | "overrides": [ 1253 | { 1254 | "matcher": { 1255 | "id": "byName", 1256 | "options": "Avg Upload 30 days" 1257 | }, 1258 | "properties": [ 1259 | { 1260 | "id": "custom.cellOptions", 1261 | "value": { 1262 | "mode": "lcd", 1263 | "type": "gauge" 1264 | } 1265 | }, 1266 | { 1267 | "id": "unit", 1268 | "value": "bytes" 1269 | }, 1270 | { 1271 | "id": "color" 1272 | } 1273 | ] 1274 | }, 1275 | { 1276 | "matcher": { 1277 | "id": "byName", 1278 | "options": "Ratio" 1279 | }, 1280 | "properties": [ 1281 | { 1282 | "id": "custom.cellOptions", 1283 | "value": { 1284 | "mode": "gradient", 1285 | "type": "color-background" 1286 | } 1287 | }, 1288 | { 1289 | "id": "decimals", 1290 | "value": 2 1291 | }, 1292 | { 1293 | "id": "thresholds", 1294 | "value": { 1295 | "mode": "absolute", 1296 | "steps": [ 1297 | { 1298 | "color": "red", 1299 | "value": null 1300 | }, 1301 | { 1302 | "color": "#EAB839", 1303 | "value": 1 1304 | }, 1305 | { 1306 | "color": "green", 1307 | "value": 2 1308 | } 1309 | ] 1310 | } 1311 | }, 1312 | { 1313 | "id": "custom.width", 1314 | "value": 80 1315 | }, 1316 | { 1317 | "id": "color" 1318 | } 1319 | ] 1320 | }, 1321 | { 1322 | "matcher": { 1323 | "id": "byName", 1324 | "options": "Torrrent name" 1325 | }, 1326 | "properties": [ 1327 | { 1328 | "id": "custom.width", 1329 | "value": 300 1330 | } 1331 | ] 1332 | }, 1333 | { 1334 | "matcher": { 1335 | "id": "byName", 1336 | "options": "id" 1337 | }, 1338 | "properties": [ 1339 | { 1340 | "id": "custom.width", 1341 | "value": 30 1342 | } 1343 | ] 1344 | }, 1345 | { 1346 | "matcher": { 1347 | "id": "byName", 1348 | "options": "Files" 1349 | }, 1350 | "properties": [ 1351 | { 1352 | "id": "custom.width", 1353 | "value": 30 1354 | } 1355 | ] 1356 | }, 1357 | { 1358 | "matcher": { 1359 | "id": "byName", 1360 | "options": "Seeders (avg)" 1361 | }, 1362 | "properties": [ 1363 | { 1364 | "id": "custom.width", 1365 | "value": 75 1366 | }, 1367 | { 1368 | "id": "decimals", 1369 | "value": 0 1370 | }, 1371 | { 1372 | "id": "color", 1373 | "value": { 1374 | "fixedColor": "green", 1375 | "mode": "fixed" 1376 | } 1377 | } 1378 | ] 1379 | }, 1380 | { 1381 | "matcher": { 1382 | "id": "byName", 1383 | "options": "Leechers (avg)" 1384 | }, 1385 | "properties": [ 1386 | { 1387 | "id": "custom.width", 1388 | "value": 80 1389 | }, 1390 | { 1391 | "id": "decimals", 1392 | "value": 0 1393 | }, 1394 | { 1395 | "id": "color", 1396 | "value": { 1397 | "fixedColor": "red", 1398 | "mode": "shades" 1399 | } 1400 | } 1401 | ] 1402 | }, 1403 | { 1404 | "matcher": { 1405 | "id": "byName", 1406 | "options": "Added" 1407 | }, 1408 | "properties": [ 1409 | { 1410 | "id": "custom.width", 1411 | "value": 100 1412 | }, 1413 | { 1414 | "id": "unit", 1415 | "value": "dateTimeFromNow" 1416 | } 1417 | ] 1418 | }, 1419 | { 1420 | "matcher": { 1421 | "id": "byName", 1422 | "options": "Downloads" 1423 | }, 1424 | "properties": [ 1425 | { 1426 | "id": "custom.width", 1427 | "value": 95 1428 | } 1429 | ] 1430 | }, 1431 | { 1432 | "matcher": { 1433 | "id": "byName", 1434 | "options": "Current upload" 1435 | }, 1436 | "properties": [ 1437 | { 1438 | "id": "custom.cellOptions", 1439 | "value": { 1440 | "mode": "gradient", 1441 | "type": "gauge" 1442 | } 1443 | }, 1444 | { 1445 | "id": "color", 1446 | "value": { 1447 | "mode": "continuous-RdYlGr" 1448 | } 1449 | }, 1450 | { 1451 | "id": "unit", 1452 | "value": "binBps" 1453 | } 1454 | ] 1455 | }, 1456 | { 1457 | "matcher": { 1458 | "id": "byName", 1459 | "options": "Avg upload 1 day" 1460 | }, 1461 | "properties": [ 1462 | { 1463 | "id": "custom.cellOptions", 1464 | "value": { 1465 | "mode": "lcd", 1466 | "type": "gauge" 1467 | } 1468 | }, 1469 | { 1470 | "id": "unit", 1471 | "value": "binBps" 1472 | }, 1473 | { 1474 | "id": "color" 1475 | } 1476 | ] 1477 | }, 1478 | { 1479 | "matcher": { 1480 | "id": "byName", 1481 | "options": "Peers connected" 1482 | }, 1483 | "properties": [ 1484 | { 1485 | "id": "custom.width", 1486 | "value": 75 1487 | }, 1488 | { 1489 | "id": "color" 1490 | }, 1491 | { 1492 | "id": "thresholds", 1493 | "value": { 1494 | "mode": "absolute", 1495 | "steps": [ 1496 | { 1497 | "color": "text", 1498 | "value": null 1499 | }, 1500 | { 1501 | "color": "green", 1502 | "value": 1 1503 | } 1504 | ] 1505 | } 1506 | } 1507 | ] 1508 | }, 1509 | { 1510 | "matcher": { 1511 | "id": "byName", 1512 | "options": "Peers DL (downloading from us)" 1513 | }, 1514 | "properties": [ 1515 | { 1516 | "id": "custom.width", 1517 | "value": 75 1518 | }, 1519 | { 1520 | "id": "color" 1521 | }, 1522 | { 1523 | "id": "thresholds", 1524 | "value": { 1525 | "mode": "absolute", 1526 | "steps": [ 1527 | { 1528 | "color": "text", 1529 | "value": null 1530 | }, 1531 | { 1532 | "color": "green", 1533 | "value": 1 1534 | } 1535 | ] 1536 | } 1537 | } 1538 | ] 1539 | }, 1540 | { 1541 | "matcher": { 1542 | "id": "byName", 1543 | "options": "Size" 1544 | }, 1545 | "properties": [ 1546 | { 1547 | "id": "unit", 1548 | "value": "bytes" 1549 | }, 1550 | { 1551 | "id": "custom.width", 1552 | "value": 75 1553 | } 1554 | ] 1555 | }, 1556 | { 1557 | "matcher": { 1558 | "id": "byName", 1559 | "options": "Uploaded" 1560 | }, 1561 | "properties": [ 1562 | { 1563 | "id": "unit", 1564 | "value": "bytes" 1565 | }, 1566 | { 1567 | "id": "custom.width", 1568 | "value": 80 1569 | } 1570 | ] 1571 | } 1572 | ] 1573 | }, 1574 | "gridPos": { 1575 | "h": 22, 1576 | "w": 24, 1577 | "x": 0, 1578 | "y": 17 1579 | }, 1580 | "id": 15, 1581 | "options": { 1582 | "cellHeight": "sm", 1583 | "footer": { 1584 | "countRows": false, 1585 | "fields": "", 1586 | "reducer": [ 1587 | "sum" 1588 | ], 1589 | "show": false 1590 | }, 1591 | "showHeader": true, 1592 | "sortBy": [ 1593 | { 1594 | "desc": true, 1595 | "displayName": "Avg upload 1 day" 1596 | } 1597 | ] 1598 | }, 1599 | "pluginVersion": "11.2.0", 1600 | "targets": [ 1601 | { 1602 | "datasource": { 1603 | "type": "prometheus", 1604 | "uid": "${DS_PROMETHEUS}" 1605 | }, 1606 | "editorMode": "code", 1607 | "exemplar": false, 1608 | "expr": "avg_over_time (transmission_torrent_upload_bytes{name=~\"$Torrent\"}[30d])", 1609 | "format": "table", 1610 | "instant": true, 1611 | "legendFormat": "{{name}}", 1612 | "range": false, 1613 | "refId": "A" 1614 | }, 1615 | { 1616 | "datasource": { 1617 | "type": "prometheus", 1618 | "uid": "${DS_PROMETHEUS}" 1619 | }, 1620 | "editorMode": "code", 1621 | "exemplar": false, 1622 | "expr": "transmission_torrent_ratio{name=~\"$Torrent\"}", 1623 | "format": "table", 1624 | "hide": false, 1625 | "instant": true, 1626 | "legendFormat": "{{name}}", 1627 | "range": false, 1628 | "refId": "B" 1629 | }, 1630 | { 1631 | "datasource": { 1632 | "type": "prometheus", 1633 | "uid": "${DS_PROMETHEUS}" 1634 | }, 1635 | "editorMode": "code", 1636 | "exemplar": false, 1637 | "expr": "transmission_torrent_files_total{name=~\"$Torrent\"}", 1638 | "format": "table", 1639 | "hide": false, 1640 | "instant": true, 1641 | "legendFormat": "__auto", 1642 | "range": false, 1643 | "refId": "C" 1644 | }, 1645 | { 1646 | "datasource": { 1647 | "type": "prometheus", 1648 | "uid": "${DS_PROMETHEUS}" 1649 | }, 1650 | "editorMode": "code", 1651 | "exemplar": false, 1652 | "expr": "avg_over_time (transmission_torrent_seeders{name=~\"$Torrent\"}[30d])", 1653 | "format": "table", 1654 | "hide": false, 1655 | "instant": true, 1656 | "legendFormat": "{{name}}", 1657 | "range": false, 1658 | "refId": "D" 1659 | }, 1660 | { 1661 | "datasource": { 1662 | "type": "prometheus", 1663 | "uid": "${DS_PROMETHEUS}" 1664 | }, 1665 | "editorMode": "code", 1666 | "exemplar": false, 1667 | "expr": "avg_over_time (transmission_torrent_leechers{name=~\"$Torrent\"}[30d])", 1668 | "format": "table", 1669 | "hide": false, 1670 | "instant": true, 1671 | "legendFormat": "{{name}}", 1672 | "range": false, 1673 | "refId": "E" 1674 | }, 1675 | { 1676 | "datasource": { 1677 | "type": "prometheus", 1678 | "uid": "${DS_PROMETHEUS}" 1679 | }, 1680 | "editorMode": "code", 1681 | "exemplar": false, 1682 | "expr": "transmission_torrent_added{name=~\"$Torrent\"} * 1000", 1683 | "format": "table", 1684 | "hide": false, 1685 | "instant": true, 1686 | "legendFormat": "{{name}}", 1687 | "range": false, 1688 | "refId": "F" 1689 | }, 1690 | { 1691 | "datasource": { 1692 | "type": "prometheus", 1693 | "uid": "${DS_PROMETHEUS}" 1694 | }, 1695 | "editorMode": "code", 1696 | "exemplar": false, 1697 | "expr": "transmission_torrent_downloads_total{name=~\"$Torrent\"}", 1698 | "format": "table", 1699 | "hide": false, 1700 | "instant": true, 1701 | "legendFormat": "{{name}}", 1702 | "range": false, 1703 | "refId": "G" 1704 | }, 1705 | { 1706 | "datasource": { 1707 | "type": "prometheus", 1708 | "uid": "${DS_PROMETHEUS}" 1709 | }, 1710 | "editorMode": "code", 1711 | "exemplar": false, 1712 | "expr": "transmission_torrent_upload_bytes{name=~\"$Torrent\"}", 1713 | "format": "table", 1714 | "hide": false, 1715 | "instant": true, 1716 | "legendFormat": "{{name}}", 1717 | "range": false, 1718 | "refId": "H" 1719 | }, 1720 | { 1721 | "datasource": { 1722 | "type": "prometheus", 1723 | "uid": "${DS_PROMETHEUS}" 1724 | }, 1725 | "editorMode": "code", 1726 | "exemplar": false, 1727 | "expr": "avg_over_time (transmission_torrent_upload_bytes{name=~\"$Torrent\"}[1d])", 1728 | "format": "table", 1729 | "hide": false, 1730 | "instant": true, 1731 | "legendFormat": "{{name}}", 1732 | "range": false, 1733 | "refId": "I" 1734 | }, 1735 | { 1736 | "datasource": { 1737 | "type": "prometheus", 1738 | "uid": "${DS_PROMETHEUS}" 1739 | }, 1740 | "editorMode": "code", 1741 | "exemplar": false, 1742 | "expr": "transmission_torrent_peers_connected{name=~\"$Torrent\"}", 1743 | "format": "table", 1744 | "hide": false, 1745 | "instant": true, 1746 | "legendFormat": "{{name}}", 1747 | "range": false, 1748 | "refId": "J" 1749 | }, 1750 | { 1751 | "datasource": { 1752 | "type": "prometheus", 1753 | "uid": "${DS_PROMETHEUS}" 1754 | }, 1755 | "editorMode": "code", 1756 | "exemplar": false, 1757 | "expr": "transmission_torrent_peers_getting_from_us{name=~\"$Torrent\"}", 1758 | "format": "table", 1759 | "hide": false, 1760 | "instant": true, 1761 | "legendFormat": "{{name}}", 1762 | "range": false, 1763 | "refId": "K" 1764 | }, 1765 | { 1766 | "datasource": { 1767 | "type": "prometheus", 1768 | "uid": "${DS_PROMETHEUS}" 1769 | }, 1770 | "editorMode": "code", 1771 | "exemplar": false, 1772 | "expr": "transmission_torrent_total_size{name=~\"$Torrent\"}", 1773 | "format": "table", 1774 | "hide": false, 1775 | "instant": true, 1776 | "legendFormat": "{{name}}", 1777 | "range": false, 1778 | "refId": "L" 1779 | }, 1780 | { 1781 | "datasource": { 1782 | "type": "prometheus", 1783 | "uid": "${DS_PROMETHEUS}" 1784 | }, 1785 | "editorMode": "code", 1786 | "exemplar": false, 1787 | "expr": "transmission_torrent_uploaded_ever{name=~\"$Torrent\"}", 1788 | "format": "table", 1789 | "hide": false, 1790 | "instant": true, 1791 | "legendFormat": "{{name}}", 1792 | "range": false, 1793 | "refId": "M" 1794 | } 1795 | ], 1796 | "title": "Torrents stats", 1797 | "transformations": [ 1798 | { 1799 | "id": "joinByField", 1800 | "options": { 1801 | "byField": "id", 1802 | "mode": "outer" 1803 | } 1804 | }, 1805 | { 1806 | "id": "sortBy", 1807 | "options": { 1808 | "fields": {}, 1809 | "sort": [ 1810 | { 1811 | "desc": true, 1812 | "field": "Value #A" 1813 | } 1814 | ] 1815 | } 1816 | }, 1817 | { 1818 | "id": "organize", 1819 | "options": { 1820 | "excludeByName": { 1821 | "Time 1": true, 1822 | "Time 10": true, 1823 | "Time 11": true, 1824 | "Time 12": true, 1825 | "Time 13": true, 1826 | "Time 2": true, 1827 | "Time 3": true, 1828 | "Time 4": true, 1829 | "Time 5": true, 1830 | "Time 6": true, 1831 | "Time 7": true, 1832 | "Time 8": true, 1833 | "Time 9": true, 1834 | "Value #C": true, 1835 | "Value #G": true, 1836 | "__name__": true, 1837 | "__name__ 1": true, 1838 | "__name__ 2": true, 1839 | "__name__ 3": true, 1840 | "__name__ 4": true, 1841 | "__name__ 5": true, 1842 | "__name__ 6": true, 1843 | "__name__ 7": true, 1844 | "__name__ 8": true, 1845 | "chain 1": true, 1846 | "chain 10": true, 1847 | "chain 11": true, 1848 | "chain 12": true, 1849 | "chain 13": true, 1850 | "chain 2": true, 1851 | "chain 3": true, 1852 | "chain 4": true, 1853 | "chain 5": true, 1854 | "chain 6": true, 1855 | "chain 7": true, 1856 | "chain 8": true, 1857 | "chain 9": true, 1858 | "id 1": true, 1859 | "id 2": true, 1860 | "instance 1": true, 1861 | "instance 10": true, 1862 | "instance 11": true, 1863 | "instance 12": true, 1864 | "instance 13": true, 1865 | "instance 2": true, 1866 | "instance 3": true, 1867 | "instance 4": true, 1868 | "instance 5": true, 1869 | "instance 6": true, 1870 | "instance 7": true, 1871 | "instance 8": true, 1872 | "instance 9": true, 1873 | "job 1": true, 1874 | "job 10": true, 1875 | "job 11": true, 1876 | "job 12": true, 1877 | "job 13": true, 1878 | "job 2": true, 1879 | "job 3": true, 1880 | "job 4": true, 1881 | "job 5": true, 1882 | "job 6": true, 1883 | "job 7": true, 1884 | "job 8": true, 1885 | "job 9": true, 1886 | "name 1": false, 1887 | "name 10": true, 1888 | "name 11": true, 1889 | "name 12": true, 1890 | "name 13": true, 1891 | "name 2": true, 1892 | "name 3": true, 1893 | "name 4": true, 1894 | "name 5": true, 1895 | "name 6": true, 1896 | "name 7": true, 1897 | "name 8": true, 1898 | "name 9": true, 1899 | "tracker": true, 1900 | "tracker 1": true, 1901 | "tracker 2": true, 1902 | "tracker 3": true 1903 | }, 1904 | "includeByName": {}, 1905 | "indexByName": { 1906 | "Time 1": 15, 1907 | "Time 10": 66, 1908 | "Time 11": 72, 1909 | "Time 12": 78, 1910 | "Time 13": 84, 1911 | "Time 2": 19, 1912 | "Time 3": 25, 1913 | "Time 4": 31, 1914 | "Time 5": 37, 1915 | "Time 6": 43, 1916 | "Time 7": 48, 1917 | "Time 8": 55, 1918 | "Time 9": 61, 1919 | "Value #A": 14, 1920 | "Value #B": 8, 1921 | "Value #C": 4, 1922 | "Value #D": 5, 1923 | "Value #E": 6, 1924 | "Value #F": 2, 1925 | "Value #G": 7, 1926 | "Value #H": 12, 1927 | "Value #I": 13, 1928 | "Value #J": 10, 1929 | "Value #K": 11, 1930 | "Value #L": 3, 1931 | "Value #M": 9, 1932 | "__name__ 1": 24, 1933 | "__name__ 2": 26, 1934 | "__name__ 3": 49, 1935 | "__name__ 4": 56, 1936 | "__name__ 5": 67, 1937 | "__name__ 6": 73, 1938 | "__name__ 7": 79, 1939 | "__name__ 8": 85, 1940 | "chain 1": 16, 1941 | "chain 10": 68, 1942 | "chain 11": 74, 1943 | "chain 12": 80, 1944 | "chain 13": 86, 1945 | "chain 2": 20, 1946 | "chain 3": 27, 1947 | "chain 4": 32, 1948 | "chain 5": 38, 1949 | "chain 6": 44, 1950 | "chain 7": 50, 1951 | "chain 8": 57, 1952 | "chain 9": 62, 1953 | "id": 0, 1954 | "instance 1": 17, 1955 | "instance 10": 69, 1956 | "instance 11": 75, 1957 | "instance 12": 81, 1958 | "instance 13": 87, 1959 | "instance 2": 21, 1960 | "instance 3": 28, 1961 | "instance 4": 33, 1962 | "instance 5": 39, 1963 | "instance 6": 45, 1964 | "instance 7": 51, 1965 | "instance 8": 58, 1966 | "instance 9": 63, 1967 | "job 1": 18, 1968 | "job 10": 70, 1969 | "job 11": 76, 1970 | "job 12": 82, 1971 | "job 13": 88, 1972 | "job 2": 22, 1973 | "job 3": 29, 1974 | "job 4": 34, 1975 | "job 5": 40, 1976 | "job 6": 46, 1977 | "job 7": 52, 1978 | "job 8": 59, 1979 | "job 9": 64, 1980 | "name 1": 1, 1981 | "name 10": 71, 1982 | "name 11": 77, 1983 | "name 12": 83, 1984 | "name 13": 89, 1985 | "name 2": 23, 1986 | "name 3": 30, 1987 | "name 4": 35, 1988 | "name 5": 41, 1989 | "name 6": 47, 1990 | "name 7": 53, 1991 | "name 8": 60, 1992 | "name 9": 65, 1993 | "tracker 1": 36, 1994 | "tracker 2": 42, 1995 | "tracker 3": 54 1996 | }, 1997 | "renameByName": { 1998 | "Time 2": "", 1999 | "Value #A": "Avg Upload 30 days", 2000 | "Value #B": "Ratio", 2001 | "Value #C": "Files", 2002 | "Value #D": "Seeders (avg)", 2003 | "Value #E": "Leechers (avg)", 2004 | "Value #F": "Added", 2005 | "Value #G": "Downloads", 2006 | "Value #H": "Current upload", 2007 | "Value #I": "Avg upload 1 day", 2008 | "Value #J": "Peers connected", 2009 | "Value #K": "Peers DL (downloading from us)", 2010 | "Value #L": "Size", 2011 | "Value #M": "Uploaded", 2012 | "name": "Torrent", 2013 | "name 1": "Torrrent name", 2014 | "name 10": "", 2015 | "tracker": "", 2016 | "tracker 3": "" 2017 | } 2018 | } 2019 | }, 2020 | { 2021 | "id": "convertFieldType", 2022 | "options": { 2023 | "conversions": [ 2024 | { 2025 | "dateFormat": "$(UNIX)", 2026 | "destinationType": "time", 2027 | "targetField": "Added" 2028 | } 2029 | ], 2030 | "fields": {} 2031 | } 2032 | } 2033 | ], 2034 | "type": "table" 2035 | } 2036 | ], 2037 | "refresh": "10s", 2038 | "schemaVersion": 39, 2039 | "tags": [ 2040 | "prometheus", 2041 | "transmission" 2042 | ], 2043 | "templating": { 2044 | "list": [ 2045 | { 2046 | "current": {}, 2047 | "datasource": { 2048 | "type": "prometheus", 2049 | "uid": "${DS_PROMETHEUS}" 2050 | }, 2051 | "definition": "label_values(transmission_torrent_ratio,name)", 2052 | "hide": 0, 2053 | "includeAll": true, 2054 | "multi": true, 2055 | "name": "Torrent", 2056 | "options": [], 2057 | "query": { 2058 | "qryType": 1, 2059 | "query": "label_values(transmission_torrent_ratio,name)", 2060 | "refId": "PrometheusVariableQueryEditor-VariableQuery" 2061 | }, 2062 | "refresh": 1, 2063 | "regex": "", 2064 | "skipUrlSync": false, 2065 | "sort": 0, 2066 | "tagValuesQuery": "", 2067 | "tagsQuery": "", 2068 | "type": "query", 2069 | "useTags": false 2070 | } 2071 | ] 2072 | }, 2073 | "time": { 2074 | "from": "now-1h", 2075 | "to": "now" 2076 | }, 2077 | "timepicker": { 2078 | "refresh_intervals": [ 2079 | "5s", 2080 | "10s", 2081 | "30s", 2082 | "1m", 2083 | "5m", 2084 | "15m", 2085 | "30m", 2086 | "1h", 2087 | "2h", 2088 | "1d" 2089 | ], 2090 | "time_options": [ 2091 | "5m", 2092 | "15m", 2093 | "1h", 2094 | "6h", 2095 | "12h", 2096 | "24h", 2097 | "2d", 2098 | "7d", 2099 | "30d" 2100 | ] 2101 | }, 2102 | "timezone": "", 2103 | "title": "Transmission", 2104 | "uid": "y_TlWHJiz", 2105 | "version": 25, 2106 | "weekStart": "" 2107 | } -------------------------------------------------------------------------------- /examples/docker-compose.yml: -------------------------------------------------------------------------------- 1 | transmission: 2 | image: linuxserver/transmission 3 | restart: always 4 | ports: 5 | - "127.0.0.1:9091:9091" 6 | - "51413:51413" 7 | - "51413:51413/udp" 8 | 9 | transmission-exporter: 10 | image: metalmatze/transmission-exporter 11 | restart: always 12 | links: 13 | - transmission 14 | ports: 15 | - "127.0.0.1:19091:19091" 16 | environment: 17 | TRANSMISSION_ADDR: http://transmission:9091 18 | 19 | prometheus: 20 | image: quay.io/prometheus/prometheus 21 | command: "-config.file=/etc/prometheus/prometheus.yml" 22 | links: 23 | - transmission-exporter 24 | ports: 25 | - "127.0.0.1:9090:9090" 26 | volumes: 27 | - "./prometheus:/etc/prometheus/" 28 | -------------------------------------------------------------------------------- /examples/kubernetes/transmission.yml: -------------------------------------------------------------------------------- 1 | --- 2 | apiVersion: v1 3 | kind: Namespace 4 | metadata: 5 | name: transmission 6 | --- 7 | apiVersion: extensions/v1beta1 8 | kind: Deployment 9 | metadata: 10 | name: transmission 11 | namespace: transmission 12 | spec: 13 | replicas: 1 14 | selector: 15 | matchLabels: 16 | name: transmission 17 | template: 18 | metadata: 19 | labels: 20 | name: transmission 21 | annotations: 22 | prometheus.io/scrape: 'true' 23 | prometheus.io/port: '9190' 24 | spec: 25 | containers: 26 | - image: linuxserver/transmission 27 | imagePullPolicy: Always 28 | name: transmission 29 | env: 30 | - name: PGID 31 | value: "1000" 32 | - name: "PUID" 33 | value: "1000" 34 | - name: "TZ" 35 | value: "Europe/London" 36 | resources: 37 | limits: 38 | cpu: 2000m 39 | memory: 2000Mi 40 | requests: 41 | cpu: 1000m 42 | memory: 1000Mi 43 | ports: 44 | - name: default-http 45 | containerPort: 9091 46 | - name: dht 47 | containerPort: 51413 48 | - name: dht-udp 49 | containerPort: 51413 50 | protocol: UDP 51 | # You should assign these to some real volumes 52 | # volumeMounts: 53 | # - name: downloads 54 | # mountPath: "/downloads" 55 | # - name: config 56 | # mountPath: "/config" 57 | - image: metalmatze/transmission-exporter 58 | name: transmission-exporter 59 | imagePullPolicy: Always 60 | resources: 61 | limits: 62 | cpu: 100m 63 | memory: 100Mi 64 | requests: 65 | cpu: 50m 66 | memory: 50Mi 67 | ports: 68 | - name: prom-scrape 69 | containerPort: 9190 70 | env: 71 | - name: TRANSMISSION_ADDR 72 | value: "http://localhost:9091" 73 | # Uncomment these once you add authentication to transmission 74 | # - name: TRANSMISSION_USERNAME 75 | # value: "transmission" 76 | # You can use a tool like `kontemplate` + `pass` to avoid hardcoding this in your repo 77 | # - name: TRANSMISSION_PASSWORD 78 | # value: "hunter2" 79 | - name: WEB_PATH 80 | value: "/metrics" 81 | - name: WEB_ADDR 82 | value: ":9190" 83 | # You should create some real PVCs for this 84 | # 85 | # volumes: 86 | # - name: downloads 87 | # persistentVolumeClaim: 88 | # claimName: transmission-downloads 89 | # - name: config 90 | # persistentVolumeClaim: 91 | # claimName: transmission-config 92 | --- 93 | apiVersion: v1 94 | kind: Service 95 | metadata: 96 | namespace: transmission 97 | name: transmission 98 | spec: 99 | selector: 100 | name: transmission 101 | ports: 102 | - name: default-http 103 | protocol: TCP 104 | port: 80 105 | targetPort: 9091 106 | - name: dht 107 | protocol: TCP 108 | port: 51413 109 | targetPort: 51413 110 | - name: dht-udp 111 | protocol: UDP 112 | port: 51413 113 | targetPort: 51413 114 | --- 115 | -------------------------------------------------------------------------------- /examples/prometheus/prometheus.yml: -------------------------------------------------------------------------------- 1 | global: 2 | scrape_interval: 15s 3 | evaluation_interval: 15s 4 | 5 | scrape_configs: 6 | - job_name: 'transmission-exporter' 7 | static_configs: 8 | - targets: 9 | - 'transmission-exporter:19091' 10 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/metalmatze/transmission-exporter 2 | 3 | go 1.13 4 | 5 | require ( 6 | github.com/alexflint/go-arg v0.0.0-20180516182405-f7c0423bd11e 7 | github.com/alexflint/go-scalar v0.0.0-20170216020425-e80c3b7ed292 // indirect 8 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 // indirect 9 | github.com/davecgh/go-spew v1.1.1 // indirect 10 | github.com/gogo/protobuf v1.1.1 // indirect 11 | github.com/golang/protobuf v1.2.0 // indirect 12 | github.com/joho/godotenv v1.3.0 13 | github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect 14 | github.com/pmezard/go-difflib v1.0.0 // indirect 15 | github.com/prometheus/client_golang v0.9.0-pre1.0.20181010161331-7866eead363e 16 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 // indirect 17 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e // indirect 18 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d // indirect 19 | github.com/stretchr/testify v1.2.2 // indirect 20 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 // indirect 21 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alexflint/go-arg v0.0.0-20180516182405-f7c0423bd11e h1:dzrBxLIjiq17Da9DhY3svGRhptiUg1LUzdkOuFYjAzA= 2 | github.com/alexflint/go-arg v0.0.0-20180516182405-f7c0423bd11e/go.mod h1:PHxo6ZWOLVMZZgWSAqBynb/KhIqoGO6WKwOVX7rM9dg= 3 | github.com/alexflint/go-scalar v0.0.0-20170216020425-e80c3b7ed292 h1:0YTMOir1UPjebSvNmIrEKO9FFd+RZc1wwZHUrxfn4BI= 4 | github.com/alexflint/go-scalar v0.0.0-20170216020425-e80c3b7ed292/go.mod h1:dgifnFPveotJNpwJdl1hDPu5vSuqVVUPIr3isfcvgBA= 5 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 6 | github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= 7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 10 | github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= 11 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM= 12 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= 14 | github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 15 | github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= 16 | github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/prometheus/client_golang v0.9.0-pre1.0.20181010161331-7866eead363e h1:mwBXwBO7Hj6zVWKUlmaUibcQvPZn6eiTiV1ijb9BYs0= 20 | github.com/prometheus/client_golang v0.9.0-pre1.0.20181010161331-7866eead363e/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= 21 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= 22 | github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= 23 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e h1:n/3MEhJQjQxrOUCzh1Y3Re6aJUUWRp2M9+Oc3eVn/54= 24 | github.com/prometheus/common v0.0.0-20180801064454-c7de2306084e/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= 25 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d h1:GoAlyOgbOEIFdaDqxJVlbOQ1DtGmZWs/Qau0hIlk+WQ= 26 | github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= 27 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= 28 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 29 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1 h1:Y/KGZSOdz/2r0WJ9Mkmz6NJBusp0kiNx1Cn82lzJQ6w= 30 | golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 31 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA= 32 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 33 | -------------------------------------------------------------------------------- /session.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | type ( 4 | // SessionCommand is the root command to interact with Transmission via RPC 5 | SessionCommand struct { 6 | Method string `json:"method,omitempty"` 7 | Session Session `json:"arguments,omitempty"` 8 | Result string `json:"result,omitempty"` 9 | } 10 | 11 | // Session information about the current transmission session 12 | Session struct { 13 | AltSpeedDown int `json:"alt-speed-down"` 14 | AltSpeedEnabled bool `json:"alt-speed-enabled"` 15 | //Alt_speed_time_begin int `json:"alt-speed-time-begin"` 16 | //Alt_speed_time_day int `json:"alt-speed-time-day"` 17 | //Alt_speed_time_enabled bool `json:"alt-speed-time-enabled"` 18 | //Alt_speed_time_end int `json:"alt-speed-time-end"` 19 | AltSpeedUp int `json:"alt-speed-up"` 20 | //Blocklist_enabled bool `json:"blocklist-enabled"` 21 | //Blocklist_size int `json:"blocklist-size"` 22 | //Blocklist_url string `json:"blocklist-url"` 23 | CacheSizeMB int `json:"cache-size-mb"` 24 | //Config_dir string `json:"config-dir"` 25 | //Dht_enabled bool `json:"dht-enabled"` 26 | DownloadDir string `json:"download-dir"` 27 | DownloadDirFreeSpace int64 `json:"download-dir-free-space"` 28 | DownloadQueueEnabled bool `json:"download-queue-enabled"` 29 | DownloadQueueSize int `json:"download-queue-size"` 30 | //Encryption string `json:"encryption"` 31 | //Idle_seeding_limit int `json:"idle-seeding-limit"` 32 | //Idle_seeding_limit_enabled bool `json:"idle-seeding-limit-enabled"` 33 | IncompleteDir string `json:"incomplete-dir"` 34 | //Incomplete_dir_enabled bool `json:"incomplete-dir-enabled"` 35 | //Lpd_enabled bool `json:"lpd-enabled"` 36 | PeerLimitGlobal int `json:"peer-limit-global"` 37 | PeerLimitPerTorrent int `json:"peer-limit-per-torrent"` 38 | //Peer_port int `json:"peer-port"` 39 | //Peer_port_random_on_start bool `json:"peer-port-random-on-start"` 40 | //Pex_enabled bool `json:"pex-enabled"` 41 | //Port_forwarding_enabled bool `json:"port-forwarding-enabled"` 42 | //Queue_stalled_enabled bool `json:"queue-stalled-enabled"` 43 | //Queue_stalled_minutes int `json:"queue-stalled-minutes"` 44 | //Rename_partial_files bool `json:"rename-partial-files"` 45 | //RPC_version int `json:"rpc-version"` 46 | //RPC_version_minimum int `json:"rpc-version-minimum"` 47 | //Script_torrent_done_enabled bool `json:"script-torrent-done-enabled"` 48 | //Script_torrent_done_filename string `json:"script-torrent-done-filename"` 49 | SeedQueueEnabled bool `json:"seed-queue-enabled"` 50 | SeedQueueSize int `json:"seed-queue-size"` 51 | SeedRatioLimit float64 `json:"seedRatioLimit"` 52 | SeedRatioLimited bool `json:"seedRatioLimited"` 53 | SpeedLimitDown int `json:"speed-limit-down"` 54 | SpeedLimitDownEnabled bool `json:"speed-limit-down-enabled"` 55 | SpeedLimitUp int `json:"speed-limit-up"` 56 | SpeedLimitUpEnabled bool `json:"speed-limit-up-enabled"` 57 | //Start_added_torrents bool `json:"start-added-torrents"` 58 | //Trash_original_torrent_files bool `json:"trash-original-torrent-files"` 59 | //Utp_enabled bool `json:"utp-enabled"` 60 | Version string `json:"version"` 61 | } 62 | ) 63 | -------------------------------------------------------------------------------- /session_stats.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | type ( 4 | // SessionStatsCmd is the root command to interact with Transmission via RPC 5 | SessionStatsCmd struct { 6 | SessionStats `json:"arguments"` 7 | Result string `json:"result"` 8 | } 9 | 10 | // SessionStats contains information about the current & cumulative session 11 | SessionStats struct { 12 | DownloadSpeed int64 `json:"downloadSpeed"` 13 | UploadSpeed int64 `json:"uploadSpeed"` 14 | ActiveTorrentCount int `json:"activeTorrentCount"` 15 | PausedTorrentCount int `json:"pausedTorrentCount"` 16 | TorrentCount int `json:"torrentCount"` 17 | CumulativeStats SessionStateStats `json:"cumulative-stats"` 18 | CurrentStats SessionStateStats `json:"current-stats"` 19 | } 20 | // SessionStateStats contains current or cumulative session stats 21 | SessionStateStats struct { 22 | DownloadedBytes int64 `json:"downloadedBytes"` 23 | UploadedBytes int64 `json:"uploadedBytes"` 24 | FilesAdded int64 `json:"filesAdded"` 25 | SecondsActive int64 `json:"secondsActive"` 26 | SessionCount int64 `json:"sessionCount"` 27 | } 28 | ) 29 | -------------------------------------------------------------------------------- /torrent.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | type ( 4 | // TorrentCommand is the root command to interact with Transmission via RPC 5 | TorrentCommand struct { 6 | Method string `json:"method,omitempty"` 7 | Arguments TorrentArguments `json:"arguments,omitempty"` 8 | Result string `json:"result,omitempty"` 9 | } 10 | // TorrentArguments specifies the TorrentCommand in more detail 11 | TorrentArguments struct { 12 | Fields []string `json:"fields,omitempty"` 13 | Torrents []Torrent `json:"torrents,omitempty"` 14 | Ids []int `json:"ids,omitempty"` 15 | DeleteData bool `json:"delete-local-data,omitempty"` 16 | DownloadDir string `json:"download-dir,omitempty"` 17 | MetaInfo string `json:"metainfo,omitempty"` 18 | Filename string `json:"filename,omitempty"` 19 | TorrentAdded TorrentArgumentsAdded `json:"torrent-added"` 20 | } 21 | // TorrentArgumentsAdded specifies the torrent to get added data from 22 | TorrentArgumentsAdded struct { 23 | HashString string `json:"hashString"` 24 | ID int64 `json:"id"` 25 | Name string `json:"name"` 26 | } 27 | 28 | // Torrent represents a transmission torrent 29 | Torrent struct { 30 | ID int `json:"id"` 31 | Name string `json:"name"` 32 | Status int `json:"status"` 33 | Added int `json:"addedDate"` 34 | LeftUntilDone int64 `json:"leftUntilDone"` 35 | Eta int `json:"eta"` 36 | UploadRatio float64 `json:"uploadRatio"` 37 | RateDownload int `json:"rateDownload"` 38 | RateUpload int `json:"rateUpload"` 39 | DownloadDir string `json:"downloadDir"` 40 | IsFinished bool `json:"isFinished"` 41 | PercentDone float64 `json:"percentDone"` 42 | SeedRatioMode int `json:"seedRatioMode"` 43 | HashString string `json:"hashString"` 44 | Error int `json:"error"` 45 | ErrorString string `json:"errorString"` 46 | Files []File `json:"files"` 47 | FilesStats []FileStat `json:"fileStats"` 48 | TrackerStats []TrackerStat `json:"trackerStats"` 49 | Peers []Peer `json:"peers"` 50 | PeersConnected int `json:"peersConnected"` 51 | PeersGettingFromUs int `json:"peersGettingFromUs"` 52 | TotalSize int `json:"totalSize"` 53 | UploadedEver int `json:"uploadedEver"` 54 | } 55 | 56 | // ByID implements the sort Interface to sort by ID 57 | ByID []Torrent 58 | // ByName implements the sort Interface to sort by Name 59 | ByName []Torrent 60 | // ByDate implements the sort Interface to sort by Date 61 | ByDate []Torrent 62 | // ByRatio implements the sort Interface to sort by Ratio 63 | ByRatio []Torrent 64 | 65 | // File is a file contained inside a torrent 66 | File struct { 67 | BytesCompleted int64 `json:"bytesCompleted"` 68 | Length int64 `json:"length"` 69 | Name string `json:"name"` 70 | } 71 | 72 | // FileStat describe a file's priority & if it's wanted 73 | FileStat struct { 74 | BytesCompleted int64 `json:"bytesCompleted"` 75 | Priority int `json:"priority"` 76 | Wanted bool `json:"wanted"` 77 | } 78 | 79 | // TrackerStat has stats about the torrent's tracker 80 | TrackerStat struct { 81 | Announce string `json:"announce"` 82 | AnnounceState int `json:"announceState"` 83 | DownloadCount int `json:"downloadCount"` 84 | HasAnnounced bool `json:"hasAnnounced"` 85 | HasScraped bool `json:"hasScraped"` 86 | Host string `json:"host"` 87 | ID int `json:"id"` 88 | IsBackup bool `json:"isBackup"` 89 | LastAnnouncePeerCount int `json:"lastAnnouncePeerCount"` 90 | LastAnnounceResult string `json:"lastAnnounceResult"` 91 | LastAnnounceStartTime int `json:"lastAnnounceStartTime"` 92 | LastAnnounceSucceeded bool `json:"lastAnnounceSucceeded"` 93 | LastAnnounceTime int `json:"lastAnnounceTime"` 94 | LastAnnounceTimedOut bool `json:"lastAnnounceTimedOut"` 95 | LastScrapeResult string `json:"lastScrapeResult"` 96 | LastScrapeStartTime int `json:"lastScrapeStartTime"` 97 | LastScrapeSucceeded bool `json:"lastScrapeSucceeded"` 98 | LastScrapeTime int `json:"lastScrapeTime"` 99 | LastScrapeTimedOut bool `json:"lastScrapeTimedOut"` 100 | LeecherCount int `json:"leecherCount"` 101 | NextAnnounceTime int `json:"nextAnnounceTime"` 102 | NextScrapeTime int `json:"nextScrapeTime"` 103 | Scrape string `json:"scrape"` 104 | ScrapeState int `json:"scrapeState"` 105 | SeederCount int `json:"seederCount"` 106 | Tier int `json:"tier"` 107 | } 108 | 109 | // Peer of a torrent 110 | Peer struct { 111 | Address string `json:"address"` 112 | ClientIsChoked bool `json:"clientIsChoked"` 113 | ClientIsInterested bool `json:"clientIsInterested"` 114 | ClientName string `json:"clientName"` 115 | FlagStr string `json:"flagStr"` 116 | IsDownloadingFrom bool `json:"isDownloadingFrom"` 117 | IsEncrypted bool `json:"isEncrypted"` 118 | IsIncoming bool `json:"isIncoming"` 119 | IsUTP bool `json:"isUTP"` 120 | IsUploadingTo bool `json:"isUploadingTo"` 121 | PeerIsChoked bool `json:"peerIsChoked"` 122 | PeerIsInterested bool `json:"peerIsInterested"` 123 | Port int `json:"port"` 124 | Progress float64 `json:"progress"` 125 | RateToClient int `json:"rateToClient"` 126 | RateToPeer int `json:"rateToPeer"` 127 | } 128 | ) 129 | 130 | func (t ByID) Len() int { return len(t) } 131 | func (t ByID) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 132 | func (t ByID) Less(i, j int) bool { return t[i].ID < t[j].ID } 133 | 134 | func (t ByName) Len() int { return len(t) } 135 | func (t ByName) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 136 | func (t ByName) Less(i, j int) bool { return t[i].Name < t[j].Name } 137 | 138 | func (t ByDate) Len() int { return len(t) } 139 | func (t ByDate) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 140 | func (t ByDate) Less(i, j int) bool { return t[i].Added < t[j].Added } 141 | 142 | func (t ByRatio) Len() int { return len(t) } 143 | func (t ByRatio) Swap(i, j int) { t[i], t[j] = t[j], t[i] } 144 | func (t ByRatio) Less(i, j int) bool { return t[i].UploadRatio < t[j].UploadRatio } 145 | -------------------------------------------------------------------------------- /transmission.go: -------------------------------------------------------------------------------- 1 | package transmission 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "errors" 7 | "io/ioutil" 8 | "net/http" 9 | "strings" 10 | ) 11 | 12 | const endpoint = "/transmission/rpc/" 13 | 14 | type ( 15 | // User to authenticate with Transmission 16 | User struct { 17 | Username string 18 | Password string 19 | } 20 | // Client connects to transmission via HTTP 21 | Client struct { 22 | URL string 23 | token string 24 | 25 | User *User 26 | client http.Client 27 | } 28 | ) 29 | 30 | // New create new transmission torrent 31 | func New(url string, user *User) *Client { 32 | return &Client{ 33 | URL: url + endpoint, 34 | User: user, 35 | } 36 | } 37 | 38 | func (c *Client) post(body []byte) ([]byte, error) { 39 | authRequest, err := c.authRequest("POST", body) 40 | if err != nil { 41 | return make([]byte, 0), err 42 | } 43 | 44 | res, err := c.client.Do(authRequest) 45 | if err != nil { 46 | return make([]byte, 0), err 47 | } 48 | defer res.Body.Close() 49 | 50 | if res.StatusCode == http.StatusUnauthorized { 51 | return make([]byte, 0), errors.New("authorization failed, check your username and password and make sure the ip is whitelisted") 52 | } 53 | 54 | if res.StatusCode == http.StatusConflict { 55 | c.getToken() 56 | authRequest, err := c.authRequest("POST", body) 57 | if err != nil { 58 | return make([]byte, 0), err 59 | } 60 | res, err = c.client.Do(authRequest) 61 | if err != nil { 62 | return make([]byte, 0), err 63 | } 64 | } 65 | 66 | resBody, err := ioutil.ReadAll(res.Body) 67 | if err != nil { 68 | return make([]byte, 0), err 69 | } 70 | return resBody, nil 71 | } 72 | 73 | func (c *Client) getToken() error { 74 | req, err := http.NewRequest("POST", c.URL, strings.NewReader("")) 75 | if err != nil { 76 | return err 77 | } 78 | 79 | if c.User != nil { 80 | req.SetBasicAuth(c.User.Username, c.User.Password) 81 | } 82 | 83 | res, err := c.client.Do(req) 84 | if err != nil { 85 | return err 86 | } 87 | defer res.Body.Close() 88 | c.token = res.Header.Get("X-Transmission-Session-Id") 89 | return nil 90 | } 91 | 92 | func (c *Client) authRequest(method string, body []byte) (*http.Request, error) { 93 | if c.token == "" { 94 | err := c.getToken() 95 | if err != nil { 96 | return nil, err 97 | } 98 | } 99 | req, err := http.NewRequest(method, c.URL, bytes.NewReader(body)) 100 | if err != nil { 101 | return nil, err 102 | } 103 | req.Header.Add("X-Transmission-Session-Id", c.token) 104 | 105 | if c.User != nil { 106 | req.SetBasicAuth(c.User.Username, c.User.Password) 107 | } 108 | 109 | return req, nil 110 | } 111 | 112 | // GetTorrents get a list of torrents 113 | func (c *Client) GetTorrents() ([]Torrent, error) { 114 | cmd := TorrentCommand{ 115 | Method: "torrent-get", 116 | Arguments: TorrentArguments{ 117 | Fields: []string{ 118 | "id", 119 | "name", 120 | "hashString", 121 | "status", 122 | "addedDate", 123 | "leftUntilDone", 124 | "eta", 125 | "uploadRatio", 126 | "rateDownload", 127 | "rateUpload", 128 | "downloadDir", 129 | "isFinished", 130 | "percentDone", 131 | "seedRatioMode", 132 | "error", 133 | "errorString", 134 | "files", 135 | "fileStats", 136 | "peers", 137 | "trackers", 138 | "trackerStats", 139 | "peersConnected", 140 | "peersGettingFromUs", 141 | "totalSize", 142 | "uploadedEver", 143 | }, 144 | }, 145 | } 146 | 147 | req, err := json.Marshal(&cmd) 148 | if err != nil { 149 | return nil, err 150 | } 151 | 152 | resp, err := c.post(req) 153 | if err != nil { 154 | return nil, err 155 | } 156 | 157 | var out TorrentCommand 158 | if err := json.Unmarshal(resp, &out); err != nil { 159 | return nil, err 160 | } 161 | 162 | return out.Arguments.Torrents, nil 163 | } 164 | 165 | // GetSession gets the current session from transmission 166 | func (c *Client) GetSession() (*Session, error) { 167 | req, err := json.Marshal(SessionCommand{Method: "session-get"}) 168 | if err != nil { 169 | return nil, err 170 | } 171 | 172 | resp, err := c.post(req) 173 | if err != nil { 174 | return nil, err 175 | } 176 | 177 | var cmd SessionCommand 178 | if err := json.Unmarshal(resp, &cmd); err != nil { 179 | return nil, err 180 | } 181 | 182 | return &cmd.Session, nil 183 | } 184 | 185 | // GetSessionStats gets stats on the current & cumulative session 186 | func (c *Client) GetSessionStats() (*SessionStats, error) { 187 | req, err := json.Marshal(SessionCommand{Method: "session-stats"}) 188 | if err != nil { 189 | return nil, err 190 | } 191 | 192 | resp, err := c.post(req) 193 | if err != nil { 194 | return nil, err 195 | } 196 | 197 | var cmd SessionStatsCmd 198 | if err := json.Unmarshal(resp, &cmd); err != nil { 199 | return nil, err 200 | } 201 | 202 | return &cmd.SessionStats, nil 203 | } 204 | --------------------------------------------------------------------------------