From cce13957ccc18b9fa45ce2c282792d73489fa063 Mon Sep 17 00:00:00 2001 From: gavinmcfall Date: Tue, 9 Dec 2025 14:42:20 +1300 Subject: [PATCH] feat(sonarr): add series tag metrics Add support for collecting series counts by tag using the /api/v3/tag/detail endpoint, matching the existing radarr_movie_tag_total functionality. New metric: sonarr_series_tag_total{tag="..."} - counts series per tag --- internal/arr/collector/sonarr.go | 22 +++++++++++++ internal/arr/model/sonarr.go | 8 +++++ .../test_fixtures/sonarr/expected_metrics.txt | 4 +++ .../sonarr/expected_metrics_extended.txt | 4 +++ .../test_fixtures/sonarr/v3_tag_detail.json | 33 +++++++++++++++++++ 5 files changed, 71 insertions(+) create mode 100644 internal/arr/test_fixtures/sonarr/v3_tag_detail.json diff --git a/internal/arr/collector/sonarr.go b/internal/arr/collector/sonarr.go index 0e38102..37423c0 100644 --- a/internal/arr/collector/sonarr.go +++ b/internal/arr/collector/sonarr.go @@ -19,6 +19,7 @@ type sonarrCollector struct { seriesMonitoredMetric *prometheus.Desc // Total number of monitored series seriesUnmonitoredMetric *prometheus.Desc // Total number of unmonitored series seriesFileSizeMetric *prometheus.Desc // Total fizesize of all series in bytes + seriesTagsMetric *prometheus.Desc // Total number of series by tag seasonMetric *prometheus.Desc // Total number of seasons seasonDownloadedMetric *prometheus.Desc // Total number of downloaded seasons seasonMonitoredMetric *prometheus.Desc // Total number of monitored seasons @@ -66,6 +67,12 @@ func NewSonarrCollector(conf *config.ArrConfig) *sonarrCollector { nil, prometheus.Labels{"url": conf.URL}, ), + seriesTagsMetric: prometheus.NewDesc( + "sonarr_series_tag_total", + "Total number of downloaded series by tag", + []string{"tag"}, + prometheus.Labels{"url": conf.URL}, + ), seasonMetric: prometheus.NewDesc( "sonarr_season_total", "Total number of seasons", @@ -147,6 +154,7 @@ func (collector *sonarrCollector) Describe(ch chan<- *prometheus.Desc) { ch <- collector.seriesMonitoredMetric ch <- collector.seriesUnmonitoredMetric ch <- collector.seriesFileSizeMetric + ch <- collector.seriesTagsMetric ch <- collector.seasonMetric ch <- collector.seasonDownloadedMetric ch <- collector.seasonMonitoredMetric @@ -305,11 +313,25 @@ func (collector *sonarrCollector) Collect(ch chan<- prometheus.Metric) { return } + // Get tag details for series + tagObjects := model.TagSeries{} + if err := c.DoRequest("tag/detail", &tagObjects); err != nil { + log.Errorw("Error getting tags", + "error", err) + ch <- prometheus.NewInvalidMetric(collector.errorMetric, err) + return + } + ch <- prometheus.MustNewConstMetric(collector.seriesMetric, prometheus.GaugeValue, float64(len(series))) ch <- prometheus.MustNewConstMetric(collector.seriesDownloadedMetric, prometheus.GaugeValue, float64(seriesDownloaded)) ch <- prometheus.MustNewConstMetric(collector.seriesMonitoredMetric, prometheus.GaugeValue, float64(seriesMonitored)) ch <- prometheus.MustNewConstMetric(collector.seriesUnmonitoredMetric, prometheus.GaugeValue, float64(seriesUnmonitored)) ch <- prometheus.MustNewConstMetric(collector.seriesFileSizeMetric, prometheus.GaugeValue, float64(seriesFileSize)) + for _, tag := range tagObjects { + ch <- prometheus.MustNewConstMetric(collector.seriesTagsMetric, prometheus.GaugeValue, float64(len(tag.SeriesIds)), + tag.Label, + ) + } ch <- prometheus.MustNewConstMetric(collector.seasonMetric, prometheus.GaugeValue, float64(seasons)) ch <- prometheus.MustNewConstMetric(collector.seasonDownloadedMetric, prometheus.GaugeValue, float64(seasonsDownloaded)) ch <- prometheus.MustNewConstMetric(collector.seasonMonitoredMetric, prometheus.GaugeValue, float64(seasonsMonitored)) diff --git a/internal/arr/model/sonarr.go b/internal/arr/model/sonarr.go index faf166c..4b3f700 100644 --- a/internal/arr/model/sonarr.go +++ b/internal/arr/model/sonarr.go @@ -70,3 +70,11 @@ type Episode []struct { HasFile bool `json:"hasFile"` Monitored bool `json:"monitored"` } + +// TagSeries - Stores struct of JSON response for tag details +// https://sonarr.tv/docs/api/#/TagDetails/get_api_v3_tag_detail +type TagSeries []struct { + ID int `json:"id"` + Label string `json:"label"` + SeriesIds []int `json:"seriesIds"` +} diff --git a/internal/arr/test_fixtures/sonarr/expected_metrics.txt b/internal/arr/test_fixtures/sonarr/expected_metrics.txt index c716ff0..ceffd3d 100644 --- a/internal/arr/test_fixtures/sonarr/expected_metrics.txt +++ b/internal/arr/test_fixtures/sonarr/expected_metrics.txt @@ -28,6 +28,10 @@ sonarr_series_downloaded_total{url="SOMEURL"} 5 # HELP sonarr_series_filesize_bytes Total fizesize of all series in bytes # TYPE sonarr_series_filesize_bytes gauge sonarr_series_filesize_bytes{url="SOMEURL"} 7.91293980833e+11 +# HELP sonarr_series_tag_total Total number of series by tag +# TYPE sonarr_series_tag_total gauge +sonarr_series_tag_total{tag="comedy",url="SOMEURL"} 3 +sonarr_series_tag_total{tag="drama",url="SOMEURL"} 2 # HELP sonarr_series_monitored_total Total number of monitored series # TYPE sonarr_series_monitored_total gauge sonarr_series_monitored_total{url="SOMEURL"} 5 diff --git a/internal/arr/test_fixtures/sonarr/expected_metrics_extended.txt b/internal/arr/test_fixtures/sonarr/expected_metrics_extended.txt index e9382e4..baf56b6 100644 --- a/internal/arr/test_fixtures/sonarr/expected_metrics_extended.txt +++ b/internal/arr/test_fixtures/sonarr/expected_metrics_extended.txt @@ -38,6 +38,10 @@ sonarr_series_downloaded_total{url="SOMEURL"} 5 # HELP sonarr_series_filesize_bytes Total fizesize of all series in bytes # TYPE sonarr_series_filesize_bytes gauge sonarr_series_filesize_bytes{url="SOMEURL"} 7.91293980833e+11 +# HELP sonarr_series_tag_total Total number of series by tag +# TYPE sonarr_series_tag_total gauge +sonarr_series_tag_total{tag="comedy",url="SOMEURL"} 3 +sonarr_series_tag_total{tag="drama",url="SOMEURL"} 2 # HELP sonarr_series_monitored_total Total number of monitored series # TYPE sonarr_series_monitored_total gauge sonarr_series_monitored_total{url="SOMEURL"} 5 diff --git a/internal/arr/test_fixtures/sonarr/v3_tag_detail.json b/internal/arr/test_fixtures/sonarr/v3_tag_detail.json new file mode 100644 index 0000000..7f43fab --- /dev/null +++ b/internal/arr/test_fixtures/sonarr/v3_tag_detail.json @@ -0,0 +1,33 @@ +[ + { + "label": "comedy", + "delayProfileIds": [], + "importListIds": [], + "notificationIds": [], + "restrictionIds": [], + "indexerIds": [], + "downloadClientIds": [], + "autoTagIds": [], + "seriesIds": [ + 37, + 21, + 46 + ], + "id": 14 + }, + { + "label": "drama", + "delayProfileIds": [], + "importListIds": [], + "notificationIds": [], + "restrictionIds": [], + "indexerIds": [], + "downloadClientIds": [], + "autoTagIds": [], + "seriesIds": [ + 371, + 464 + ], + "id": 196 + } +]