fix(metrics): write system metrics on start (#3641)
* fix(metrics): write system metrics on start * add broken basic auth test * refactor: simplify Prometheus instantiation Signed-off-by: Deluan <deluan@navidrome.org> * fix: basic authentication Signed-off-by: Deluan <deluan@navidrome.org> * refactor: move magic strings to constants Signed-off-by: Deluan <deluan@navidrome.org> * refactor: simplify prometheus http handler Signed-off-by: Deluan <deluan@navidrome.org> * add artist metadata to aggregrate sql --------- Signed-off-by: Deluan <deluan@navidrome.org> Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
+68
-45
@@ -3,32 +3,59 @@ package metrics
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/chi/v5/middleware"
|
||||
"github.com/navidrome/navidrome/conf"
|
||||
"github.com/navidrome/navidrome/consts"
|
||||
"github.com/navidrome/navidrome/log"
|
||||
"github.com/navidrome/navidrome/model"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
||||
)
|
||||
|
||||
func WriteInitialMetrics() {
|
||||
getPrometheusMetrics().versionInfo.With(prometheus.Labels{"version": consts.Version}).Set(1)
|
||||
type Metrics interface {
|
||||
WriteInitialMetrics(ctx context.Context)
|
||||
WriteAfterScanMetrics(ctx context.Context, success bool)
|
||||
GetHandler() http.Handler
|
||||
}
|
||||
|
||||
func WriteAfterScanMetrics(ctx context.Context, dataStore model.DataStore, success bool) {
|
||||
processSqlAggregateMetrics(ctx, dataStore, getPrometheusMetrics().dbTotal)
|
||||
type metrics struct {
|
||||
ds model.DataStore
|
||||
}
|
||||
|
||||
func NewPrometheusInstance(ds model.DataStore) Metrics {
|
||||
return &metrics{ds: ds}
|
||||
}
|
||||
|
||||
func (m *metrics) WriteInitialMetrics(ctx context.Context) {
|
||||
getPrometheusMetrics().versionInfo.With(prometheus.Labels{"version": consts.Version}).Set(1)
|
||||
processSqlAggregateMetrics(ctx, m.ds, getPrometheusMetrics().dbTotal)
|
||||
}
|
||||
|
||||
func (m *metrics) WriteAfterScanMetrics(ctx context.Context, success bool) {
|
||||
processSqlAggregateMetrics(ctx, m.ds, getPrometheusMetrics().dbTotal)
|
||||
|
||||
scanLabels := prometheus.Labels{"success": strconv.FormatBool(success)}
|
||||
getPrometheusMetrics().lastMediaScan.With(scanLabels).SetToCurrentTime()
|
||||
getPrometheusMetrics().mediaScansCounter.With(scanLabels).Inc()
|
||||
}
|
||||
|
||||
// Prometheus' metrics requires initialization. But not more than once
|
||||
var (
|
||||
prometheusMetricsInstance *prometheusMetrics
|
||||
prometheusOnce sync.Once
|
||||
)
|
||||
func (m *metrics) GetHandler() http.Handler {
|
||||
r := chi.NewRouter()
|
||||
|
||||
if conf.Server.Prometheus.Password != "" {
|
||||
r.Use(middleware.BasicAuth("metrics", map[string]string{
|
||||
consts.PrometheusAuthUser: conf.Server.Prometheus.Password,
|
||||
}))
|
||||
}
|
||||
r.Handle("/", promhttp.Handler())
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
type prometheusMetrics struct {
|
||||
dbTotal *prometheus.GaugeVec
|
||||
@@ -37,19 +64,9 @@ type prometheusMetrics struct {
|
||||
mediaScansCounter *prometheus.CounterVec
|
||||
}
|
||||
|
||||
func getPrometheusMetrics() *prometheusMetrics {
|
||||
prometheusOnce.Do(func() {
|
||||
var err error
|
||||
prometheusMetricsInstance, err = newPrometheusMetrics()
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create Prometheus metrics instance.", err)
|
||||
}
|
||||
})
|
||||
return prometheusMetricsInstance
|
||||
}
|
||||
|
||||
func newPrometheusMetrics() (*prometheusMetrics, error) {
|
||||
res := &prometheusMetrics{
|
||||
// Prometheus' metrics requires initialization. But not more than once
|
||||
var getPrometheusMetrics = sync.OnceValue(func() *prometheusMetrics {
|
||||
instance := &prometheusMetrics{
|
||||
dbTotal: prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Name: "db_model_totals",
|
||||
@@ -79,42 +96,48 @@ func newPrometheusMetrics() (*prometheusMetrics, error) {
|
||||
[]string{"success"},
|
||||
),
|
||||
}
|
||||
err := prometheus.DefaultRegisterer.Register(instance.dbTotal)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create Prometheus metric instance", fmt.Errorf("unable to register db_model_totals metrics: %w", err))
|
||||
}
|
||||
err = prometheus.DefaultRegisterer.Register(instance.versionInfo)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create Prometheus metric instance", fmt.Errorf("unable to register navidrome_info metrics: %w", err))
|
||||
}
|
||||
err = prometheus.DefaultRegisterer.Register(instance.lastMediaScan)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create Prometheus metric instance", fmt.Errorf("unable to register media_scan_last metrics: %w", err))
|
||||
}
|
||||
err = prometheus.DefaultRegisterer.Register(instance.mediaScansCounter)
|
||||
if err != nil {
|
||||
log.Fatal("Unable to create Prometheus metric instance", fmt.Errorf("unable to register media_scans metrics: %w", err))
|
||||
}
|
||||
return instance
|
||||
})
|
||||
|
||||
err := prometheus.DefaultRegisterer.Register(res.dbTotal)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to register db_model_totals metrics: %w", err)
|
||||
}
|
||||
err = prometheus.DefaultRegisterer.Register(res.versionInfo)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to register navidrome_info metrics: %w", err)
|
||||
}
|
||||
err = prometheus.DefaultRegisterer.Register(res.lastMediaScan)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to register media_scan_last metrics: %w", err)
|
||||
}
|
||||
err = prometheus.DefaultRegisterer.Register(res.mediaScansCounter)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unable to register media_scans metrics: %w", err)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func processSqlAggregateMetrics(ctx context.Context, dataStore model.DataStore, targetGauge *prometheus.GaugeVec) {
|
||||
albumsCount, err := dataStore.Album(ctx).CountAll()
|
||||
func processSqlAggregateMetrics(ctx context.Context, ds model.DataStore, targetGauge *prometheus.GaugeVec) {
|
||||
albumsCount, err := ds.Album(ctx).CountAll()
|
||||
if err != nil {
|
||||
log.Warn("album CountAll error", err)
|
||||
return
|
||||
}
|
||||
targetGauge.With(prometheus.Labels{"model": "album"}).Set(float64(albumsCount))
|
||||
|
||||
songsCount, err := dataStore.MediaFile(ctx).CountAll()
|
||||
artistCount, err := ds.Artist(ctx).CountAll()
|
||||
if err != nil {
|
||||
log.Warn("artist CountAll error", err)
|
||||
return
|
||||
}
|
||||
targetGauge.With(prometheus.Labels{"model": "artist"}).Set(float64(artistCount))
|
||||
|
||||
songsCount, err := ds.MediaFile(ctx).CountAll()
|
||||
if err != nil {
|
||||
log.Warn("media CountAll error", err)
|
||||
return
|
||||
}
|
||||
targetGauge.With(prometheus.Labels{"model": "media"}).Set(float64(songsCount))
|
||||
|
||||
usersCount, err := dataStore.User(ctx).CountAll()
|
||||
usersCount, err := ds.User(ctx).CountAll()
|
||||
if err != nil {
|
||||
log.Warn("user CountAll error", err)
|
||||
return
|
||||
|
||||
Reference in New Issue
Block a user