feat(metrics): add metrics server (#1930)

* feat(metrics): add metrics server

* chore: update license headers

* feat(metrics): add optional basic auth

* feat(metrics): add go and process collectors

---------

Co-authored-by: ze0s <43699394+zze0s@users.noreply.github.com>
Co-authored-by: ze0s <ze0s@riseup.net>
This commit is contained in:
Antoine 2025-01-25 17:58:18 +01:00 committed by GitHub
parent 0d5902c8f6
commit 3f8bc0140c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
16 changed files with 1191 additions and 83 deletions

View file

@ -0,0 +1,89 @@
// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package collector
import (
"context"
"github.com/autobrr/autobrr/internal/feed"
"github.com/prometheus/client_golang/prometheus"
)
type feedCollector struct {
feedService feed.Service
totalCount *prometheus.Desc
enabledCount *prometheus.Desc
LastRunTimestamp *prometheus.Desc
NextRunTimestamp *prometheus.Desc
errorMetric *prometheus.Desc
}
func (collector *feedCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.totalCount
ch <- collector.enabledCount
ch <- collector.LastRunTimestamp
ch <- collector.NextRunTimestamp
ch <- collector.errorMetric
}
func (collector *feedCollector) Collect(ch chan<- prometheus.Metric) {
feeds, err := collector.feedService.Find(context.TODO())
if err != nil {
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
enabled := 0
for _, f := range feeds {
if f.Enabled {
enabled++
}
if !f.LastRun.IsZero() {
ch <- prometheus.MustNewConstMetric(collector.LastRunTimestamp, prometheus.GaugeValue, float64(int(f.LastRun.Unix())), f.Name)
}
if !f.NextRun.IsZero() {
ch <- prometheus.MustNewConstMetric(collector.NextRunTimestamp, prometheus.GaugeValue, float64(int(f.NextRun.Unix())), f.Name)
}
}
ch <- prometheus.MustNewConstMetric(collector.totalCount, prometheus.GaugeValue, float64(len(feeds)))
ch <- prometheus.MustNewConstMetric(collector.enabledCount, prometheus.GaugeValue, float64(enabled))
}
func NewFeedCollector(feedService feed.Service) *feedCollector {
return &feedCollector{
feedService: feedService,
totalCount: prometheus.NewDesc(
"autobrr_feed_total",
"Number of feeds",
nil,
nil,
),
enabledCount: prometheus.NewDesc(
"autobrr_feed_enabled_total",
"Number of enabled feeds",
nil,
nil,
),
LastRunTimestamp: prometheus.NewDesc(
"autobrr_feed_last_run_timestamp_seconds",
"The timestamp of the last feed run",
[]string{"feed"},
nil,
),
NextRunTimestamp: prometheus.NewDesc(
"autobrr_feed_next_run_timestamp_seconds",
"The timestamp of the next feed run",
[]string{"feed"},
nil,
),
errorMetric: prometheus.NewDesc(
"autobrr_feed_collector_error",
"Error while collecting feed metrics",
nil,
nil,
),
}
}

View file

@ -0,0 +1,66 @@
// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package collector
import (
"context"
"github.com/autobrr/autobrr/internal/filter"
"github.com/prometheus/client_golang/prometheus"
)
type filterCollector struct {
filterService filter.Service
totalCount *prometheus.Desc
enabledCount *prometheus.Desc
errorMetric *prometheus.Desc
}
func (collector *filterCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.totalCount
ch <- collector.enabledCount
ch <- collector.errorMetric
}
func (collector *filterCollector) Collect(ch chan<- prometheus.Metric) {
lists, err := collector.filterService.ListFilters(context.TODO())
if err != nil {
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
enabled := 0
for _, f := range lists {
if f.Enabled {
enabled++
}
}
ch <- prometheus.MustNewConstMetric(collector.totalCount, prometheus.GaugeValue, float64(len(lists)))
ch <- prometheus.MustNewConstMetric(collector.enabledCount, prometheus.GaugeValue, float64(enabled))
}
func NewFilterCollector(filterService filter.Service) *filterCollector {
return &filterCollector{
filterService: filterService,
totalCount: prometheus.NewDesc(
"autobrr_filter_total",
"Number of filters",
nil,
nil,
),
enabledCount: prometheus.NewDesc(
"autobrr_filter_enabled_total",
"Number of enabled filters",
nil,
nil,
),
errorMetric: prometheus.NewDesc(
"autobrr_filter_collector_error",
"Error while collecting filter metrics",
nil,
nil,
),
}
}

View file

@ -0,0 +1,140 @@
// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package collector
import (
"context"
"github.com/autobrr/autobrr/internal/irc"
"github.com/prometheus/client_golang/prometheus"
)
type ircCollector struct {
ircService irc.Service
totalCount *prometheus.Desc
enabledCount *prometheus.Desc
connectedCount *prometheus.Desc
healthyCount *prometheus.Desc
channelCount *prometheus.Desc
channelEnabledCount *prometheus.Desc
channelMonitoringCount *prometheus.Desc
channelLastAnnouncedTimestamp *prometheus.Desc
errorMetric *prometheus.Desc
}
func (collector *ircCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.totalCount
ch <- collector.enabledCount
ch <- collector.connectedCount
ch <- collector.channelCount
ch <- collector.channelEnabledCount
ch <- collector.channelMonitoringCount
ch <- collector.channelLastAnnouncedTimestamp
ch <- collector.errorMetric
}
func (collector *ircCollector) Collect(ch chan<- prometheus.Metric) {
networks, err := collector.ircService.GetNetworksWithHealth(context.TODO())
if err != nil {
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
enabled := 0
healthy := 0
connected := 0
for _, n := range networks {
if n.Enabled {
enabled++
}
if n.Connected {
connected++
}
if n.Healthy {
healthy++
}
channelsEnabled := 0
channelsMonitoring := 0
for _, c := range n.Channels {
if c.Enabled {
channelsEnabled++
}
if c.Monitoring {
channelsMonitoring++
}
if !c.LastAnnounce.IsZero() {
ch <- prometheus.MustNewConstMetric(collector.channelLastAnnouncedTimestamp, prometheus.GaugeValue, float64(int(c.LastAnnounce.Unix())), n.Name, c.Name)
}
}
ch <- prometheus.MustNewConstMetric(collector.channelCount, prometheus.GaugeValue, float64(len(n.Channels)), n.Name)
ch <- prometheus.MustNewConstMetric(collector.channelEnabledCount, prometheus.GaugeValue, float64(channelsEnabled), n.Name)
ch <- prometheus.MustNewConstMetric(collector.channelMonitoringCount, prometheus.GaugeValue, float64(channelsMonitoring), n.Name)
}
ch <- prometheus.MustNewConstMetric(collector.totalCount, prometheus.GaugeValue, float64(len(networks)))
ch <- prometheus.MustNewConstMetric(collector.enabledCount, prometheus.GaugeValue, float64(enabled))
ch <- prometheus.MustNewConstMetric(collector.connectedCount, prometheus.GaugeValue, float64(connected))
ch <- prometheus.MustNewConstMetric(collector.healthyCount, prometheus.GaugeValue, float64(healthy))
}
func NewIRCCollector(ircService irc.Service) *ircCollector {
return &ircCollector{
ircService: ircService,
totalCount: prometheus.NewDesc(
"autobrr_irc_total",
"Number of IRC networks",
nil,
nil,
),
enabledCount: prometheus.NewDesc(
"autobrr_irc_enabled_total",
"Number of enabled IRC networks",
nil,
nil,
),
connectedCount: prometheus.NewDesc(
"autobrr_irc_connected_total",
"Number of connected IRC networks",
nil,
nil,
),
healthyCount: prometheus.NewDesc(
"autobrr_irc_healthy_total",
"Number of healthy IRC networks",
nil,
nil,
),
channelCount: prometheus.NewDesc(
"autobrr_irc_channel_total",
"Number of IRC channel",
[]string{"network"},
nil,
),
channelEnabledCount: prometheus.NewDesc(
"autobrr_irc_channel_enabled_total",
"Number of enabled IRC channel",
[]string{"network"},
nil,
),
channelMonitoringCount: prometheus.NewDesc(
"autobrr_irc_channel_monitored_total",
"Number of IRC channel monitored",
[]string{"network"},
nil,
),
channelLastAnnouncedTimestamp: prometheus.NewDesc(
"autobrr_irc_channel_last_announced_timestamp_seconds",
"The timestamp of the last announced release",
[]string{"network", "channel"},
nil,
),
errorMetric: prometheus.NewDesc(
"autobrr_irc_collector_error",
"Error while collecting irc metrics",
nil,
nil,
),
}
}

View file

@ -0,0 +1,78 @@
// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package collector
import (
"context"
"github.com/autobrr/autobrr/internal/list"
"github.com/prometheus/client_golang/prometheus"
)
type listCollector struct {
listService list.Service
totalCount *prometheus.Desc
enabledCount *prometheus.Desc
LastRefreshTimestamp *prometheus.Desc
errorMetric *prometheus.Desc
}
func (collector *listCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.totalCount
ch <- collector.enabledCount
ch <- collector.LastRefreshTimestamp
ch <- collector.errorMetric
}
func (collector *listCollector) Collect(ch chan<- prometheus.Metric) {
lists, err := collector.listService.List(context.TODO())
if err != nil {
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
enabled := 0
for _, l := range lists {
if l.Enabled {
enabled++
}
if !l.LastRefreshTime.IsZero() {
ch <- prometheus.MustNewConstMetric(collector.LastRefreshTimestamp, prometheus.GaugeValue, float64(int(l.LastRefreshTime.Unix())), l.Name)
}
}
ch <- prometheus.MustNewConstMetric(collector.totalCount, prometheus.GaugeValue, float64(len(lists)))
ch <- prometheus.MustNewConstMetric(collector.enabledCount, prometheus.GaugeValue, float64(enabled))
}
func NewListCollector(listService list.Service) *listCollector {
return &listCollector{
listService: listService,
totalCount: prometheus.NewDesc(
"autobrr_list_total",
"Number of lists",
nil,
nil,
),
enabledCount: prometheus.NewDesc(
"autobrr_list_enabled_total",
"Number of enabled lists",
nil,
nil,
),
LastRefreshTimestamp: prometheus.NewDesc(
"autobrr_list_last_refresh_timestamp_seconds",
"The timestamp of the last list run",
[]string{"list"},
nil,
),
errorMetric: prometheus.NewDesc(
"autobrr_list_collector_error",
"Error while collecting list metrics",
nil,
nil,
),
}
}

View file

@ -0,0 +1,96 @@
// Copyright (c) 2021 - 2025, Ludvig Lundgren and the autobrr contributors.
// SPDX-License-Identifier: GPL-2.0-or-later
package collector
import (
"context"
"github.com/autobrr/autobrr/internal/release"
"github.com/prometheus/client_golang/prometheus"
)
type releaseCollector struct {
releaseService release.Service
totalCount *prometheus.Desc
filteredCount *prometheus.Desc
filterRejectedCount *prometheus.Desc
pushApprovedCount *prometheus.Desc
pushRejectedCount *prometheus.Desc
pushErrorCount *prometheus.Desc
errorMetric *prometheus.Desc
}
func (collector *releaseCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- collector.totalCount
ch <- collector.filteredCount
ch <- collector.filterRejectedCount
ch <- collector.pushApprovedCount
ch <- collector.pushRejectedCount
ch <- collector.pushErrorCount
ch <- collector.errorMetric
}
func (collector *releaseCollector) Collect(ch chan<- prometheus.Metric) {
stats, err := collector.releaseService.Stats(context.TODO())
if err != nil {
ch <- prometheus.NewInvalidMetric(collector.errorMetric, err)
return
}
ch <- prometheus.MustNewConstMetric(collector.totalCount, prometheus.GaugeValue, float64(stats.TotalCount))
ch <- prometheus.MustNewConstMetric(collector.filteredCount, prometheus.GaugeValue, float64(stats.FilteredCount))
ch <- prometheus.MustNewConstMetric(collector.filterRejectedCount, prometheus.GaugeValue, float64(stats.FilterRejectedCount))
ch <- prometheus.MustNewConstMetric(collector.pushApprovedCount, prometheus.GaugeValue, float64(stats.PushApprovedCount))
ch <- prometheus.MustNewConstMetric(collector.pushRejectedCount, prometheus.GaugeValue, float64(stats.PushRejectedCount))
ch <- prometheus.MustNewConstMetric(collector.pushErrorCount, prometheus.GaugeValue, float64(stats.PushErrorCount))
}
func NewReleaseCollector(releaseService release.Service) *releaseCollector {
return &releaseCollector{
releaseService: releaseService,
totalCount: prometheus.NewDesc(
"autobrr_release_total",
"Number of releases",
nil,
nil,
),
filteredCount: prometheus.NewDesc(
"autobrr_release_filtered_total",
"Number of releases filtered",
nil,
nil,
),
filterRejectedCount: prometheus.NewDesc(
"autobrr_release_filter_rejected_total",
"Number of releases that got rejected because of a filter",
nil,
nil,
),
pushApprovedCount: prometheus.NewDesc(
"autobrr_release_push_approved_total",
"Number of releases push approved",
nil,
nil,
),
pushRejectedCount: prometheus.NewDesc(
"autobrr_release_push_rejected_total",
"Number of releases push rejected",
nil,
nil,
),
pushErrorCount: prometheus.NewDesc(
"autobrr_release_push_error_total",
"Number of releases push errored",
nil,
nil,
),
errorMetric: prometheus.NewDesc(
"autobrr_release_collector_error",
"Error while collecting release metrics",
nil,
nil,
),
}
}