mirror of
https://github.com/navidrome/navidrome.git
synced 2025-04-04 21:17:37 +03:00
Base SQL metrics in MetricsWorker (#2002)
* feat: Add metrics worker * refactor: Add todos for useful for metrics methods * feat: Run MetricsWorker is Prometheus is Enabled * refactor: Unused low-level variable was removed in metrics * feat: No worker for metrics, add more * refactor: Unnecessary todo removed * refactor: Remove dead unused constant * Reduce metrics public interface Co-authored-by: Deluan <deluan@navidrome.org>
This commit is contained in:
parent
d31faf5249
commit
457e1fc97b
4 changed files with 129 additions and 0 deletions
|
@ -10,6 +10,7 @@ import (
|
||||||
|
|
||||||
"github.com/navidrome/navidrome/conf"
|
"github.com/navidrome/navidrome/conf"
|
||||||
"github.com/navidrome/navidrome/consts"
|
"github.com/navidrome/navidrome/consts"
|
||||||
|
"github.com/navidrome/navidrome/core"
|
||||||
"github.com/navidrome/navidrome/db"
|
"github.com/navidrome/navidrome/db"
|
||||||
"github.com/navidrome/navidrome/log"
|
"github.com/navidrome/navidrome/log"
|
||||||
"github.com/navidrome/navidrome/resources"
|
"github.com/navidrome/navidrome/resources"
|
||||||
|
@ -91,6 +92,8 @@ func startServer(ctx context.Context) func() error {
|
||||||
a.MountRouter("ListenBrainz Auth", consts.URLPathNativeAPI+"/listenbrainz", CreateListenBrainzRouter())
|
a.MountRouter("ListenBrainz Auth", consts.URLPathNativeAPI+"/listenbrainz", CreateListenBrainzRouter())
|
||||||
}
|
}
|
||||||
if conf.Server.Prometheus.Enabled {
|
if conf.Server.Prometheus.Enabled {
|
||||||
|
// blocking call because takes <1ms but useful if fails
|
||||||
|
core.WriteInitialMetrics()
|
||||||
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, promhttp.Handler())
|
a.MountRouter("Prometheus metrics", conf.Server.Prometheus.MetricsPath, promhttp.Handler())
|
||||||
}
|
}
|
||||||
if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") {
|
if strings.HasPrefix(conf.Server.UILoginBackgroundURL, "/") {
|
||||||
|
|
123
core/metrics.go
Normal file
123
core/metrics.go
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
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))
|
||||||
|
}
|
|
@ -24,4 +24,5 @@ type PlayerRepository interface {
|
||||||
Get(id string) (*Player, error)
|
Get(id string) (*Player, error)
|
||||||
FindMatch(userName, client, typ string) (*Player, error)
|
FindMatch(userName, client, typ string) (*Player, error)
|
||||||
Put(p *Player) error
|
Put(p *Player) error
|
||||||
|
// TODO: Add CountAll method. Useful at least for metrics.
|
||||||
}
|
}
|
||||||
|
|
|
@ -151,8 +151,10 @@ func (s *scanner) RescanAll(ctx context.Context, fullRescan bool) error {
|
||||||
}
|
}
|
||||||
if hasError {
|
if hasError {
|
||||||
log.Error("Errors while scanning media. Please check the logs")
|
log.Error("Errors while scanning media. Please check the logs")
|
||||||
|
core.WriteAfterScanMetrics(ctx, s.ds, false)
|
||||||
return ErrScanError
|
return ErrScanError
|
||||||
}
|
}
|
||||||
|
core.WriteAfterScanMetrics(ctx, s.ds, true)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue