Repository: onedr0p/exportarr (go)

Issue:
Parse Indexer Unavailable Errors to emit `unavailable_indexers` metric
# Details

**Describe the solution you'd like:**

Prowlarr, interestingly, does not consider indexers which have been marked "unavailable" as "disabled", so they don't increment the disabled metric (It only counts indexers which were manually disabled). Unavailable indexers seems like a metric that will commonly want to be alerted on, but the only place it shows up is as a health issue (of source `IndexerLongTermStatusCheck`). We should consider special casing this error type to emit an unavailable indexer metric. The actual logic isn't particularly difficulty, but system health issues are collected by a common collector, so we probably need to think a little bit about where and how to collect this and emit it as another metric. 

![image](https://user-images.githubusercontent.com/5419724/234966557-ed9a7940-6966-4bf9-a630-73132c85fe67.png)

**Anything else you would like to add:**

<!-- Note: Miscellaneous information that will assist in solving the issue. -->

**Additional Information:**

<!-- Note: Anything to give further context to the requested new feature. -->


Pull request:
Parse unavailable indexer health messages and emit  metric
<!--
Before you open the request please review the following guidelines and tips to help it be more easily integrated:

- Describe the scope of your change - i.e. what the change does.
- Describe any known limitations with your change.
- Please run any tests or examples that can exercise your modified code.

Thank you for contributing! We will try to test and integrate the change as soon as we can. There is no need to bump or check in on a pull request (it will clutter the discussion of the request).

Also don't be worried if the request is closed or not integrated sometimes our priorities might not match the priorities of the pull request. Don't fret, the open source community thrives on forks and GitHub makes it easy to keep your changes in a forked repo.
-->

**Description of the change**

<!-- Describe the scope of your change - i.e. what the change does. -->

Prowlarr doesn't consider indexers that it's marked unavailable due to errors as "disabled", it only considers them "unavailable". This unavailable status doesn't have a metric of it's own, it just shows up as a health message, with the server names in a comma delimited list in the message.

This change looks for `IndexerStatusCheck` health messages, and emits an extra metric per indexer. 

**Benefits**

<!-- What benefits will be realized by the code change? -->

This extra metric will make it much simpler to build alarms, as one can key off the number of indexers unavailable (or even % of indexers unavailable by dividing this by `indexers_total` or `indexers_enabled`)

**Possible drawbacks**

<!-- Describe any known limitations with your change -->

**Applicable issues**

<!-- Enter any applicable Issues here (You can reference an issue using #) -->
- fixes #158 

**Additional information**

<!-- If there's anything else that's important and relevant to your pull request, mention that information here.-->
Metrics:
```
# HELP prowlarr_indexer_unavailable Indexers marked unavailable due to repeated errors
# TYPE prowlarr_indexer_unavailable gauge
prowlarr_indexer_unavailable{indexer="ServerOne",url="https://my.instance"} 1
prowlarr_indexer_unavailable{indexer="ServerTwo",url="https://my.instance"} 1
prowlarr_indexer_unavailable{indexer="ServerThree",url="https://my.instance"} 1
prowlarr_indexer_unavailable{indexer="Server.four",url="https://my.instance"} 1
```

Patch:
diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md
index 317374b..a74222c 100644
--- a/.github/ISSUE_TEMPLATE/bug.md
+++ b/.github/ISSUE_TEMPLATE/bug.md
@@ -2,7 +2,7 @@
 name: Bug report
 about: Create a report to help us improve
 title: ''
-labels: kind:bug
+labels: bug
 assignees: ''
 
 ---
diff --git a/.github/ISSUE_TEMPLATE/enhancement.md b/.github/ISSUE_TEMPLATE/enhancement.md
index a5f93fc..6178392 100644
--- a/.github/ISSUE_TEMPLATE/enhancement.md
+++ b/.github/ISSUE_TEMPLATE/enhancement.md
@@ -2,7 +2,7 @@
 name: Feature request
 about: Suggest an idea for this project
 title: ''
-labels: kind:enhancement
+labels: enhancement
 assignees: ''
 
 ---
diff --git a/internal/arr/collector/health.go b/internal/arr/collector/health.go
index 4b352ce..cab46a0 100644
--- a/internal/arr/collector/health.go
+++ b/internal/arr/collector/health.go
@@ -11,12 +11,18 @@ import (
 )
 
 type systemHealthCollector struct {
-	config             *config.ArrConfig // App configuration
-	systemHealthMetric *prometheus.Desc  // Total number of health issues
-	errorMetric        *prometheus.Desc  // Error Description for use with InvalidMetric
+	config             *config.ArrConfig          // App configuration
+	systemHealthMetric *prometheus.Desc           // Total number of health issues
+	errorMetric        *prometheus.Desc           // Error Description for use with InvalidMetric
+	extraEmitters      []ExtraHealthMetricEmitter // Registered Emitters for extra per-app metrics
 }
 
-func NewSystemHealthCollector(c *config.ArrConfig) *systemHealthCollector {
+type ExtraHealthMetricEmitter interface {
+	Describe() *prometheus.Desc
+	Emit(model.SystemHealthMessage) []prometheus.Metric
+}
+
+func NewSystemHealthCollector(c *config.ArrConfig, emitters ...ExtraHealthMetricEmitter) *systemHealthCollector {
 	return &systemHealthCollector{
 		config: c,
 		systemHealthMetric: prometheus.NewDesc(
@@ -31,11 +37,15 @@ func NewSystemHealthCollector(c *config.ArrConfig) *systemHealthCollector {
 			nil,
 			prometheus.Labels{"url": c.URL},
 		),
+		extraEmitters: emitters,
 	}
 }
 
 func (collector *systemHealthCollector) Describe(ch chan<- *prometheus.Desc) {
 	ch <- collector.systemHealthMetric
+	for _, emitter := range collector.extraEmitters {
+		ch <- emitter.Describe()
+	}
 }
 
 func (collector *systemHealthCollector) Collect(ch chan<- prometheus.Metric) {
@@ -58,6 +68,11 @@ func (collector *systemHealthCollector) Collect(ch chan<- prometheus.Metric) {
 			ch <- prometheus.MustNewConstMetric(collector.systemHealthMetric, prometheus.GaugeValue, float64(1),
 				s.Source, s.Type, s.Message, s.WikiURL,
 			)
+			for _, emitter := range collector.extraEmitters {
+				for _, metric := range emitter.Emit(s) {
+					ch <- metric
+				}
+			}
 		}
 	} else {
 		ch <- prometheus.MustNewConstMetric(collector.systemHealthMetric, prometheus.GaugeValue, float64(0), "", "", "", "")
diff --git a/internal/arr/collector/prowlarr.go b/internal/arr/collector/prowlarr.go
index 03e7796..fb66e61 100644
--- a/internal/arr/collector/prowlarr.go
+++ b/internal/arr/collector/prowlarr.go
@@ -1,6 +1,7 @@
 package collector
 
 import (
+	"strings"
 	"sync"
 	"time"
 
@@ -91,6 +92,42 @@ func (u *userAgentStatCache) UpdateKey(key string, value model.UserAgentStats) m
 	return entry
 }
 
+type UnavailableIndexerEmitter struct {
+	url string
+}
+
+func NewUnavailableIndexerEmitter(url string) *UnavailableIndexerEmitter {
+	return &UnavailableIndexerEmitter{
+		url: url,
+	}
+}
+
+func (e *UnavailableIndexerEmitter) Describe() *prometheus.Desc {
+	return prometheus.NewDesc(
+		"prowlarr_indexer_unavailable",
+		"Indexers marked unavailable due to repeated errors",
+		[]string{"indexer"},
+		prometheus.Labels{"url": e.url},
+	)
+}
+
+func (e *UnavailableIndexerEmitter) Emit(msg model.SystemHealthMessage) []prometheus.Metric {
+	ret := []prometheus.Metric{}
+	if msg.Source == "IndexerStatusCheck" || msg.Source == "IndexerLongTermStatusCheck" {
+		parts := strings.Split(msg.Message, ":")
+		svrs := parts[len(parts)-1]
+		for _, svr := range strings.Split(svrs, ",") {
+			ret = append(ret, prometheus.MustNewConstMetric(
+				e.Describe(),
+				prometheus.GaugeValue,
+				1,
+				strings.TrimSpace(svr),
+			))
+		}
+	}
+	return ret
+}
+
 type prowlarrCollector struct {
 	config                           *config.ArrConfig  // App configuration
 	indexerStatCache                 indexerStatCache   // Cache of indexer stats
diff --git a/internal/arr/model/shared.go b/internal/arr/model/shared.go
index 859c356..fbc4f2a 100644
--- a/internal/arr/model/shared.go
+++ b/internal/arr/model/shared.go
@@ -42,8 +42,10 @@ type History struct {
 	TotalRecords int `json:"totalRecords"`
 }
 
+type SystemHealth []SystemHealthMessage
+
 // SystemHealth - Stores struct of JSON response
-type SystemHealth []struct {
+type SystemHealthMessage struct {
 	Source  string `json:"source"`
 	Type    string `json:"type"`
 	Message string `json:"message"`
diff --git a/internal/commands/arr.go b/internal/commands/arr.go
index e0a2da8..fd306de 100644
--- a/internal/commands/arr.go
+++ b/internal/commands/arr.go
@@ -166,7 +166,8 @@ var prowlarrCmd = &cobra.Command{
 				collector.NewProwlarrCollector(c),
 				collector.NewHistoryCollector(c),
 				collector.NewSystemStatusCollector(c),
-				collector.NewSystemHealthCollector(c),
+				collector.NewSystemHealthCollector(c,
+					collector.NewUnavailableIndexerEmitter(c.URL)),
 			)
 		})
 		return nil
diff --git a/internal/sabnzbd/collector/collector.go b/internal/sabnzbd/collector/collector.go
index 34201f9..4ea5460 100644
--- a/internal/sabnzbd/collector/collector.go
+++ b/internal/sabnzbd/collector/collector.go
@@ -20,139 +20,139 @@ var (
 	downloadedBytes = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "downloaded_bytes"),
 		"Total Bytes Downloaded by SABnzbd",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	serverDownloadedBytes = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "server_downloaded_bytes"),
 		"Total Bytes Downloaded from UseNet Server",
-		[]string{"target", "server"},
+		[]string{"url", "server"},
 		nil,
 	)
 	serverArticlesTotal = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "server_articles_total"),
 		"Total Articles Attempted to download from UseNet Server",
-		[]string{"target", "server"},
+		[]string{"url", "server"},
 		nil,
 	)
 	serverArticlesSuccess = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "server_articles_success"),
 		"Total Articles Successfully downloaded from UseNet Server",
-		[]string{"target", "server"},
+		[]string{"url", "server"},
 		nil,
 	)
 	info = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "info"),
 		"Info about the target SabnzbD instance",
