navidrome/core/metrics.go

124 lines
3.5 KiB
Go

package core
import (
"context"
"fmt"
"strconv"
"sync"
"github.com/navidrome/navidrome/consts"
"github.com/navidrome/navidrome/log"
"github.com/navidrome/navidrome/model"
"github.com/prometheus/client_golang/prometheus"
)
func WriteInitialMetrics() {
getPrometheusMetrics().versionInfo.With(prometheus.Labels{"version": consts.Version}).Set(1)
}
func WriteAfterScanMetrics(ctx context.Context, dataStore model.DataStore, success bool) {
processSqlAggregateMetrics(ctx, dataStore, 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
)
type prometheusMetrics struct {
dbTotal *prometheus.GaugeVec
versionInfo *prometheus.GaugeVec
lastMediaScan *prometheus.GaugeVec
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{
dbTotal: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "db_model_totals",
Help: "Total number of DB items per model",
},
[]string{"model"},
),
versionInfo: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "navidrome_info",
Help: "Information about Navidrome version",
},
[]string{"version"},
),
lastMediaScan: prometheus.NewGaugeVec(
prometheus.GaugeOpts{
Name: "media_scan_last",
Help: "Last media scan timestamp by success",
},
[]string{"success"},
),
mediaScansCounter: prometheus.NewCounterVec(
prometheus.CounterOpts{
Name: "media_scans",
Help: "Total success media scans by success",
},
[]string{"success"},
),
}
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()
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()
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()
if err != nil {
log.Warn("user CountAll error", err)
return
}
targetGauge.With(prometheus.Labels{"model": "user"}).Set(float64(usersCount))
}