-		[]string{"target", "version", "status"},
+		[]string{"url", "version", "status"},
 		nil,
 	)
 	paused = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "paused"),
 		"Is the target SabnzbD instance paused",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	pausedAll = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "paused_all"),
 		"Are all the target SabnzbD instance's queues paused",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	pauseDuration = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "pause_duration_seconds"),
 		"Duration until the SabnzbD instance is unpaused",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	diskUsed = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "disk_used_bytes"),
 		"Used Bytes Used on the SabnzbD instance's disk",
-		[]string{"target", "folder"},
+		[]string{"url", "folder"},
 		nil,
 	)
 	diskTotal = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "disk_total_bytes"),
 		"Total Bytes on the SabnzbD instance's disk",
-		[]string{"target", "folder"},
+		[]string{"url", "folder"},
 		nil,
 	)
 	remainingQuota = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "remaining_quota_bytes"),
 		"Total Bytes Left in the SabnzbD instance's quota",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	quota = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "quota_bytes"),
 		"Total Bytes in the SabnzbD instance's quota",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	cachedArticles = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "article_cache_articles"),
 		"Total Articles Cached in the SabnzbD instance",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	cachedBytes = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "article_cache_bytes"),
 		"Total Bytes Cached in the SabnzbD instance Article Cache",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	speed = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "speed_bps"),
 		"Total Bytes Downloaded per Second by the SabnzbD instance",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	bytesRemaining = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "remaining_bytes"),
 		"Total Bytes Remaining to Download by the SabnzbD instance",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	bytesTotal = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "total_bytes"),
 		"Total Bytes in queue to Download by the SabnzbD instance",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	queueLength = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "queue_length"),
 		"Total Number of Items in the SabnzbD instance's queue",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	status = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "status"),
 		"Status of the SabnzbD instance's queue (0=Unknown, 1=Idle, 2=Paused, 3=Downloading)",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	timeEstimate = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "time_estimate_seconds"),
 		"Estimated Time Remaining to Download by the SabnzbD instance",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	warnings = prometheus.NewDesc(
-		prometheus.BuildFQName(METRIC_PREFIX, "", "warnings"),
+		prometheus.BuildFQName(METRIC_PREFIX, "", "queue_warnings"),
 		"Total Warnings in the SabnzbD instance's queue",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	queueQueryDuration = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "queue_query_duration_seconds"),
 		"Duration querying the queue endpoint of SabnzbD",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 	serverStatsQueryDuration = prometheus.NewDesc(
 		prometheus.BuildFQName(METRIC_PREFIX, "", "server_stats_query_duration_seconds"),
 		"Duration querying the server_stats endpoint of SabnzbD",
-		[]string{"target"},
+		[]string{"url"},
 		nil,
 	)
 )


Test patch:
diff --git a/internal/arr/collector/prowlarr_test.go b/internal/arr/collector/prowlarr_test.go
new file mode 100644
index 0000000..cf8532e
--- /dev/null
+++ b/internal/arr/collector/prowlarr_test.go
@@ -0,0 +1,60 @@
+package collector
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/onedr0p/exportarr/internal/arr/model"
+	"github.com/prometheus/client_golang/prometheus"
+	"github.com/prometheus/client_golang/prometheus/testutil"
+	"github.com/stretchr/testify/require"
+)
+
+type testCollector struct {
+	emitter ExtraHealthMetricEmitter
+	msg     model.SystemHealthMessage
+}
+
+func (c *testCollector) Describe(ch chan<- *prometheus.Desc) {
+	ch <- c.emitter.Describe()
+}
+
+func (c *testCollector) Collect(ch chan<- prometheus.Metric) {
+	for _, metric := range c.emitter.Emit(c.msg) {
+		ch <- metric
+	}
+}
+
+func TestUnavailableIndexerEmitter(t *testing.T) {
+	emitter := UnavailableIndexerEmitter{
+		url: "http://localhost:9117",
+	}
+
+	require := require.New(t)
+	require.NotNil(emitter.Describe())
+
+	msg := model.SystemHealthMessage{
+		Source:  "IndexerLongTermStatusCheck",
+		Type:    "warning",
+		WikiURL: "https://wiki.servarr.com/prowlarr/system#indexers-are-unavailable-due-to-failures",
+		Message: "Indexers unavailable due to failures for more than 6 hours: Server1, ServerTwo, ServerTHREE, Server.four",
+	}
+	metrics := emitter.Emit(msg)
+	require.Len(metrics, 4)
+
+	testCol := &testCollector{
+		emitter: &emitter,
+		msg:     msg,
+	}
+
+	expected := strings.NewReader(
+		`# HELP prowlarr_indexer_unavailable Indexers marked unavailable due to repeated errors
+		# TYPE prowlarr_indexer_unavailable gauge
+		prowlarr_indexer_unavailable{indexer="Server.four",url="http://localhost:9117"} 1
+		prowlarr_indexer_unavailable{indexer="Server1",url="http://localhost:9117"} 1
+		prowlarr_indexer_unavailable{indexer="ServerTHREE",url="http://localhost:9117"} 1
+		prowlarr_indexer_unavailable{indexer="ServerTwo",url="http://localhost:9117"} 1
+		`)
+	err := testutil.CollectAndCompare(testCol, expected)
+	require.NoError(err)
+}
diff --git a/internal/sabnzbd/test_fixtures/expected_metrics.txt b/internal/sabnzbd/test_fixtures/expected_metrics.txt
index fc230e4..79a6f8e 100644
--- a/internal/sabnzbd/test_fixtures/expected_metrics.txt
+++ b/internal/sabnzbd/test_fixtures/expected_metrics.txt
@@ -1,68 +1,68 @@
 # HELP sabnzbd_article_cache_articles Total Articles Cached in the SabnzbD instance
 # TYPE sabnzbd_article_cache_articles gauge
-sabnzbd_article_cache_articles{target="http://127.0.0.1:39965"} 0
+sabnzbd_article_cache_articles{url="http://127.0.0.1:39965"} 0
 # HELP sabnzbd_article_cache_bytes Total Bytes Cached in the SabnzbD instance Article Cache
 # TYPE sabnzbd_article_cache_bytes gauge
-sabnzbd_article_cache_bytes{target="http://127.0.0.1:39965"} 0
+sabnzbd_article_cache_bytes{url="http://127.0.0.1:39965"} 0
 # HELP sabnzbd_disk_total_bytes Total Bytes on the SabnzbD instance's disk
 # TYPE sabnzbd_disk_total_bytes gauge
-sabnzbd_disk_total_bytes{folder="complete",target="http://127.0.0.1:39965"} 4.6051713089536e+13
-sabnzbd_disk_total_bytes{folder="download",target="http://127.0.0.1:39965"} 4.6050639347712e+13
+sabnzbd_disk_total_bytes{folder="complete",url="http://127.0.0.1:39965"} 4.6051713089536e+13
+sabnzbd_disk_total_bytes{folder="download",url="http://127.0.0.1:39965"} 4.6050639347712e+13
 # HELP sabnzbd_disk_used_bytes Used Bytes Used on the SabnzbD instance's disk
 # TYPE sabnzbd_disk_used_bytes gauge
-sabnzbd_disk_used_bytes{folder="complete",target="http://127.0.0.1:39965"} 8.771826456985602e+12
-sabnzbd_disk_used_bytes{folder="download",target="http://127.0.0.1:39965"} 8.712770656665602e+12
+sabnzbd_disk_used_bytes{folder="complete",url="http://127.0.0.1:39965"} 8.771826456985602e+12
+sabnzbd_disk_used_bytes{folder="download",url="http://127.0.0.1:39965"} 8.712770656665602e+12
 # HELP sabnzbd_downloaded_bytes Total Bytes Downloaded by SABnzbd
 # TYPE sabnzbd_downloaded_bytes counter
-sabnzbd_downloaded_bytes{target="http://127.0.0.1:39965"} 5.869995742788e+12
+sabnzbd_downloaded_bytes{url="http://127.0.0.1:39965"} 5.869995742788e+12
 # HELP sabnzbd_info Info about the target SabnzbD instance
 # TYPE sabnzbd_info gauge
-sabnzbd_info{status="Downloading",target="http://127.0.0.1:39965",version="3.7.2"} 1
+sabnzbd_info{status="Downloading",url="http://127.0.0.1:39965",version="3.7.2"} 1
 # HELP sabnzbd_pause_duration_seconds Duration until the SabnzbD instance is unpaused
 # TYPE sabnzbd_pause_duration_seconds gauge
-sabnzbd_pause_duration_seconds{target="http://127.0.0.1:39965"} 0
+sabnzbd_pause_duration_seconds{url="http://127.0.0.1:39965"} 0
 # HELP sabnzbd_paused Is the target SabnzbD instance paused
 # TYPE sabnzbd_paused gauge
-sabnzbd_paused{target="http://127.0.0.1:39965"} 0
+sabnzbd_paused{url="http://127.0.0.1:39965"} 0
 # HELP sabnzbd_paused_all Are all the target SabnzbD instance's queues paused
 # TYPE sabnzbd_paused_all gauge
-sabnzbd_paused_all{target="http://127.0.0.1:39965"} 0
+sabnzbd_paused_all{url="http://127.0.0.1:39965"} 0
 # HELP sabnzbd_queue_length Total Number of Items in the SabnzbD instance's queue
 # TYPE sabnzbd_queue_length gauge
-sabnzbd_queue_length{target="http://127.0.0.1:39965"} 2
+sabnzbd_queue_length{url="http://127.0.0.1:39965"} 2
 # HELP sabnzbd_quota_bytes Total Bytes in the SabnzbD instance's quota
 # TYPE sabnzbd_quota_bytes gauge
-sabnzbd_quota_bytes{target="http://127.0.0.1:39965"} 1.07911053312e+12
+sabnzbd_quota_bytes{url="http://127.0.0.1:39965"} 1.07911053312e+12
 # HELP sabnzbd_remaining_bytes Total Bytes Remaining to Download by the SabnzbD instance
 # TYPE sabnzbd_remaining_bytes gauge
-sabnzbd_remaining_bytes{target="http://127.0.0.1:39965"} 3.21070825472e+09
+sabnzbd_remaining_bytes{url="http://127.0.0.1:39965"} 3.21070825472e+09
 # HELP sabnzbd_remaining_quota_bytes Total Bytes Left in the SabnzbD instance's quota
 # TYPE sabnzbd_remaining_quota_bytes gauge
-sabnzbd_remaining_quota_bytes{target="http://127.0.0.1:39965"} 1.073741824e+12
+sabnzbd_remaining_quota_bytes{url="http://127.0.0.1:39965"} 1.073741824e+12
 # HELP sabnzbd_server_articles_success Total Articles Successfully downloaded from UseNet Server
 # TYPE sabnzbd_server_articles_success counter
-sabnzbd_server_articles_success{server="server1.example.tld",target="http://127.0.0.1:39965"} 12618
-sabnzbd_server_articles_success{server="server2.example.tld",target="http://127.0.0.1:39965"} 9869
+sabnzbd_server_articles_success{server="server1.example.tld",url="http://127.0.0.1:39965"} 12618
+sabnzbd_server_articles_success{server="server2.example.tld",url="http://127.0.0.1:39965"} 9869
 # HELP sabnzbd_server_articles_total Total Articles Attempted to download from UseNet Server
 # TYPE sabnzbd_server_articles_total counter
-sabnzbd_server_articles_total{server="server1.example.tld",target="http://127.0.0.1:39965"} 12622
-sabnzbd_server_articles_total{server="server2.example.tld",target="http://127.0.0.1:39965"} 9869
+sabnzbd_server_articles_total{server="server1.example.tld",url="http://127.0.0.1:39965"} 12622
+sabnzbd_server_articles_total{server="server2.example.tld",url="http://127.0.0.1:39965"} 9869
 # HELP sabnzbd_server_downloaded_bytes Total Bytes Downloaded from UseNet Server
 # TYPE sabnzbd_server_downloaded_bytes counter
-sabnzbd_server_downloaded_bytes{server="server1.example.tld",target="http://127.0.0.1:39965"} 4.8069637e+07
-sabnzbd_server_downloaded_bytes{server="server2.example.tld",target="http://127.0.0.1:39965"} 1.10895796e+08
+sabnzbd_server_downloaded_bytes{server="server1.example.tld",url="http://127.0.0.1:39965"} 4.8069637e+07
+sabnzbd_server_downloaded_bytes{server="server2.example.tld",url="http://127.0.0.1:39965"} 1.10895796e+08
 # HELP sabnzbd_speed_bps Total Bytes Downloaded per Second by the SabnzbD instance
 # TYPE sabnzbd_speed_bps gauge
-sabnzbd_speed_bps{target="http://127.0.0.1:39965"} 358.4
+sabnzbd_speed_bps{url="http://127.0.0.1:39965"} 358.4
 # HELP sabnzbd_status Status of the SabnzbD instance's queue (0=Unknown, 1=Idle, 2=Paused, 3=Downloading)
 # TYPE sabnzbd_status gauge
-sabnzbd_status{target="http://127.0.0.1:39965"} 3
+sabnzbd_status{url="http://127.0.0.1:39965"} 3
 # HELP sabnzbd_time_estimate_seconds Estimated Time Remaining to Download by the SabnzbD instance
 # TYPE sabnzbd_time_estimate_seconds gauge
-sabnzbd_time_estimate_seconds{target="http://127.0.0.1:39965"} 8.985543e+06
+sabnzbd_time_estimate_seconds{url="http://127.0.0.1:39965"} 8.985543e+06
 # HELP sabnzbd_total_bytes Total Bytes in queue to Download by the SabnzbD instance
 # TYPE sabnzbd_total_bytes gauge
-sabnzbd_total_bytes{target="http://127.0.0.1:39965"} 3.21175683072e+09
-# HELP sabnzbd_warnings Total Warnings in the SabnzbD instance's queue
-# TYPE sabnzbd_warnings gauge
-sabnzbd_warnings{target="http://127.0.0.1:39965"} 0
+sabnzbd_total_bytes{url="http://127.0.0.1:39965"} 3.21175683072e+09
+# HELP sabnzbd_queue_warnings Total Warnings in the SabnzbD instance's queue
+# TYPE sabnzbd_queue_warnings gauge
+sabnzbd_queue_warnings{url="http://127.0.0.1:39965"} 0